From 0924ea99400350c88e982239c2aaa956e6087797 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Mon, 13 Apr 2026 18:30:26 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8B=BC=E9=9F=B3=E5=AD=90=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=B9=E9=85=8D+=E4=BA=A7=E5=93=81=E5=90=8D?= =?UTF-8?q?=E6=8B=BC=E9=9F=B3=E8=A1=A5=E5=85=A8+=E6=BB=B4=E6=95=B0?= =?UTF-8?q?=E6=A1=86=E7=BC=A9=E7=AA=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 拼音匹配支持子序列(js匹配紧致霜),排序: 前缀>子串>子序列 - 补全产品名拼音(身紧致霜膏膜等) - 滴数输入框从65/70px缩至50px Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/RecipeDetailOverlay.vue | 4 +- frontend/src/composables/usePinyinMatch.js | 40 ++++++++++++++++++- frontend/src/views/RecipeManager.vue | 12 +++--- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/RecipeDetailOverlay.vue b/frontend/src/components/RecipeDetailOverlay.vue index fdb3803..b2a13a2 100644 --- a/frontend/src/components/RecipeDetailOverlay.vue +++ b/frontend/src/components/RecipeDetailOverlay.vue @@ -1694,8 +1694,8 @@ async function saveRecipe() { } .editor-drops { - width: 70px; - padding: 7px 10px; + width: 50px; + padding: 7px 4px; border: 1.5px solid var(--border, #e0d4c0); border-radius: 8px; font-size: 13px; diff --git a/frontend/src/composables/usePinyinMatch.js b/frontend/src/composables/usePinyinMatch.js index ba447e1..aaefbf9 100644 --- a/frontend/src/composables/usePinyinMatch.js +++ b/frontend/src/composables/usePinyinMatch.js @@ -76,6 +76,12 @@ const PINYIN_MAP = { '空': 'k', '风': 'f', '文': 'w', '月': 'y', '云': 'y', '五': 'w', '味': 'w', '愈': 'y', '创': 'c', '慰': 'w', '扁': 'b', '广': 'g', '州': 'z', '热': 'r', + // Product name chars + '身': 's', '紧': 'j', '致': 'z', '霜': 's', '膏': 'g', + '膜': 'm', '乳': 'r', '液': 'y', '瓶': 'p', '盒': 'h', + '深': 's', '层': 'c', '肤': 'f', '磨': 'm', '砂': 's', + '龄': 'l', '无': 'w', '年': 'n', '华': 'h', '娇': 'j', + '颜': 'y', '喷': 'p', '雾': 'w', } /** @@ -95,12 +101,42 @@ export function getPinyinInitials(name) { /** * Check if a query matches a name by pinyin initials. - * The query is matched as a prefix or substring of the pinyin initials. + * Supports: prefix match, substring match, and subsequence match. */ export function matchesPinyinInitials(name, query) { if (!query || !name) return false const initials = getPinyinInitials(name) if (!initials) return false const q = query.toLowerCase() - return initials.startsWith(q) + // Prefix or substring (consecutive) + if (initials.includes(q)) return true + // Subsequence: each char of q appears in order in initials + let pos = 0 + for (const ch of q) { + pos = initials.indexOf(ch, pos) + if (pos === -1) return false + pos++ + } + return true +} + +/** + * Score how well a query matches pinyin initials. + * 0 = prefix, 1 = substring, 2 = subsequence, -1 = no match + */ +export function pinyinMatchScore(name, query) { + if (!query || !name) return -1 + const initials = getPinyinInitials(name) + if (!initials) return -1 + const q = query.toLowerCase() + if (initials.startsWith(q)) return 0 + if (initials.includes(q)) return 1 + // Subsequence check + let pos = 0 + for (const ch of q) { + pos = initials.indexOf(ch, pos) + if (pos === -1) return -1 + pos++ + } + return 2 } diff --git a/frontend/src/views/RecipeManager.vue b/frontend/src/views/RecipeManager.vue index 1bf4969..a3bd2ee 100644 --- a/frontend/src/views/RecipeManager.vue +++ b/frontend/src/views/RecipeManager.vue @@ -413,7 +413,7 @@ import { useUiStore } from '../stores/ui' import { api } from '../composables/useApi' import { showConfirm, showPrompt } from '../composables/useDialog' import { parseSingleBlock, parseMultiRecipes } from '../composables/useSmartPaste' -import { matchesPinyinInitials } from '../composables/usePinyinMatch' +import { matchesPinyinInitials, pinyinMatchScore } from '../composables/usePinyinMatch' import RecipeCard from '../components/RecipeCard.vue' import TagPicker from '../components/TagPicker.vue' import RecipeDetailOverlay from '../components/RecipeDetailOverlay.vue' @@ -866,11 +866,11 @@ function filteredOilNames(search) { const results = oils.oilNames.filter(name => name.toLowerCase().includes(q) || matchesPinyinInitials(name, q) ) - // Sort: pinyin prefix match first, then name contains, then rest + // Sort: prefix > substring > subsequence > name match only results.sort((a, b) => { - const aPin = matchesPinyinInitials(a, q) ? 0 : 1 - const bPin = matchesPinyinInitials(b, q) ? 0 : 1 - if (aPin !== bPin) return aPin - bPin + const sa = pinyinMatchScore(a, q), sb = pinyinMatchScore(b, q) + const scoreA = sa >= 0 ? sa : 3, scoreB = sb >= 0 ? sb : 3 + if (scoreA !== scoreB) return scoreA - scoreB return a.localeCompare(b, 'zh') }) return results @@ -2112,7 +2112,7 @@ watch(() => recipeStore.recipes, () => { .editor-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 8px; } .editor-table th { text-align: left; padding: 6px 4px; color: #999; font-weight: 500; font-size: 12px; border-bottom: 1px solid #eee; } .editor-table td { padding: 6px 4px; border-bottom: 1px solid #f5f5f5; } -.editor-drops { width: 65px; padding: 6px 8px; border: 1.5px solid #d4cfc7; border-radius: 8px; font-size: 13px; text-align: center; outline: none; font-family: inherit; } +.editor-drops { width: 50px; padding: 6px 4px; border: 1.5px solid #d4cfc7; border-radius: 8px; font-size: 13px; text-align: center; outline: none; font-family: inherit; } .editor-drops:focus { border-color: #7ec6a4; } .drops-with-unit { display: flex; align-items: center; gap: 2px; } .unit-hint { font-size: 11px; color: #b0aab5; white-space: nowrap; }