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
提取 checkDupName 函数统一所有保存路径的重名检测: - 同名同配方:提示已存在,不保存 - 同名不同配方:展示差异,强制改名,循环检测直到不重名 - 覆盖:单条保存、全部保存、共享到公共库 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -981,6 +981,44 @@ function toggleFormTag(tag) {
|
||||
else formTags.value.push(tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check name against public + personal recipes.
|
||||
* Same name + same content → toast and return false.
|
||||
* Same name + different content → show diff, prompt rename, loop until unique.
|
||||
* Returns final name or false if cancelled.
|
||||
*/
|
||||
async function checkDupName(name, ings) {
|
||||
let currentName = name
|
||||
while (true) {
|
||||
const pubDup = recipeStore.recipes.find(r => r.name === currentName)
|
||||
const diaryDup = diaryStore.userDiary.find(d => d.name === currentName)
|
||||
const dup = pubDup || diaryDup
|
||||
if (!dup) return currentName
|
||||
|
||||
const dupIngs = (dup.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil))
|
||||
const myIngs = ings.filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil))
|
||||
const identical = dupIngs.length === myIngs.length && dupIngs.every((d, i) => d.oil === myIngs[i].oil && d.drops === myIngs[i].drops)
|
||||
const where = pubDup ? '公共配方库' : '我的配方'
|
||||
|
||||
if (identical) {
|
||||
ui.showToast(`${where}中已有一模一样的配方「${currentName}」`)
|
||||
return false
|
||||
}
|
||||
|
||||
const existIngs = dupIngs.map(i => `${i.oil}${i.drops}滴`).join('、')
|
||||
const newIngs = myIngs.map(i => `${i.oil}${i.drops}滴`).join('、')
|
||||
const ok = await showConfirm(
|
||||
`${where}中已有同名配方「${currentName}」,内容不同:\n\n已有:${existIngs}\n新的:${newIngs}\n\n请改名后保存`,
|
||||
{ okText: '改名', cancelText: '取消' }
|
||||
)
|
||||
if (!ok) return false
|
||||
const newName = await showPrompt('请输入新名称:', currentName)
|
||||
if (!newName || !newName.trim()) return false
|
||||
currentName = newName.trim()
|
||||
// Loop back to check the new name
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCurrentRecipe() {
|
||||
if (formVolume.value === 'custom' && !formCustomVolume.value) {
|
||||
ui.showToast('请输入自定义容量')
|
||||
@@ -1010,33 +1048,11 @@ async function saveCurrentRecipe() {
|
||||
|
||||
// Dedup check for new recipes (not editing)
|
||||
if (!editingRecipe.value) {
|
||||
const name = formName.value.trim()
|
||||
// Check public library
|
||||
const pubDup = recipeStore.recipes.find(r => r.name === name)
|
||||
// Check personal diary
|
||||
const diaryDup = diaryStore.userDiary.find(d => d.name === name)
|
||||
const dup = pubDup || diaryDup
|
||||
if (dup) {
|
||||
const dupIngs = (dup.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil))
|
||||
const myIngs = cleanIngs.filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil))
|
||||
const identical = dupIngs.length === myIngs.length && dupIngs.every((ing, i) => ing.oil === myIngs[i].oil && ing.drops === myIngs[i].drops)
|
||||
const where = pubDup ? '公共配方库' : '我的配方'
|
||||
if (identical) {
|
||||
ui.showToast(`${where}中已有一模一样的配方「${name}」`)
|
||||
return
|
||||
}
|
||||
// Show difference
|
||||
const existIngs = dupIngs.map(i => `${i.oil}${i.drops}滴`).join('、')
|
||||
const newIngs = myIngs.map(i => `${i.oil}${i.drops}滴`).join('、')
|
||||
const ok = await showConfirm(
|
||||
`${where}中已有同名配方「${name}」,内容不同:\n\n已有:${existIngs}\n新的:${newIngs}\n\n是否改名后保存?`,
|
||||
{ okText: '改名', cancelText: '取消' }
|
||||
)
|
||||
if (!ok) return
|
||||
const newName = await showPrompt('请输入新名称:', name)
|
||||
if (!newName || !newName.trim()) return
|
||||
formName.value = newName.trim()
|
||||
diaryPayload.name = newName.trim()
|
||||
const result = await checkDupName(diaryPayload.name, cleanIngs)
|
||||
if (result === false) return // cancelled
|
||||
if (result !== diaryPayload.name) {
|
||||
formName.value = result
|
||||
diaryPayload.name = result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1129,23 +1145,31 @@ async function saveParsedRecipe(index) {
|
||||
async function saveAllParsed() {
|
||||
// Sync current form edits back first
|
||||
syncFormToParsed()
|
||||
let saved = 0
|
||||
for (let i = parsedRecipes.value.length - 1; i >= 0; i--) {
|
||||
const toPublic = auth.canManage && await showConfirm('全部保存到哪里?', { okText: '公共配方库', cancelText: '个人配方' })
|
||||
let saved = 0, skipped = 0
|
||||
for (let i = 0; i < parsedRecipes.value.length; i++) {
|
||||
const r = parsedRecipes.value[i]
|
||||
if (!r.name.trim() || r.ingredients.length === 0) continue
|
||||
const ings = r.ingredients.map(ing => ({ oil: ing.oil, drops: ing.drops }))
|
||||
const finalName = await checkDupName(r.name.trim(), ings)
|
||||
if (finalName === false) { skipped++; continue }
|
||||
try {
|
||||
await diaryStore.createDiary({
|
||||
name: r.name.trim(),
|
||||
ingredients: r.ingredients.map(i => ({ oil: i.oil, drops: i.drops })),
|
||||
if (toPublic) {
|
||||
await recipeStore.saveRecipe({
|
||||
name: finalName,
|
||||
ingredients: ings.map(ing => ({ oil_name: ing.oil, drops: ing.drops })),
|
||||
note: '',
|
||||
tags: [],
|
||||
})
|
||||
} else {
|
||||
await diaryStore.createDiary({ name: finalName, ingredients: ings, note: '', tags: [] })
|
||||
}
|
||||
saved++
|
||||
} catch {}
|
||||
}
|
||||
parsedRecipes.value = []
|
||||
parsedCurrentIndex.value = -1
|
||||
ui.showToast(`已保存 ${saved} 条配方到我的配方`)
|
||||
ui.showToast(`已保存 ${saved} 条配方到${toPublic ? '公共配方库' : '我的配方'}`)
|
||||
closeOverlay()
|
||||
}
|
||||
|
||||
@@ -1424,28 +1448,10 @@ async function recommendApprove(recipe) {
|
||||
}
|
||||
|
||||
async function shareDiaryToPublic(diary) {
|
||||
// Check for duplicates in public library
|
||||
const dup = recipeStore.recipes.find(r => r.name === diary.name)
|
||||
if (dup) {
|
||||
const dIngs = (diary.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil))
|
||||
const pIngs = (dup.ingredients || []).filter(i => i.oil).sort((a, b) => a.oil.localeCompare(b.oil))
|
||||
const identical = dIngs.length === pIngs.length && dIngs.every((ing, i) => ing.oil === pIngs[i].oil && ing.drops === pIngs[i].drops)
|
||||
if (identical) {
|
||||
ui.showToast('公共配方库中已有一模一样的配方「' + diary.name + '」')
|
||||
return
|
||||
}
|
||||
// Same name, different content — show details
|
||||
const existIngs = pIngs.map(i => `${i.oil}${i.drops}滴`).join('、')
|
||||
const newIngs = dIngs.map(i => `${i.oil}${i.drops}滴`).join('、')
|
||||
const action = await showConfirm(
|
||||
`公共配方库中已有同名配方「${diary.name}」,内容不同:\n\n已有:${existIngs}\n新的:${newIngs}\n\n是否改名后共享?`,
|
||||
{ okText: '改名', cancelText: '取消' }
|
||||
)
|
||||
if (!action) return
|
||||
const newName = await showPrompt('请输入新名称:', diary.name)
|
||||
if (!newName || !newName.trim()) return
|
||||
diary = { ...diary, name: newName.trim() }
|
||||
}
|
||||
const ings = (diary.ingredients || []).map(i => ({ oil: i.oil, drops: i.drops }))
|
||||
const result = await checkDupName(diary.name, ings)
|
||||
if (result === false) return
|
||||
if (result !== diary.name) diary = { ...diary, name: result }
|
||||
|
||||
const ok = await showConfirm(`将「${diary.name}」共享到公共配方库?`)
|
||||
if (!ok) return
|
||||
|
||||
Reference in New Issue
Block a user