提取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", "file-saver": "2.0.5",
"fuse.js": "6.4.3", "fuse.js": "6.4.3",
"highlight.js": "9.18.5", "highlight.js": "9.18.5",
"jquery": "^3.6.0",
"jquery-mousewheel": "^3.1.13",
"js-beautify": "1.13.0", "js-beautify": "1.13.0",
"js-cookie": "3.0.1", "js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1", "jsencrypt": "3.0.0-rc.1",
"jquery": "^3.6.0",
"jquery-mousewheel": "^3.1.13",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"luckysheet": "^2.1.13", "luckysheet": "^2.1.13",

View File

@ -1,20 +1,20 @@
<template> <template>
<div class="upload-container"> <div class="upload-container">
<el-upload <el-upload
ref="upload" ref="upload"
class="upload-demo" class="upload-demo"
drag drag
action="#" action="#"
multiple multiple
:show-file-list="true" :show-file-list="true"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:on-remove="handleRemove" :on-remove="handleRemove"
:on-change="handleFileChange" :on-change="handleFileChange"
:on-exceed="handleExceed" :on-exceed="handleExceed"
:file-list="files" :file-list="files"
:accept="accept" :accept="accept"
:limit="limitUploadNum" :limit="limitUploadNum"
:auto-upload="autoUpload" :auto-upload="autoUpload"
:http-request="customUpload" :http-request="customUpload"
> >
<div class="upload-content"> <div class="upload-content">
@ -63,6 +63,8 @@ import {
uploadSmallFile, uploadSmallFile,
uploadLargeFile, uploadLargeFile,
} from '@/api/common/uploadFile.js' } from '@/api/common/uploadFile.js'
import mammoth from 'mammoth'
import * as XLSX from 'xlsx'
// //
const FILE_STATUS = { const FILE_STATUS = {
@ -214,7 +216,7 @@ export default {
// //
handleFileChange(file, fileList) { handleFileChange(file, fileList) {
if (this.shouldIgnoreFileChange(file.status)) return if (this.shouldIgnoreFileChange(file.status)) return
this.files = this.formatFileList(fileList) this.files = this.formatFileList(fileList)
this.updatePreview(fileList) this.updatePreview(fileList)
}, },
@ -222,11 +224,11 @@ export default {
// //
handleExceed(files, fileList) { handleExceed(files, fileList) {
console.log('文件超出限制处理', files, fileList) console.log('文件超出限制处理', files, fileList)
if (files.length > 0) { if (files.length > 0) {
// //
this.$emit('del-file', { ...fileList[0], response: fileList[0].res }) this.$emit('del-file', { ...fileList[0], response: fileList[0].res })
// //
this.files = [] this.files = []
const newFile = files[0] const newFile = files[0]
@ -258,7 +260,7 @@ export default {
this.updatePreview(fileList) this.updatePreview(fileList)
this.files = this.formatFileList(fileList) this.files = this.formatFileList(fileList)
const delFileObj = this.findFileByRawFile(file.raw) const delFileObj = this.findFileByRawFile(file.raw)
if (delFileObj) { if (delFileObj) {
delFileObj.response = delFileObj.res delFileObj.response = delFileObj.res
@ -273,7 +275,7 @@ export default {
async customUpload(options) { async customUpload(options) {
const { file } = options const { file } = options
this.isUploading = true this.isUploading = true
const uploadFileObj = this.findFileByRawFile(file) const uploadFileObj = this.findFileByRawFile(file)
if (!uploadFileObj) { if (!uploadFileObj) {
this.handleError(new Error('文件对象不存在'), file) this.handleError(new Error('文件对象不存在'), file)
@ -282,14 +284,14 @@ export default {
const fileUid = uploadFileObj.uid const fileUid = uploadFileObj.uid
const statusText = this.fileUploadRule.fields_json ? '识别中' : '上传中' const statusText = this.fileUploadRule.fields_json ? '识别中' : '上传中'
this.updateFileStatus(fileUid, FILE_STATUS.UPLOADING, statusText, null, 0) this.updateFileStatus(fileUid, FILE_STATUS.UPLOADING, statusText, null, 0)
this.$bus.$emit('startUpload', statusText) this.$bus.$emit('startUpload', statusText)
try { try {
const formData = this.createFormData(file) const formData = this.createFormData(file)
const res = await this.uploadFile(formData, file.size) const res = await this.uploadFile(formData, file.size)
if (res.code === 200) { if (res.code === 200) {
this.handleSuccess(res, uploadFileObj) this.handleSuccess(res, uploadFileObj)
} else { } else {
@ -304,10 +306,26 @@ export default {
// //
handleSuccess(response, file) { handleSuccess(response, file) {
this.$bus.$emit('endUpload') const fileExt = this.getFileExtension(file.name);
this.updateFileStatus(file.uid, FILE_STATUS.SUCCESS, '', response.data, 100) if (fileExt === 'docx') {
this.$emit('file-change', this.getCurrentFiles(), this.type) this.parseDocxFile(file.raw).then(text => {
this.isUploading = false // 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 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) { getFileExtension(filename) {
return filename.split('.').pop().toLowerCase() return filename.split('.').pop().toLowerCase()
@ -337,7 +402,7 @@ export default {
async uploadFile(formData, fileSize) { async uploadFile(formData, fileSize) {
const isLargeFile = fileSize > DEFAULT_FILE_SIZE const isLargeFile = fileSize > DEFAULT_FILE_SIZE
const hasOcr = !!this.fileUploadRule.fields_json const hasOcr = !!this.fileUploadRule.fields_json
if (isLargeFile) { if (isLargeFile) {
return hasOcr ? await uploadLargeFileByOcr(formData) : await uploadLargeFile(formData) return hasOcr ? await uploadLargeFileByOcr(formData) : await uploadLargeFile(formData)
} else { } else {
@ -346,8 +411,8 @@ export default {
}, },
findFileByRawFile(rawFile) { findFileByRawFile(rawFile) {
return this.files.find(item => return this.files.find(item =>
item.raw === rawFile || item.raw === rawFile ||
(item.name === rawFile.name && item.size === rawFile.size) (item.name === rawFile.name && item.size === rawFile.size)
) )
}, },
@ -369,10 +434,10 @@ export default {
} }
const updatedFile = { ...this.files[fileIndex], status } const updatedFile = { ...this.files[fileIndex], status }
if (statusText) updatedFile.statusText = statusText if (statusText) updatedFile.statusText = statusText
if (percentage !== null) updatedFile.percentage = Math.max(0, Math.min(100, percentage)) if (percentage !== null) updatedFile.percentage = Math.max(0, Math.min(100, percentage))
if (responseData) { if (responseData) {
updatedFile.response = responseData updatedFile.response = responseData
updatedFile.response.businessType = this.fileUploadRule?.fileUploadType updatedFile.response.businessType = this.fileUploadRule?.fileUploadType
@ -455,7 +520,7 @@ export default {
isDocumentFile(file) { isDocumentFile(file) {
if (!file || !file.name) return false if (!file || !file.name) return false
const fileExtension = this.getFileExtension(file.name) 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) (file && file.fileType === FILE_TYPES.DOCUMENT)
}, },
@ -468,8 +533,8 @@ export default {
if (fileList.length === 1 && fileList[0]) { if (fileList.length === 1 && fileList[0]) {
const file = fileList[0].raw || fileList[0] const file = fileList[0].raw || fileList[0]
if (this.isImageFile(file)) { if (this.isImageFile(file)) {
fileList[0].lsFilePath ? fileList[0].lsFilePath ?
this.generateImagePreviewFromPath(fileList[0]) : this.generateImagePreviewFromPath(fileList[0]) :
this.generateImagePreview(file) this.generateImagePreview(file)
} else if (this.isDocumentFile(file)) { } else if (this.isDocumentFile(file)) {
this.generateDocumentPreview(file) this.generateDocumentPreview(file)
@ -515,12 +580,12 @@ export default {
if (fileList.length > 0) { if (fileList.length > 0) {
const firstFile = fileList[0] const firstFile = fileList[0]
if (firstFile?.lsFilePath) { if (firstFile?.lsFilePath) {
this.isImageFile(firstFile) ? this.isImageFile(firstFile) ?
this.generateImagePreviewFromPath(firstFile) : this.generateImagePreviewFromPath(firstFile) :
this.generateDocumentPreview(firstFile) this.generateDocumentPreview(firstFile)
} else if (firstFile?.raw) { } else if (firstFile?.raw) {
this.isImageFile(firstFile.raw) ? this.isImageFile(firstFile.raw) ?
this.generateImagePreview(firstFile.raw) : this.generateImagePreview(firstFile.raw) :
this.generateDocumentPreview(firstFile.raw) this.generateDocumentPreview(firstFile.raw)
} else { } else {
this.clearPreview() this.clearPreview()

View File

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

View File

@ -22,6 +22,7 @@
@del-file="handleDelFile" @del-file="handleDelFile"
:uploadType="uploadType" :uploadType="uploadType"
:maxFileTips="maxFileTips" :maxFileTips="maxFileTips"
@docx-content="handleDocxContent"
:limitUploadNum="1" :limitUploadNum="1"
type="finance_report" type="finance_report"
:show-file-list="true" :show-file-list="true"
@ -69,7 +70,7 @@ export default {
dicts: ['identification_tag'], dicts: ['identification_tag'],
data() { data() {
return { return {
uploadType: 'pdf、doc、docx', uploadType: 'docx',
maxFileTips: '100MB', maxFileTips: '100MB',
ocrRuleList: ['finance_report'], ocrRuleList: ['finance_report'],
fileUploadList: [], fileUploadList: [],
@ -112,7 +113,58 @@ export default {
} }
}, },
methods: { 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 // uid
const financeFiles = Array.isArray(data.fileList) const financeFiles = Array.isArray(data.fileList)
? data.fileList.filter(file => file && file.businessType === 'finance_report') ? 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 = { this.formData = {
...(data || {}), ...(data || {}),
fileList: financeFiles, fileList: financeFiles,
fileName: financeFiles.length > 0 ? this.getFileName(financeFiles[0]) : '', fileName: financeFiles.length > 0 ? this.getFileName(financeFiles[0]) : '',
delFileList: [] delFileList: [],
reportYear: reportYear,
} }
}, },
@ -148,12 +211,10 @@ export default {
handleFileChange(files, type) { handleFileChange(files, type) {
if (type === 'finance_report' && files instanceof Array) { if (type === 'finance_report' && files instanceof Array) {
// if (files.length > 0 && files[0].response) {
if (files.length > 0 && files[0].response) { //
//
this.isReplacingFile = false; this.isReplacingFile = false;
// //
if (this.formData.fileList.length > 0) { if (this.formData.fileList.length > 0) {
const oldFile = this.formData.fileList[0]; const oldFile = this.formData.fileList[0];
const delPath = oldFile?.response?.fileRes?.filePath || oldFile?.filePath || null; const delPath = oldFile?.response?.fileRes?.filePath || oldFile?.filePath || null;
@ -169,16 +230,28 @@ export default {
this.formData.fileList = markedFiles; this.formData.fileList = markedFiles;
// //
if (markedFiles.length > 0) { if (markedFiles.length > 0) {
const originalFileName = this.getFileName(markedFiles[0]) const originalFileName = this.getFileName(markedFiles[0])
const safeFileName = originalFileName.replace(/--+/g, '-') const safeFileName = originalFileName.replace(/--+/g, '-')
this.formData.fileName = safeFileName this.formData.fileName = safeFileName
this.$refs.financeReportForm.validateField('fileName') 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) { } else if (!this.isReplacingFile) {
//
this.formData.fileList = []; this.formData.fileList = [];
this.formData.fileName = ''; this.formData.fileName = '';
} }

View File

@ -24,6 +24,7 @@
:maxFileTips="maxFileTips" :maxFileTips="maxFileTips"
:limitUploadNum="1" :limitUploadNum="1"
type="financial_statement" type="financial_statement"
@xlsx-content="handleXlsxContent"
:show-file-list="true" :show-file-list="true"
/> />
</el-form-item> </el-form-item>
@ -144,7 +145,7 @@ export default {
dicts: ['identification_tag'], dicts: ['identification_tag'],
data() { data() {
return { return {
uploadType: 'pdf、doc、docx', uploadType: 'xlsx、xls',
maxFileTips: '100MB', maxFileTips: '100MB',
ocrRuleList: ['financial_statement'], ocrRuleList: ['financial_statement'],
fileUploadList: [], fileUploadList: [],
@ -231,6 +232,56 @@ export default {
} }
}, },
methods: { 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) { setFormData(data) {
// uid // uid
const financialFiles = Array.isArray(data.fileList) const financialFiles = Array.isArray(data.fileList)
@ -284,9 +335,14 @@ export default {
})).slice(-1); })).slice(-1);
this.formData.fileList = markedFiles; 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) { } else if (!this.isReplacingFile) {
//
this.formData.fileList = []; this.formData.fileList = [];
} }
} }

View File

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

View File

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