diff --git a/backend/app/api/v1/endpoints/notifications.py b/backend/app/api/v1/endpoints/notifications.py index 97f82f3..f405e02 100644 --- a/backend/app/api/v1/endpoints/notifications.py +++ b/backend/app/api/v1/endpoints/notifications.py @@ -4,9 +4,12 @@ import uuid from fastapi import APIRouter, Depends, Query from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload from app.core.database import get_db from app.models.notification import Notification +from app.models.task import Task +from app.models.product import Product from app.schemas.notification import NotificationResponse, NotificationListResponse router = APIRouter(prefix="/notifications", tags=["消息通知"]) @@ -46,10 +49,28 @@ async def list_notifications( result = await db.execute(stmt) notifications = result.scalars().all() + # 🚀 批量查询关联的 product_serial_number + task_ids = [n.task_id for n in notifications if n.task_id] + serial_map: dict[uuid.UUID, str] = {} + if task_ids: + task_result = await db.execute( + select(Task.id, Product.serial_number) + .join(Product, Task.product_id == Product.id) + .where(Task.id.in_(task_ids)) + ) + for row in task_result: + serial_map[row[0]] = row[1] + + # 组装响应 + response_list: list[NotificationResponse] = [] + for n in notifications: + resp = NotificationResponse.model_validate(n) + if n.task_id and n.task_id in serial_map: + resp.product_serial_number = serial_map[n.task_id] + response_list.append(resp) + return NotificationListResponse( - notifications=[ - NotificationResponse.model_validate(n) for n in notifications - ], + notifications=response_list, total=total, unread_count=unread_count, ) diff --git a/backend/app/schemas/notification.py b/backend/app/schemas/notification.py index 7fb565c..f719ed1 100644 --- a/backend/app/schemas/notification.py +++ b/backend/app/schemas/notification.py @@ -13,6 +13,7 @@ class NotificationResponse(BaseModel): content: str type: str task_id: uuid.UUID | None = None + product_serial_number: str | None = None is_read: bool created_at: datetime diff --git a/track-uniapp/src/pages/notify/index.vue b/track-uniapp/src/pages/notify/index.vue index 55f691b..a117ab9 100644 --- a/track-uniapp/src/pages/notify/index.vue +++ b/track-uniapp/src/pages/notify/index.vue @@ -103,7 +103,15 @@ async function handleCardTap(item) { if (!item.is_read) { try { await markNotificationRead(item.id); item.is_read = true; } catch {} } - if (item.task_id) { + // 🚀 优先使用 product_serial_number,兜底从 content 中解析 + let sn = item.product_serial_number || ""; + if (!sn && item.content) { + const match = item.content.match(/\[([A-Za-z0-9]{8,16})\]/); + if (match) sn = match[1]; + } + if (sn) { + uni.navigateTo({ url: `/pages/scan/detail?serial=${sn}` }); + } else if (item.task_id) { uni.navigateTo({ url: `/pages/scan/detail?taskId=${item.task_id}` }); } } diff --git a/track-uniapp/src/pages/scan/detail.vue b/track-uniapp/src/pages/scan/detail.vue index b1c429a..4dfb252 100644 --- a/track-uniapp/src/pages/scan/detail.vue +++ b/track-uniapp/src/pages/scan/detail.vue @@ -246,7 +246,7 @@ export default { }, msgUnreadCount() { if (!this.lastMsgSeenAt) return this.messages.length; return this.messages.filter(m => m.created_at > this.lastMsgSeenAt).length; }, }, - onLoad(options) { this.loadUsers(); this.loadCurrentUser(); const sn = options.serial || ""; if (sn) this.doQuery(sn); }, + onLoad(options) { this.loadUsers(); this.loadCurrentUser(); const sn = options.serial || ""; if (sn) { this.doQuery(sn); return; } /* 🚀 兜底: 从 taskId 反查 product */ const tid = options.taskId || ""; if (tid) this.doQueryByTask(tid); }, // 🚀 onShow 生命周期:每次页面显示时刷新留言板(解决从聊天室退回不更新问题) onShow() { if (this.product?.id) { this.fetchMessages(); } }, methods: { @@ -256,6 +256,8 @@ export default { statusColor(s) { switch (s) { case "PENDING": return "s-yellow"; case "WIP": return "s-blue"; case "COMPLETED": return "s-green"; case "REJECTED": return "s-red"; default: return "s-gray"; } }, async doQuery(sn) { this.loading = true; this.error = ""; try { this.product = await get(`/products/scan/${sn}`); this.$nextTick(() => { this.currentMode = 'workspace'; this.autoLockTaskId = this.findMyImmersiveTask() || ''; if (!this.product.task_tree || !this.product.task_tree.length) { this.openCreateFirstTask(); } }); this.fetchMessages(); } catch (e) { this.error = e?.data?.detail || "查询失败"; } finally { this.loading = false; } }, + // 🚀 从 taskId 反查 product_serial → 再 doQuery + async doQueryByTask(tid) { try { const task = await get(`/tasks/${tid}`); const sn = task?.product_sn || ""; if (sn) { this.doQuery(sn); } else { this.error = "未找到关联产品"; this.loading = false; } } catch { this.error = "任务查询失败"; this.loading = false; } }, findMyImmersiveTask() { // 🚀 扫描用户的 WIP/PENDING 任务,自动沉浸锁定 if (!this.product || !this.product.task_tree) return null;