|
|
|
@ -359,12 +359,13 @@ |
|
|
|
<el-form-item> |
|
|
|
<template slot="label"> |
|
|
|
<span style="color: #909399; font-size: 12px; "> |
|
|
|
支持多种输入:RFID前道+绑定+Converting 或 RFID前道、绑定、Converting |
|
|
|
支持多种输入:RFID前道+绑定+Converting 或 1.RFID前道 2.绑定 或 RFID前道、绑定 |
|
|
|
</span> |
|
|
|
</template> |
|
|
|
<el-input |
|
|
|
v-model="batchProcessInput" |
|
|
|
@blur="handleBatchInput" |
|
|
|
ref="batchInput" |
|
|
|
@keyup.enter.native="handleBatchInput" |
|
|
|
placeholder="输入工序名称后按回车或失去焦点自动识别"> |
|
|
|
</el-input> |
|
|
|
@ -423,10 +424,10 @@ |
|
|
|
</el-table> |
|
|
|
|
|
|
|
<div slot="footer"> |
|
|
|
<el-button @click="batchAddDialogVisible = false">取消</el-button> |
|
|
|
<el-button type="primary" @click="confirmBatchCreate" :disabled="validBatchCount === 0" :loading="batchCreating"> |
|
|
|
批量创建({{ validBatchCount }}个) |
|
|
|
</el-button> |
|
|
|
<el-button @click="batchAddDialogVisible = false">取消</el-button> |
|
|
|
</div> |
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
@ -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] |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
|