feat: 审核流程完善 + 共享状态提示 + 贡献统计含拒绝
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 4s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 4s
PR Preview / deploy-preview (pull_request) Successful in 16s
Test / e2e-test (push) Failing after 55s
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 4s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 4s
PR Preview / deploy-preview (pull_request) Successful in 16s
Test / e2e-test (push) Failing after 55s
审核流程:
- 高级编辑者可看到待审核配方,点击推荐通过→通知管理员
- 高级编辑者可直接拒绝(和管理员相同逻辑)
- 管理员收到推荐通知后最终决定
- 去审核通知点击自动展开待审核列表
- 新增 /api/recipes/{id}/recommend 端点
共享:
- 已共享配方再点共享→提示"已共享,感谢贡献"
- 审核中配方再点共享→提示"正在审核中,请耐心等待"
贡献统计:
- 被拒绝的配方也计入总申请数(0/1不会变回0/0)
- reject_recipe日志记录from_user
其他:
- 配方卡片去掉编辑按钮
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,11 +14,6 @@
|
||||
</button>
|
||||
</div>
|
||||
<div style="flex:1"></div>
|
||||
<button
|
||||
v-if="canEditThisRecipe"
|
||||
class="action-btn action-btn-sm"
|
||||
@click="goEditInManager"
|
||||
>编辑</button>
|
||||
<button class="detail-close-btn" @click="handleClose">✕</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -130,8 +130,14 @@ function isSearchMissing(n) {
|
||||
}
|
||||
|
||||
function isReviewable(n) {
|
||||
if (!auth.isAdmin || !n.title) return false
|
||||
return n.title.includes('待审核') || n.title.includes('商业认证') || n.title.includes('申请')
|
||||
if (!n.title) return false
|
||||
// Admin: review recipe/business/applications
|
||||
if (auth.isAdmin) {
|
||||
return n.title.includes('待审核') || n.title.includes('商业认证') || n.title.includes('申请') || n.title.includes('推荐通过')
|
||||
}
|
||||
// Senior editor: assigned reviews
|
||||
if (auth.canManage && n.title.includes('请审核')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
async function markAdded(n) {
|
||||
@@ -141,7 +147,8 @@ async function markAdded(n) {
|
||||
function goReview(n) {
|
||||
markOneRead(n)
|
||||
emit('close')
|
||||
if (n.title.includes('配方')) {
|
||||
if (n.title.includes('配方') || n.title.includes('审核') || n.title.includes('推荐')) {
|
||||
localStorage.setItem('oil_open_pending', '1')
|
||||
router.push('/manage')
|
||||
} else if (n.title.includes('商业认证') || n.title.includes('申请')) {
|
||||
router.push('/users')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="recipe-manager">
|
||||
<!-- Review Bar (admin only) -->
|
||||
<div v-if="auth.isAdmin && pendingCount > 0" class="review-bar" @click="showPending = !showPending" >
|
||||
<!-- Review Bar (admin + senior_editor with assigned reviews) -->
|
||||
<div v-if="(auth.isAdmin || auth.canManage) && pendingCount > 0" class="review-bar" @click="showPending = !showPending" >
|
||||
📝 待审核配方: {{ pendingCount }} 条
|
||||
<span class="toggle-icon">{{ showPending ? '▾' : '▸' }}</span>
|
||||
</div>
|
||||
@@ -9,16 +9,22 @@
|
||||
<div v-for="r in pendingRecipes" :key="r._id" class="pending-item">
|
||||
<span class="pending-name clickable" @click="openRecipeDetail(r)">{{ r.name }}</span>
|
||||
<span class="pending-owner">{{ r._owner_name }}</span>
|
||||
<button class="btn-sm btn-approve" @click="approveRecipe(r)">通过</button>
|
||||
<button class="btn-sm btn-reject" @click="rejectRecipe(r)">拒绝</button>
|
||||
<button class="btn-sm btn-outline" @click="r._showAssign = !r._showAssign">指派</button>
|
||||
<div v-if="r._showAssign" class="assign-row">
|
||||
<select v-model="r._assignTo" class="assign-select">
|
||||
<option value="">选择审核人...</option>
|
||||
<option v-for="u in seniorEditors" :key="u.id" :value="u.id">{{ u.display_name || u.username }}</option>
|
||||
</select>
|
||||
<button class="btn-sm btn-primary" @click="assignReview(r)" :disabled="!r._assignTo">发送</button>
|
||||
</div>
|
||||
<template v-if="auth.isAdmin">
|
||||
<button class="btn-sm btn-approve" @click="approveRecipe(r)">通过</button>
|
||||
<button class="btn-sm btn-reject" @click="rejectRecipe(r)">拒绝</button>
|
||||
<button class="btn-sm btn-outline" @click="r._showAssign = !r._showAssign">指派</button>
|
||||
<div v-if="r._showAssign" class="assign-row">
|
||||
<select v-model="r._assignTo" class="assign-select">
|
||||
<option value="">选择审核人...</option>
|
||||
<option v-for="u in seniorEditors" :key="u.id" :value="u.id">{{ u.display_name || u.username }}</option>
|
||||
</select>
|
||||
<button class="btn-sm btn-primary" @click="assignReview(r)" :disabled="!r._assignTo">发送</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="btn-sm btn-approve" @click="recommendApprove(r)">推荐通过</button>
|
||||
<button class="btn-sm btn-reject" @click="rejectRecipe(r)">拒绝</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,7 +133,7 @@
|
||||
<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="handleShare(d)" title="共享到公共配方库">📤</button>
|
||||
<button class="btn-icon" @click="removeDiaryRecipe(d)" title="删除">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1110,6 +1116,11 @@ onMounted(async () => {
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
// Auto-expand pending if navigated from notification
|
||||
if (localStorage.getItem('oil_open_pending')) {
|
||||
localStorage.removeItem('oil_open_pending')
|
||||
showPending.value = true
|
||||
}
|
||||
// Open recipe editor if redirected from card view
|
||||
const editId = localStorage.getItem('oil_edit_recipe_id')
|
||||
if (editId) {
|
||||
@@ -1161,6 +1172,31 @@ function getDiaryShareStatus(d) {
|
||||
return null
|
||||
}
|
||||
|
||||
function handleShare(d) {
|
||||
const status = getDiaryShareStatus(d)
|
||||
if (status === 'shared') {
|
||||
ui.showToast('该配方已共享到公共配方库,感谢你的贡献!')
|
||||
return
|
||||
}
|
||||
if (status === 'pending') {
|
||||
ui.showToast('该配方正在审核中,请耐心等待')
|
||||
return
|
||||
}
|
||||
shareDiaryToPublic(d)
|
||||
}
|
||||
|
||||
async function recommendApprove(recipe) {
|
||||
try {
|
||||
await api('/api/recipes/' + recipe._id + '/recommend', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ recommendation: 'approve' }),
|
||||
})
|
||||
ui.showToast('已推荐通过,等待管理员最终审核')
|
||||
} catch {
|
||||
ui.showToast('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function shareDiaryToPublic(diary) {
|
||||
const ok = await showConfirm(`将「${diary.name}」共享到公共配方库?`)
|
||||
if (!ok) return
|
||||
|
||||
Reference in New Issue
Block a user