feat: 权限细化、商业认证跳转、UI改进 #19

Merged
hera merged 10 commits from feat/permissions-ui-improvements into main 2026-04-10 09:35:11 +00:00
2 changed files with 66 additions and 1 deletions
Showing only changes of commit 6f9c5732eb - Show all commits

View File

@@ -62,6 +62,7 @@
<div class="recipe-section">
<h3 class="section-title">
<span>📖 我的配方 ({{ myRecipes.length }})</span>
<span v-if="!auth.isAdmin && sharedCount > 0" class="contrib-tag">已贡献 {{ sharedCount }} </span>
<button class="btn-sm btn-outline" @click="toggleSelectAllDiary">全选/取消</button>
</h3>
<div class="recipe-list">
@@ -84,8 +85,11 @@
<span v-for="t in (d.tags || [])" :key="t" class="mini-tag">{{ t }}</span>
</span>
<span class="row-cost">{{ oils.fmtPrice(oils.calcCost(d.ingredients || [])) }}</span>
<span v-if="getDiaryShareStatus(d) === 'shared'" class="share-tag shared">已共享</span>
<span v-else-if="getDiaryShareStatus(d) === 'pending'" class="share-tag pending">等待审核</span>
</div>
<div class="row-actions">
<button v-if="getDiaryShareStatus(d) !== 'shared'" class="btn-icon" @click="shareDiaryToPublic(d)" title="共享到公共配方库">📤</button>
<button class="btn-icon" @click="editDiaryRecipe(d)" title="编辑"></button>
<button class="btn-icon" @click="removeDiaryRecipe(d)" title="删除">🗑</button>
</div>
@@ -609,10 +613,19 @@ async function saveAllParsed() {
closeOverlay()
}
const sharedCount = ref(0)
// Load diary on mount
onMounted(async () => {
if (auth.isLoggedIn) {
await diaryStore.loadDiary()
try {
const res = await api('/api/me/contribution')
if (res.ok) {
const data = await res.json()
sharedCount.value = data.shared_count || 0
}
} catch {}
}
})
@@ -625,6 +638,40 @@ function editDiaryRecipe(diary) {
showAddOverlay.value = true
}
function getDiaryShareStatus(d) {
// Check if a public recipe with same name exists, owned by current user or adopted by admin
const pub = recipeStore.recipes.find(r => r.name === d.name)
if (!pub) return null
if (pub._owner_id === auth.user?.id) return 'pending'
return 'shared'
}
async function shareDiaryToPublic(diary) {
const ok = await showConfirm(`将「${diary.name}」共享到公共配方库?`)
if (!ok) return
try {
const res = await api('/api/recipes', {
method: 'POST',
body: JSON.stringify({
name: diary.name,
note: diary.note || '',
ingredients: (diary.ingredients || []).map(i => ({ oil_name: i.oil, drops: i.drops })),
tags: diary.tags || [],
}),
})
if (res.ok) {
if (auth.isAdmin) {
ui.showToast('已共享到公共配方库')
} else {
ui.showToast('已提交,等待管理员审核')
}
await recipeStore.loadRecipes()
}
} catch {
ui.showToast('共享失败')
}
}
async function removeDiaryRecipe(diary) {
const ok = await showConfirm(`确定删除个人配方 "${diary.name}"`)
if (!ok) return
@@ -928,6 +975,25 @@ watch(() => recipeStore.recipes, () => {
color: #6b6375;
}
.share-tag {
font-size: 11px;
padding: 1px 8px;
border-radius: 8px;
font-weight: 500;
white-space: nowrap;
}
.share-tag.shared { background: #e8f5e9; color: #2e7d32; }
.share-tag.pending { background: #fff3e0; color: #e65100; }
.contrib-tag {
font-size: 11px;
color: #4a9d7e;
background: #e8f5e9;
padding: 2px 8px;
border-radius: 8px;
font-weight: 500;
}
.row-cost {
font-size: 13px;
color: #4a9d7e;

View File

@@ -64,7 +64,6 @@
/>
<span v-if="getDiaryShareStatus(d) === 'shared'" class="share-status shared">已共享</span>
<span v-else-if="getDiaryShareStatus(d) === 'pending'" class="share-status pending">审核中</span>
<button v-else class="share-btn" @click.stop="shareDiaryToPublic(d)" title="共享到公共配方库">📤</button>
</div>
<div v-if="myDiaryRecipes.length === 0" class="empty-hint">暂无个人配方</div>
</div>