diff --git a/backend/main.py b/backend/main.py index 8308576..6bdf449 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1659,12 +1659,21 @@ def update_oil_plan(plan_id: int, body: dict, user=Depends(get_current_user)): if not plan: conn.close() raise HTTPException(404, "方案不存在") - if plan["teacher_id"] != user["id"] and user["role"] != "admin": + is_teacher = plan["teacher_id"] == user["id"] or user["role"] == "admin" + is_owner = plan["user_id"] == user["id"] + if not is_teacher and not is_owner: conn.close() raise HTTPException(403, "无权操作") - if "title" in body: + if "health_desc" in body and is_owner: + conn.execute("UPDATE oil_plans SET health_desc = ? WHERE id = ?", (body["health_desc"], plan_id)) + who = user.get("display_name") or user["username"] + conn.execute( + "INSERT INTO notifications (target_role, title, body, target_user_id) VALUES (?, ?, ?, ?)", + ("admin", "📋 方案需求已更新", f"{who} 更新了健康需求:{body['health_desc'][:50]}", plan["teacher_id"]) + ) + if "title" in body and is_teacher: conn.execute("UPDATE oil_plans SET title = ? WHERE id = ?", (body["title"], plan_id)) - if "status" in body: + if "status" in body and is_teacher: old_status = plan["status"] conn.execute("UPDATE oil_plans SET status = ? WHERE id = ?", (body["status"], plan_id)) if body["status"] == "active" and old_status != "active": diff --git a/frontend/src/components/UserMenu.vue b/frontend/src/components/UserMenu.vue index fa74e53..48088e1 100644 --- a/frontend/src/components/UserMenu.vue +++ b/frontend/src/components/UserMenu.vue @@ -38,6 +38,8 @@
+ + @@ -129,6 +131,16 @@ function isSearchMissing(n) { return n.title && n.title.includes('用户需求') } +function isPlanRequest(n) { + return n.title && (n.title.includes('方案请求') || n.title.includes('方案需求')) +} + +function goPlanDesign(n) { + markOneRead(n) + emit('close') + window.location.hash = '#/users' +} + function isReviewable(n) { if (!n.title) return false // Admin: review recipe/business/applications @@ -269,6 +281,8 @@ onMounted(loadNotifications) } .notif-btn-added { color: #4a9d7e; border-color: #7ec6a4; } .notif-btn-added:hover { background: #e8f5e9; } +.notif-btn-plan { color: #1565c0; border-color: #90caf9; } +.notif-btn-plan:hover { background: #e3f2fd; } .notif-btn-review { color: #e65100; border-color: #ffb74d; } .notif-btn-review:hover { background: #fff3e0; } .notif-body { color: #888; font-size: 12px; margin-top: 2px; white-space: pre-line; } diff --git a/frontend/src/views/Inventory.vue b/frontend/src/views/Inventory.vue index 3a3e363..9aa8ad7 100644 --- a/frontend/src/views/Inventory.vue +++ b/frontend/src/views/Inventory.vue @@ -32,12 +32,13 @@
- +
- ⏳ 方案审核中 + ⏳ 老师正在为你定制方案中
-

{{ plansStore.pendingPlans[0].health_desc }}

+ +
修改后老师会收到通知
@@ -324,6 +325,28 @@ function matchTeacher() { ) || null } +const pendingDesc = ref('') + +// Sync pendingDesc when plans load +watch(() => plansStore.pendingPlans, (pp) => { + if (pp.length) pendingDesc.value = pp[0].health_desc || '' +}, { immediate: true }) + +async function updatePendingDesc() { + const plan = plansStore.pendingPlans[0] + if (!plan || pendingDesc.value === plan.health_desc) return + try { + await api(`/api/oil-plans/${plan.id}`, { + method: 'PUT', + body: JSON.stringify({ health_desc: pendingDesc.value }), + }) + plan.health_desc = pendingDesc.value + ui.showToast('已更新,老师会收到通知') + } catch { + ui.showToast('更新失败') + } +} + const shoppingTotal = computed(() => plansStore.shoppingList.filter(i => !i.in_inventory).reduce((s, i) => s + i.total_cost, 0).toFixed(2) ) @@ -641,6 +664,7 @@ onMounted(async () => { .overlay-header h3 { margin: 0; font-size: 16px; } .form-label { display: block; font-size: 13px; color: #6b6375; margin-bottom: 4px; font-weight: 500; } .form-textarea { width: 100%; border: 1.5px solid #e5e4e7; border-radius: 8px; padding: 8px; font-size: 13px; font-family: inherit; resize: vertical; box-sizing: border-box; } +.plan-pending-hint { font-size: 11px; color: #b0aab5; margin-top: 4px; } .teacher-matched { color: #2e7d5a; font-size: 13px; margin-top: 4px; font-weight: 500; } .teacher-no-match { color: #c62828; font-size: 12px; margin-top: 4px; }