feat: 拼音子序列匹配+产品名拼音补全+滴数框缩窄
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Failing after 5s
Test / e2e-test (push) Has been skipped
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Failing after 5s
PR Preview / deploy-preview (pull_request) Has been skipped
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Failing after 5s
Test / e2e-test (push) Has been skipped
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Failing after 5s
PR Preview / deploy-preview (pull_request) Has been skipped
- 拼音匹配支持子序列(js匹配紧致霜),排序: 前缀>子串>子序列 - 补全产品名拼音(身紧致霜膏膜等) - 滴数输入框从65/70px缩至50px Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user