提取docx、xlsx、xls文件内容并填充,修改页面样式

This commit is contained in:
LHD_HY 2025-11-18 18:03:22 +08:00
parent df332f8138
commit 3e39671644
7 changed files with 275 additions and 78 deletions

View File

@ -38,11 +38,11 @@
"file-saver": "2.0.5",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"jquery": "^3.6.0",
"jquery-mousewheel": "^3.1.13",
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"jquery": "^3.6.0",
"jquery-mousewheel": "^3.1.13",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"luckysheet": "^2.1.13",

View File

@ -1,20 +1,20 @@
<template>
<div class="upload-container">
<el-upload
ref="upload"
class="upload-demo"
drag
action="#"
multiple
<el-upload
ref="upload"
class="upload-demo"
drag
action="#"
multiple
:show-file-list="true"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-change="handleFileChange"
:on-exceed="handleExceed"
:file-list="files"
:accept="accept"
:on-exceed="handleExceed"
:file-list="files"
:accept="accept"
:limit="limitUploadNum"
:auto-upload="autoUpload"
:auto-upload="autoUpload"
:http-request="customUpload"
>
<div class="upload-content">
@ -63,6 +63,8 @@ import {
uploadSmallFile,
uploadLargeFile,
} from '@/api/common/uploadFile.js'
import mammoth from 'mammoth'
import * as XLSX from 'xlsx'
//
const FILE_STATUS = {
@ -214,7 +216,7 @@ export default {
//
handleFileChange(file, fileList) {
if (this.shouldIgnoreFileChange(file.status)) return
this.files = this.formatFileList(fileList)
this.updatePreview(fileList)
},
@ -222,11 +224,11 @@ export default {
//
handleExceed(files, fileList) {
console.log('文件超出限制处理', files, fileList)
if (files.length > 0) {
//
this.$emit('del-file', { ...fileList[0], response: fileList[0].res })
//
this.files = []
const newFile = files[0]
@ -258,7 +260,7 @@ export default {
this.updatePreview(fileList)
this.files = this.formatFileList(fileList)
const delFileObj = this.findFileByRawFile(file.raw)
if (delFileObj) {
delFileObj.response = delFileObj.res
@ -273,7 +275,7 @@ export default {
async customUpload(options) {
const { file } = options
this.isUploading = true
const uploadFileObj = this.findFileByRawFile(file)
if (!uploadFileObj) {
this.handleError(new Error('文件对象不存在'), file)
@ -282,14 +284,14 @@ export default {
const fileUid = uploadFileObj.uid
const statusText = this.fileUploadRule.fields_json ? '识别中' : '上传中'
this.updateFileStatus(fileUid, FILE_STATUS.UPLOADING, statusText, null, 0)
this.$bus.$emit('startUpload', statusText)
try {
const formData = this.createFormData(file)
const res = await this.uploadFile(formData, file.size)
if (res.code === 200) {
this.handleSuccess(res, uploadFileObj)
} else {
@ -304,10 +306,26 @@ export default {
//
handleSuccess(response, file) {
this.$bus.$emit('endUpload')
this.updateFileStatus(file.uid, FILE_STATUS.SUCCESS, '', response.data, 100)
this.$emit('file-change', this.getCurrentFiles(), this.type)
this.isUploading = false
const fileExt = this.getFileExtension(file.name);
if (fileExt === 'docx') {
this.parseDocxFile(file.raw).then(text => {
// OCR
this.$emit('docx-content', text);
});
}
else if (fileExt === 'xlsx' || fileExt === 'xls') {
this.parseXlsxFile(file.raw).then(text => {
this.$emit('xlsx-content', text); // xlsx
}).catch(err => {
console.error('xlsx解析失败:', err);
});
}
this.$bus.$emit('endUpload')
this.updateFileStatus(file.uid, FILE_STATUS.SUCCESS, '', response.data, 100)
this.$emit('file-change', this.getCurrentFiles(), this.type)
this.isUploading = false
},
//
@ -318,6 +336,53 @@ export default {
this.isUploading = false
},
parseDocxFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(e) {
const arrayBuffer = e.target.result;
// 使mammothdocx
mammoth.extractRawText({ arrayBuffer })
.then(result => {
resolve(result.value); //
})
.catch(err => {
console.error('docx解析失败:', err);
reject(err);
});
};
reader.readAsArrayBuffer(file);
});
},
parseXlsxFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(e) {
try {
//
const data = new Uint8Array(e.target.result);
// Excel
const workbook = XLSX.read(data, { type: 'array' });
//
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// JSON100
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, range: 0, raw: false });
//
const text = jsonData.map(row => row.join('\t')).join('\n');
resolve(text);
} catch (err) {
console.error('xlsx解析失败:', err);
reject(err);
}
};
//
reader.readAsArrayBuffer(file);
});
},
//
getFileExtension(filename) {
return filename.split('.').pop().toLowerCase()
@ -337,7 +402,7 @@ export default {
async uploadFile(formData, fileSize) {
const isLargeFile = fileSize > DEFAULT_FILE_SIZE
const hasOcr = !!this.fileUploadRule.fields_json
if (isLargeFile) {
return hasOcr ? await uploadLargeFileByOcr(formData) : await uploadLargeFile(formData)
} else {
@ -346,8 +411,8 @@ export default {
},
findFileByRawFile(rawFile) {
return this.files.find(item =>
item.raw === rawFile ||
return this.files.find(item =>
item.raw === rawFile ||
(item.name === rawFile.name && item.size === rawFile.size)
)
},
@ -369,10 +434,10 @@ export default {
}
const updatedFile = { ...this.files[fileIndex], status }
if (statusText) updatedFile.statusText = statusText
if (percentage !== null) updatedFile.percentage = Math.max(0, Math.min(100, percentage))
if (responseData) {
updatedFile.response = responseData
updatedFile.response.businessType = this.fileUploadRule?.fileUploadType
@ -455,7 +520,7 @@ export default {
isDocumentFile(file) {
if (!file || !file.name) return false
const fileExtension = this.getFileExtension(file.name)
return ['pdf', 'doc', 'docx', 'xls', 'xlsx'].includes(fileExtension) ||
return ['pdf', 'doc', 'docx', 'xls', 'xlsx'].includes(fileExtension) ||
(file && file.fileType === FILE_TYPES.DOCUMENT)
},
@ -468,8 +533,8 @@ export default {
if (fileList.length === 1 && fileList[0]) {
const file = fileList[0].raw || fileList[0]
if (this.isImageFile(file)) {
fileList[0].lsFilePath ?
this.generateImagePreviewFromPath(fileList[0]) :
fileList[0].lsFilePath ?
this.generateImagePreviewFromPath(fileList[0]) :
this.generateImagePreview(file)
} else if (this.isDocumentFile(file)) {
this.generateDocumentPreview(file)
@ -515,12 +580,12 @@ export default {
if (fileList.length > 0) {
const firstFile = fileList[0]
if (firstFile?.lsFilePath) {
this.isImageFile(firstFile) ?
this.generateImagePreviewFromPath(firstFile) :
this.isImageFile(firstFile) ?
this.generateImagePreviewFromPath(firstFile) :
this.generateDocumentPreview(firstFile)
} else if (firstFile?.raw) {
this.isImageFile(firstFile.raw) ?
this.generateImagePreview(firstFile.raw) :
this.isImageFile(firstFile.raw) ?
this.generateImagePreview(firstFile.raw) :
this.generateDocumentPreview(firstFile.raw)
} else {
this.clearPreview()

View File

@ -101,6 +101,8 @@ export default {
this.$refs.financialStatement.validate()
])
console.log('处理后的年份:', financeReportData);
// 2.
const formData = {
...financeReportData, // fileNamereportYear

View File

@ -22,6 +22,7 @@
@del-file="handleDelFile"
:uploadType="uploadType"
:maxFileTips="maxFileTips"
@docx-content="handleDocxContent"
:limitUploadNum="1"
type="finance_report"
:show-file-list="true"
@ -69,7 +70,7 @@ export default {
dicts: ['identification_tag'],
data() {
return {
uploadType: 'pdf、doc、docx',
uploadType: 'docx',
maxFileTips: '100MB',
ocrRuleList: ['finance_report'],
fileUploadList: [],
@ -112,7 +113,58 @@ export default {
}
},
methods: {
setFormData(data) {
handleDocxContent(text) {
// OCRdocx
const mockOcrResult = {
status_code: 200,
data: {
chat_res: this.extractInfoFromDocx(text) //
}
};
// OCR
this.handleOcrResultWithMock(mockOcrResult);
},
// docx
extractInfoFromDocx(text) {
console.log("text", text);
// 2026:202620262026
const yearMatch = text.match(
/(年份|报告年份|年度)\s*[:]\s*(\d{4})|(\d{4})\s*(年|年度)/
);
console.log("yearMatch", yearMatch);
// +/
const year = yearMatch ? (yearMatch[2] || yearMatch[3]) : '';
return { "报告年份": year };
},
// OCR
handleOcrResultWithMock(ocrResult) {
if (ocrResult.status_code === 200) {
const chat_res = ocrResult.data?.chat_res;
if (chat_res && typeof chat_res === 'object') {
Object.keys(chat_res).forEach(key => {
const formField = this.ocrResultParams[key];
if (formField === 'reportYear' && chat_res[key]) {
const year = chat_res[key].replace(/[^\d]/g, '');
this.formData[formField] = year.length === 4 ? year : '';
}
});
this.$message.success('docx内容解析成功已自动填充报告年份');
this.$refs.financeReportForm.validateField('reportYear');
}
} else {
this.$message.error(`年份提取失败: ${ocrResult.status_msg || '未找到有效年份'}`);
}
},
setFormData(data) {
// uid
const financeFiles = Array.isArray(data.fileList)
? data.fileList.filter(file => file && file.businessType === 'finance_report')
@ -125,11 +177,22 @@ export default {
}))
: [];
let reportYear = data.reportYear || '';
if (reportYear) {
if (typeof reportYear === 'string') {
reportYear = reportYear.split('-')[0];
}
if (typeof reportYear === 'number') {
reportYear = reportYear.toString();
}
}
this.formData = {
...(data || {}),
fileList: financeFiles,
fileName: financeFiles.length > 0 ? this.getFileName(financeFiles[0]) : '',
delFileList: []
delFileList: [],
reportYear: reportYear,
}
},
@ -148,12 +211,10 @@ export default {
handleFileChange(files, type) {
if (type === 'finance_report' && files instanceof Array) {
//
if (files.length > 0 && files[0].response) { //
//
if (files.length > 0 && files[0].response) {
this.isReplacingFile = false;
//
//
if (this.formData.fileList.length > 0) {
const oldFile = this.formData.fileList[0];
const delPath = oldFile?.response?.fileRes?.filePath || oldFile?.filePath || null;
@ -169,16 +230,28 @@ export default {
this.formData.fileList = markedFiles;
//
//
if (markedFiles.length > 0) {
const originalFileName = this.getFileName(markedFiles[0])
const safeFileName = originalFileName.replace(/--+/g, '-')
this.formData.fileName = safeFileName
this.$refs.financeReportForm.validateField('fileName')
}
this.handleOcrResult(markedFiles)
//
const firstFile = markedFiles[0];
const fileExt = this.getFileExt(firstFile).toLowerCase(); //
if (['doc', 'docx'].includes(fileExt)) {
// doc/docxOCR
} else if (['pdf', 'jpg', 'jpeg', 'png'].includes(fileExt)) {
// pdf/OCR
this.handleOcrResult(markedFiles);
} else {
//
this.$message.warning('不支持的文件格式,无法解析年份');
}
} else if (!this.isReplacingFile) {
//
this.formData.fileList = [];
this.formData.fileName = '';
}

View File

@ -24,6 +24,7 @@
:maxFileTips="maxFileTips"
:limitUploadNum="1"
type="financial_statement"
@xlsx-content="handleXlsxContent"
:show-file-list="true"
/>
</el-form-item>
@ -144,7 +145,7 @@ export default {
dicts: ['identification_tag'],
data() {
return {
uploadType: 'pdf、doc、docx',
uploadType: 'xlsx、xls',
maxFileTips: '100MB',
ocrRuleList: ['financial_statement'],
fileUploadList: [],
@ -231,6 +232,56 @@ export default {
}
},
methods: {
handleXlsxContent(text) {
this.$bus.$emit('startUpload', '正在解析Excel财务数据')
try {
// Excel
const financialData = this.extractFinancialDataFromText(text);
//
this.fillFormWithFinancialData(financialData);
this.$message.success('Excel解析成功已自动填充财务数据');
} catch (error) {
this.$message.error(`Excel解析失败: ${error.message}`);
} finally {
this.$bus.$emit('endUpload');
}
},
//
extractFinancialDataFromText(text) {
const data = {};
const keywords = Object.keys(this.ocrResultParams); //
//
keywords.forEach(keyword => {
// ": "" "
const regex = new RegExp(`${keyword}\\s*[:]?\\s*([\\d,.%]+)`, 'i');
const match = text.match(regex);
if (match && match[1]) {
//
data[keyword] = match[1].replace(/,/g, '').trim();
}
});
return data;
},
//
fillFormWithFinancialData(financialData) {
Object.keys(financialData).forEach(keyword => {
const formField = this.ocrResultParams[keyword];
if (formField) {
//
this.formData[formField] = financialData[keyword].replace(/[^\d.%]/g, '');
}
});
//
this.$refs.financialStatementForm.validate();
},
setFormData(data) {
// uid
const financialFiles = Array.isArray(data.fileList)
@ -284,9 +335,14 @@ export default {
})).slice(-1);
this.formData.fileList = markedFiles;
this.handleOcrResult(markedFiles)
const fileExt = this.getFileExt(markedFiles[0]);
if (['xls', 'xlsx'].includes(fileExt)) {
// @xlsx-content
} else {
// ExcelOCR
this.handleOcrResult(markedFiles);
}
} else if (!this.isReplacingFile) {
//
this.formData.fileList = [];
}
}

View File

@ -106,7 +106,7 @@ export default {
dicts: ['identification_tag'],
data() {
return {
uploadType: 'doc、docx',
uploadType: 'jpg、png、pdf',
maxFileTips: '100MB',
ocrRuleList: ['qualification_certificate'], // OCR
fileUploadList: [],

View File

@ -45,7 +45,7 @@
<!-- 资质卡片网格 -->
<div class="qualification-grid">
<!-- 新增资质卡片 -->
<div class="grid-item">
<div class="qualification-card-wrapper">
<div
class="qualification-card create-card"
@click="handleAdd"
@ -59,7 +59,7 @@
</div>
<!-- 资质信息卡片及下方的过期提示 -->
<div class="grid-item" v-for="item in qualificationList" :key="item.qualificationId">
<div class="qualification-card-wrapper" v-for="item in qualificationList" :key="item.qualificationId">
<!-- 卡片本身 -->
<div class="qualification-card">
<div class="qualification-header">
@ -114,8 +114,8 @@
</div>
<!-- 过期提示 -->
<div class="expired-tag" v-if="item.isExpired">
<i class="el-icon-warning"></i> {{ item.certificateName || '证书' }}已过期
<div class="expired-tags" v-if="item.isExpired">
<span class="expired-text">证书已过期</span>
</div>
</div>
</div>
@ -220,7 +220,6 @@ export default {
methods: {
loadQualificationTypeDict() {
// getDicts qualification_type
this.getDicts('qualification_type').then(response => {
this.qualificationTypeOptions = response.data.map(item => ({
@ -459,36 +458,35 @@ export default {
.qualification-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 20px;
gap: 24px;
margin-bottom: 30px;
max-height: calc(100vh - 280px);
overflow-y: auto;
padding: 10px;
padding: 20px 6px 20px 0px;
align-items: start;
&::-webkit-scrollbar {
width: 8px;
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
&:hover {
background: #a8a8a8;
background: rgba(0, 0, 0, 0.3);
}
}
}
//
.grid-item {
//
.qualification-card-wrapper {
display: flex;
flex-direction: column;
gap: 8px; //
}
.qualification-card {
@ -645,19 +643,22 @@ export default {
}
}
// qualification-card
.expired-tag {
padding: 6px 0;
text-align: center;
background: transparent; //
color: #ff4d4f; //
font-size: 12px;
font-weight: 500;
width: 100%; //
//
.expired-tags {
padding: 8px 12px;
flex-shrink: 0;
i {
margin-right: 5px;
.expired-text {
display: block;
color: #db3e29;
font-size: 14px;
line-height: 1.5;
word-break: break-all;
i {
margin-right: 5px;
font-size: 14px;
}
}
}