diff --git a/klp-ui/src/api/system/ocr.js b/klp-ui/src/api/system/ocr.js index c50f687a..9f22df3d 100644 --- a/klp-ui/src/api/system/ocr.js +++ b/klp-ui/src/api/system/ocr.js @@ -27,4 +27,18 @@ export function recognizeBomByModel({ imageUrl }) { data: { imageUrl }, timeout: 100000 }) +} + +/** + * 调用ocr, 识别pdf中的文字 + * + * @param {string} imageUrl 图片url + * @returns {Promise} 识别结果 + */ +export function recognizePdfText({ pdfUrl }) { + return request({ + url: '/wms/purchasePlan/recognizePdfText', + method: 'post', + data: { pdfUrl }, + }) } \ No newline at end of file diff --git a/klp-ui/src/views/wms/purchasePlan/index.vue b/klp-ui/src/views/wms/purchasePlan/index.vue index cc432943..61913f12 100644 --- a/klp-ui/src/views/wms/purchasePlan/index.vue +++ b/klp-ui/src/views/wms/purchasePlan/index.vue @@ -528,12 +528,13 @@ export default { }, handleAccessory(row) { this.accessoryOpen = true; - this.accessory = row.accessory; + this.form = row; + this.accessory = row.attachmentInfo; }, handleAccessorySuccess(fileList) { updatePurchasePlan({ - planId: this.accessory.planId, - accessory: fileList.map(item => item.ossId).join(',') + planId: this.form.planId, + attachmentInfo: fileList.map(item => item.ossId).join(',') }).then(response => { this.$modal.msgSuccess("附件上传成功"); this.getList(); diff --git a/klp-ui/src/views/wms/purchasePlan/panels/merger.vue b/klp-ui/src/views/wms/purchasePlan/panels/merger.vue index 1b04da54..38ec1ec3 100644 --- a/klp-ui/src/views/wms/purchasePlan/panels/merger.vue +++ b/klp-ui/src/views/wms/purchasePlan/panels/merger.vue @@ -12,9 +12,19 @@ - - - + + + + + + + + + @@ -69,10 +79,14 @@ export default { ], uploadQualityCertificateForm: { qualityCertificateType: 1 - } + }, + selection: [] } }, methods: { + handleSelectionChange(selection) { + this.selection = selection; + }, async handleConfirm() { if (!this.uploadQualityCertificateForm.qualityCertificateType) { this.$modal.msgError('请选择处理方式'); @@ -97,12 +111,12 @@ export default { rawMaterialId: this.info.rawMaterialId, bomId: bom.data.bomId, }) - // 逐项创建BOMItem - for (let i = 0; i < this.newResult.length; i++) { + // 逐项创建BOMItem, 只创建选中的 + for (let i = 0; i < this.selection.length; i++) { await addBomItem({ bomId: bom.data.bomId, - attrKey: this.newResult[i].attrKey, - attrValue: this.newResult[i].attrValue, + attrKey: this.selection[i].attrKey, + attrValue: this.selection[i].attrValue, }) } this.$store.dispatch('category/getBomMap'); @@ -115,11 +129,11 @@ export default { bomCode: 'N' + new Date().getTime(), }) // 逐项创建BOMItem - for (let i = 0; i < this.newResult.length; i++) { + for (let i = 0; i < this.selection.length; i++) { await addBomItem({ bomId: bom.data.bomId, - attrKey: this.newResult[i].attrKey, - attrValue: this.newResult[i].attrValue, + attrKey: this.selection[i].attrKey, + attrValue: this.selection[i].attrValue, }) } // 创建一个新的物料,使用原有物料的信息, code使用时间戳 diff --git a/klp-ui/src/views/wms/purchasePlan/panels/qualityCerticate.vue b/klp-ui/src/views/wms/purchasePlan/panels/qualityCerticate.vue index 3576776f..8f57e809 100644 --- a/klp-ui/src/views/wms/purchasePlan/panels/qualityCerticate.vue +++ b/klp-ui/src/views/wms/purchasePlan/panels/qualityCerticate.vue @@ -5,54 +5,85 @@ -
- -
+
+ +
-
+
+ + + + + + + +
+ + +
+ OCR识别 + + + +
+ ocr +
+
+ +
+
+ 大模型识别 + + + +
+ model +
+
+
+
+ +
+
+ +
+
- + - - -
- ocr -
+ + + +
- -
- model + +
+
+ 确认
+
-
-
- -
-
- - - - - - - - - - - -
- -
-
-
- 确认 -
+ + +
+ 每行一个字段名,顺序影响识别 +
+ + 取消 + 保存 + +
@@ -60,7 +91,7 @@ import FileUpload from '@/components/FileUpload' import { listByIds } from '@/api/system/oss' import { updatePurchasePlanDetail } from '@/api/wms/purchasePlanDetail' -import { recognizeText, recognizeBomByModel } from '@/api/system/ocr' +import { recognizeText, recognizeBomByModel, recognizePdfText } from '@/api/system/ocr' import { listBomItem } from '@/api/wms/bomItem' import { getRawMaterial } from '@/api/wms/rawMaterial' import Merger from './merger.vue' @@ -81,8 +112,78 @@ const so = { ocr: { loading: '等待ocr识别结果', handler: async (vm) => { - const text = await recognizeText({ imgUrl: vm.file.url }) - return text; + function extractData(text) { + // 优先读取localStorage配置的keyList + let keyList = []; + try { + const local = localStorage.getItem('ocrKeyList'); + if (local) { + keyList = local.split('\n').map(k => k.trim()).filter(k => k); + } + } catch { } + // 默认keyList + if (!keyList.length) { + keyList = [ + "订货单位", "合同号", "产品名称", "执行标准", + "卷号", "原料坯号", "规格", "材质", + "表面状态", "调制度", "切边要求", "包装种类", + "毛重", "净重", "参考长度" + ]; + } + // 预处理:将复合键合并(如"合同号"、"卷号"等) + let normalizedText = text + .replace(/卷\s*号/g, "卷号") + .replace(/原料\s*坯号/g, "原料坯号") + .replace(/切边\s*要求/g, "切边要求") + .replace(/包装\s*种类/g, "包装种类"); + const lines = normalizedText.split(/\r?\n/).filter(line => line.trim()); + const result = []; + // 核心数据行处理(跳过首行公司名) + for (let i = 1; i < lines.length; i++) { + let currentLine = lines[i]; + let remaining = currentLine; + // 处理单行中的多组键值对 + while (remaining) { + let found = false; + // 检查剩余文本是否以已知键开头 + for (const key of keyList) { + if (remaining.startsWith(key)) { + found = true; + // 移动到键之后的位置 + let afterKey = remaining.substring(key.length).trim(); + // 查找下一个键的起始位置 + let nextKeyIndex = afterKey.length; + for (const k of keyList) { + const idx = afterKey.indexOf(k); + if (idx > -1 && idx < nextKeyIndex) { + nextKeyIndex = idx; + } + } + // 提取当前键的值 + const value = afterKey.substring(0, nextKeyIndex).trim(); + result.push({ + attrKey: key, + attrValue: key === "毛重" || key === "净重" + ? value.replace(/\D/g, "") // 提取数字部分 + : value + }); + // 更新剩余文本 + remaining = afterKey.substring(nextKeyIndex).trim(); + break; + } + } + // 如果没有找到更多键,结束循环 + if (!found) break; + } + } + return result; + } + const { text } = (await recognizePdfText({ pdfUrl: vm.file.url })).data + console.log(text) + const result = extractData(text) + console.log(result) + vm.newResult = result; + return result; } }, model: { @@ -140,7 +241,7 @@ const so = { } } -// 原子操作, 用于细化的进度展示, 无论其同步还是异步一律视作异步函数执行, 并返回一个Promise +// 原子操作, 用于细化的进度展示, 無論其同步還是異步一律視作異步函數執行, 並返回一個Promise const atoms = { } @@ -173,7 +274,7 @@ export default { 'uploadQualityCertificateForm.qualityCertificate': { handler(newVal) { if (newVal) { - this.loadingMethod('oss') + this.loadingMethod('oss') } this.loadingMethod('annex') }, @@ -195,10 +296,17 @@ export default { newResult: [], modelImage, ocrImage, + showKeyListDialog: false, + keyListInput: '', } }, methods: { handleOcr() { + // 识别file是不是pdf, 只有pdf可以使用ocr + if (!this.file.url.endsWith('.pdf')) { + this.$modal.msgError('质保单不是pdf格式'); + return; + } this.loadingMethod('ocr', async (res) => { await this.loadingMethod('old') await this.loadingMethod('compare') @@ -211,7 +319,7 @@ export default { await this.loadingMethod('compare') this.active = 2; }) - + }, async handleConfirm() { // 变更状态 @@ -259,7 +367,31 @@ export default { } } this.resultDiff = false; - } + }, + // 保存keyList到localStorage + saveKeyList() { + localStorage.setItem('ocrKeyList', this.keyListInput) + this.showKeyListDialog = false + this.$message.success('已保存OCR字段配置') + }, + // 打开弹窗时初始化内容 + openKeyListDialog() { + let local = '' + try { + local = localStorage.getItem('ocrKeyList') || '' + } catch { } + if (local) { + this.keyListInput = local + } else { + this.keyListInput = [ + "订货单位", "合同号", "产品名称", "执行标准", + "卷号", "原料坯号", "规格", "材质", + "表面状态", "调制度", "切边要求", "包装种类", + "毛重", "净重", "参考长度" + ].join('\n') + } + this.showKeyListDialog = true + }, } }