From bb9eba36e5b20acf2c83881ae73060b17b2c837e Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Thu, 5 Feb 2026 11:18:25 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=BE=93=E5=85=A5RFID?= =?UTF-8?q?=E5=89=8D=E9=81=93+=E7=BB=91=E5=AE=9A+Converting=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E6=89=B9=E9=87=8F=E6=96=B0=E5=A2=9E=E5=B7=A5=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/erf/components/expTriConfirm.vue | 207 +++++++++++++++--- 1 file changed, 178 insertions(+), 29 deletions(-) diff --git a/src/views/modules/erf/components/expTriConfirm.vue b/src/views/modules/erf/components/expTriConfirm.vue index cd43ff3..be7c8f3 100644 --- a/src/views/modules/erf/components/expTriConfirm.vue +++ b/src/views/modules/erf/components/expTriConfirm.vue @@ -359,12 +359,13 @@ @@ -423,10 +424,10 @@
- 取消 批量创建({{ validBatchCount }}个) + 取消
@@ -744,10 +745,15 @@ export default { this.batchProcessList = [] this.batchAddDialogVisible = true this.loadTemplateList() + //自动获取焦点 + this.$nextTick(() => { + this.$refs.batchInput.focus(); + }); }, /** * 处理批量输入(失去焦点或回车时触发) + * 智能解析多种输入格式,支持模糊匹配模板 */ handleBatchInput() { if (!this.batchProcessInput || !this.batchProcessInput.trim()) { @@ -755,45 +761,62 @@ export default { return } - // 智能解析输入,支持多种分隔符 - let text = this.batchProcessInput - text = text.replace(/\+/g, '\n') - .replace(/、/g, '\n') - .replace(/,/g, '\n') - .replace(/,/g, '\n') - .replace(/;/g, '\n') - .replace(/;/g, '\n') - .replace(/\|/g, '\n') - - const names = text.split('\n') + let text = this.batchProcessInput.trim() + + // 检查是否包含明确的分隔符 + const hasExplicitSeparator = /[\+、,,;;\|]/.test(text) || + /\d+[\.\)]/.test(text) || + /然后|接着|再做|再进行|之后/.test(text) + + // 智能解析输入,支持多种分隔符和格式 + text = text + // 处理数字序号:1. 2. 或 1) 2) 或 1、 2、 + .replace(/\d+[\.\)、]\s*/g, '\n') + // 处理中文序号:一、二、或 第一 第二 + .replace(/[一二三四五六七八九十]+[、\.\)]\s*/g, '\n') + .replace(/第[一二三四五六七八九十]+[步道序个]\s*/g, '\n') + // 常规分隔符 + .replace(/[\+、,,;;\|]/g, '\n') + // 中文连接词 + .replace(/然后|接着|再做|再进行|之后/g, '\n') + + // 如果没有明确分隔符,将空格(包括单个空格)也视为分隔符 + if (!hasExplicitSeparator) { + text = text.replace(/\s+/g, '\n') + } else { + // 有明确分隔符时,只处理多个空格 + text = text.replace(/\s{2,}/g, '\n') + } + + // 分割并清理 + let names = text.split('\n') .map(n => n.trim()) - .filter(n => n.length > 0) + .filter(n => n.length > 1 && !/^[\d\s]+$/.test(n)) // 过滤纯数字和单字符 if (names.length === 0) { this.batchProcessList = [] + this.$message.warning('未识别到有效的工序名称') return } - // 去重 - const uniqueNames = [...new Set(names)] + // 智能去重(相似工序名合并) + const uniqueNames = this.smartDeduplication(names) - // 匹配模版 + // 智能匹配模版 this.batchProcessList = uniqueNames.map(name => { - const template = this.templateList.find(t => - t.templateName === name || t.templateName.includes(name) || name.includes(t.templateName) - ) + const matchResult = this.smartMatchTemplate(name) - if (template) { + if (matchResult.template) { return { - processStep: name, + processStep: matchResult.useName, // 使用匹配到的标准名称 matched: true, - prodApproverName: template.prodApproverName, - prodApproverUserId: template.prodApproverUserId, - qaApproverName: template.qaApproverName, - qaApproverUserId: template.qaApproverUserId, - techApproverName: template.techApproverName, - techApproverUserId: template.techApproverUserId, - remark: template.remark || '' + prodApproverName: matchResult.template.prodApproverName, + prodApproverUserId: matchResult.template.prodApproverUserId, + qaApproverName: matchResult.template.qaApproverName, + qaApproverUserId: matchResult.template.qaApproverUserId, + techApproverName: matchResult.template.techApproverName, + techApproverUserId: matchResult.template.techApproverUserId, + remark: matchResult.template.remark || '' } } @@ -809,6 +832,132 @@ export default { remark: '' } }) + + // 统计并提示 + const matchedCount = this.batchProcessList.filter(p => p.matched).length + if (matchedCount > 0) { + this.$message.success(`识别${uniqueNames.length}个工序,自动匹配${matchedCount}个模板`) + } + }, + + /** + * 智能去重 - 识别相似工序名 + * 例如: "RFID前道" 和 "RFID 前道" 会被识别为同一个 + */ + smartDeduplication(names) { + const normalized = names.map(name => ({ + original: name, + normalized: name.replace(/\s+/g, '').toLowerCase() + })) + + const unique = [] + const seen = new Set() + + for (const item of normalized) { + if (!seen.has(item.normalized)) { + seen.add(item.normalized) + unique.push(item.original) + } + } + + return unique + }, + + /** + * 智能模板匹配算法 + * @param {string} inputName - 用户输入的工序名称 + * @return {Object} { template, useName } + */ + smartMatchTemplate(inputName) { + if (!this.templateList || this.templateList.length === 0) { + return { template: null, useName: inputName } + } + + let bestMatch = null + let bestScore = 0 + let bestUseName = inputName + + for (const template of this.templateList) { + const templateName = template.templateName + const score = this.calculateMatchScore(inputName, templateName) + + if (score > bestScore) { + bestScore = score + bestMatch = template + // 如果完全匹配或高相似度,使用模板标准名称 + bestUseName = score >= 80 ? templateName : inputName + } + } + + // 相似度阈值:60分以上才算匹配成功 + if (bestScore >= 60) { + return { template: bestMatch, useName: bestUseName } + } + + return { template: null, useName: inputName } + }, + + /** + * 计算两个字符串的匹配得分(0-100) + * 综合考虑:完全匹配、包含关系、相似度 + */ + calculateMatchScore(input, template) { + const inputNorm = input.replace(/\s+/g, '').toLowerCase() + const templateNorm = template.replace(/\s+/g, '').toLowerCase() + + // 1. 完全匹配 - 100分 + if (inputNorm === templateNorm) return 100 + + // 2. 完全包含 - 90分 + if (templateNorm.includes(inputNorm) || inputNorm.includes(templateNorm)) { + return 90 + } + + // 3. 部分包含 - 按包含字符比例计算 + let commonChars = 0 + for (const char of inputNorm) { + if (templateNorm.includes(char)) commonChars++ + } + const containScore = (commonChars / Math.max(inputNorm.length, templateNorm.length)) * 70 + + // 4. 编辑距离相似度 + const distance = this.levenshteinDistance(inputNorm, templateNorm) + const maxLen = Math.max(inputNorm.length, templateNorm.length) + const similarityScore = (1 - distance / maxLen) * 80 + + // 返回最高得分 + return Math.max(containScore, similarityScore) + }, + + /** + * 计算 Levenshtein 编辑距离 + * 用于衡量两个字符串的相似程度 + */ + levenshteinDistance(str1, str2) { + const len1 = str1.length + const len2 = str2.length + const matrix = [] + + // 初始化矩阵 + for (let i = 0; i <= len1; i++) matrix[i] = [i] + for (let j = 0; j <= len2; j++) matrix[0][j] = j + + // 填充矩阵 + for (let i = 1; i <= len1; i++) { + for (let j = 1; j <= len2; j++) { + if (str1[i - 1] === str2[j - 1]) { + matrix[i][j] = matrix[i - 1][j - 1] + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j] + 1, // 删除 + matrix[i][j - 1] + 1, // 插入 + matrix[i - 1][j - 1] + 1 // 替换 + ) + } + } + } + + return matrix[len1][len2] }, /**