diff --git a/frontend/src/composables/useSmartPaste.js b/frontend/src/composables/useSmartPaste.js index 2b15267..386aed2 100644 --- a/frontend/src/composables/useSmartPaste.js +++ b/frontend/src/composables/useSmartPaste.js @@ -86,7 +86,8 @@ export function findOil(input, oilNames) { } } - // 5. Edit distance fuzzy match + // 5. Edit distance fuzzy match (only for 3+ char inputs to avoid false positives) + if (trimmed.length < 3) return null let bestMatch = null let bestDist = Infinity for (const name of oilNames) { @@ -158,9 +159,11 @@ export function parseOilChunk(text, oilNames) { let amount = parseFloat(match[2]) const unit = match[3] || '' + const isMl = unit && (unit.toLowerCase() === 'ml' || unit === '毫升') + let drops = amount // Convert ml to drops - if (unit && (unit.toLowerCase() === 'ml' || unit === '毫升')) { - amount = Math.round(amount * 20) + if (isMl) { + drops = Math.round(amount * 20) } // Try greedy match on the name part @@ -170,14 +173,18 @@ export function parseOilChunk(text, oilNames) { for (let i = 0; i < matched.length - 1; i++) { results.push({ oil: matched[i], drops: 1 }) } - results.push({ oil: matched[matched.length - 1], drops: amount }) + const item = { oil: matched[matched.length - 1], drops } + if (isMl) { item._ml = amount } + results.push(item) } else { // Try findOil as fallback const found = findOil(namePart, oilNames) if (found) { - results.push({ oil: found, drops: amount }) + const item = { oil: found, drops } + if (isMl) { item._ml = amount } + results.push(item) } else if (namePart) { - results.push({ oil: namePart, drops: amount, notFound: true }) + results.push({ oil: namePart, drops, notFound: true }) } } } @@ -291,7 +298,7 @@ export function parseSingleBlock(raw, oilNames) { } return { - name: name || '未命名配方', + name: name || '', ingredients: deduped, notFound } @@ -358,19 +365,23 @@ export function parseMultiRecipes(raw, oilNames) { for (const part of parts) { const hasNumber = /\d/.test(part) + const textPart = part.replace(/\d+\.?\d*/g, '').trim() const hasOil = oilNames.some(oil => part.includes(oil)) || Object.keys(OIL_HOMOPHONES).some(alias => part.includes(alias)) // Also check fuzzy: 3+ char parts - const fuzzyOil = !hasOil && part.replace(/\d+\.?\d*/g, '').length >= 2 && - findOil(part.replace(/\d+\.?\d*/g, '').trim(), oilNames) + const fuzzyOil = !hasOil && textPart.length >= 2 && + findOil(textPart, oilNames) + // First part only: has number but text is not any oil → likely a name like "美容1" + const isFirstNameWithNumber = !current.foundOil && current.nameParts.length === 0 && + current.ingredientParts.length === 0 && hasNumber && !hasOil && !fuzzyOil && textPart.length >= 2 if (current.foundOil && !hasOil && !fuzzyOil && !hasNumber && part.length >= 2) { // New recipe starts recipes.push(current) current = { nameParts: [], ingredientParts: [], foundOil: false } current.nameParts.push(part) - } else if (!current.foundOil && !hasOil && !fuzzyOil && !hasNumber) { - current.nameParts.push(part) + } else if ((!current.foundOil && !hasOil && !fuzzyOil && !hasNumber) || isFirstNameWithNumber) { + current.nameParts.push(isFirstNameWithNumber ? textPart : part) } else { current.foundOil = true current.ingredientParts.push(part) @@ -401,7 +412,7 @@ export function parseMultiRecipes(raw, oilNames) { } } return { - name: r.nameParts.join(' ') || '未命名配方', + name: r.nameParts.join(' ') || '', ingredients: deduped, notFound, } diff --git a/frontend/src/views/RecipeManager.vue b/frontend/src/views/RecipeManager.vue index d55c7e6..34dcaaa 100644 --- a/frontend/src/views/RecipeManager.vue +++ b/frontend/src/views/RecipeManager.vue @@ -201,28 +201,24 @@ - -