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 {
|
.editor-drops {
|
||||||
width: 70px;
|
width: 50px;
|
||||||
padding: 7px 10px;
|
padding: 7px 4px;
|
||||||
border: 1.5px solid var(--border, #e0d4c0);
|
border: 1.5px solid var(--border, #e0d4c0);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ const PINYIN_MAP = {
|
|||||||
'空': 'k', '风': 'f', '文': 'w', '月': 'y', '云': 'y',
|
'空': 'k', '风': 'f', '文': 'w', '月': 'y', '云': 'y',
|
||||||
'五': 'w', '味': 'w', '愈': 'y', '创': 'c', '慰': 'w',
|
'五': 'w', '味': 'w', '愈': 'y', '创': 'c', '慰': 'w',
|
||||||
'扁': 'b', '广': 'g', '州': 'z', '热': 'r',
|
'扁': '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.
|
* 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) {
|
export function matchesPinyinInitials(name, query) {
|
||||||
if (!query || !name) return false
|
if (!query || !name) return false
|
||||||
const initials = getPinyinInitials(name)
|
const initials = getPinyinInitials(name)
|
||||||
if (!initials) return false
|
if (!initials) return false
|
||||||
const q = query.toLowerCase()
|
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 { api } from '../composables/useApi'
|
||||||
import { showConfirm, showPrompt } from '../composables/useDialog'
|
import { showConfirm, showPrompt } from '../composables/useDialog'
|
||||||
import { parseSingleBlock, parseMultiRecipes } from '../composables/useSmartPaste'
|
import { parseSingleBlock, parseMultiRecipes } from '../composables/useSmartPaste'
|
||||||
import { matchesPinyinInitials } from '../composables/usePinyinMatch'
|
import { matchesPinyinInitials, pinyinMatchScore } from '../composables/usePinyinMatch'
|
||||||
import RecipeCard from '../components/RecipeCard.vue'
|
import RecipeCard from '../components/RecipeCard.vue'
|
||||||
import TagPicker from '../components/TagPicker.vue'
|
import TagPicker from '../components/TagPicker.vue'
|
||||||
import RecipeDetailOverlay from '../components/RecipeDetailOverlay.vue'
|
import RecipeDetailOverlay from '../components/RecipeDetailOverlay.vue'
|
||||||
@@ -866,11 +866,11 @@ function filteredOilNames(search) {
|
|||||||
const results = oils.oilNames.filter(name =>
|
const results = oils.oilNames.filter(name =>
|
||||||
name.toLowerCase().includes(q) || matchesPinyinInitials(name, q)
|
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) => {
|
results.sort((a, b) => {
|
||||||
const aPin = matchesPinyinInitials(a, q) ? 0 : 1
|
const sa = pinyinMatchScore(a, q), sb = pinyinMatchScore(b, q)
|
||||||
const bPin = matchesPinyinInitials(b, q) ? 0 : 1
|
const scoreA = sa >= 0 ? sa : 3, scoreB = sb >= 0 ? sb : 3
|
||||||
if (aPin !== bPin) return aPin - bPin
|
if (scoreA !== scoreB) return scoreA - scoreB
|
||||||
return a.localeCompare(b, 'zh')
|
return a.localeCompare(b, 'zh')
|
||||||
})
|
})
|
||||||
return results
|
return results
|
||||||
@@ -2112,7 +2112,7 @@ watch(() => recipeStore.recipes, () => {
|
|||||||
.editor-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 8px; }
|
.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 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-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; }
|
.editor-drops:focus { border-color: #7ec6a4; }
|
||||||
.drops-with-unit { display: flex; align-items: center; gap: 2px; }
|
.drops-with-unit { display: flex; align-items: center; gap: 2px; }
|
||||||
.unit-hint { font-size: 11px; color: #b0aab5; white-space: nowrap; }
|
.unit-hint { font-size: 11px; color: #b0aab5; white-space: nowrap; }
|
||||||
|
|||||||
Reference in New Issue
Block a user