M2 最后一块:让"该我办了"能出现在系统里,并且允许不经过任务直接开单。 通知(backend/app/services/notify.py 统一出口) - 七类触发:task_assigned、task_status_changed、item_commented、 ticket_assigned、ticket_submitted、ticket_approved/rejected、task_unblocked - 不通知自己:notify() 按 actor_id 跳过,调用方不用各自判断 - 已读:GET /notifications、/unread-count、POST /read、/read-all 手工开单 - POST /tickets:直接指定人、初始 open,与任务升级单共用同一条 kind='ticket' item, ticket_ext.parent_item_id 为空,验收通过不回流任务 - 指派前 _ensure_member 校验在册成员,越界指派返回 1001(避免"挂在看不到的名字上") 前端 - Web:通知页 /w/:wsId/notifications(未读/全部,点开标已读并跳目标); 工单页"新建"面板;AppShell 每 30 秒对未读数,标已读后 dispatch notifications:changed 立刻刷新; 任务与工单支持 ?open= 深链落地即展开 - 安卓:五个 tab 加"通知",未读数 30 秒刷新;工单动作按需求人/办理人/管理员显隐;手工开单对话框 验证 - pytest 85 passed(新增 backend/tests/test_notifications.py 6 个用例) - ruff check / format --check 全 0 - tsc --noEmit 通过;vite build 成功 - web/e2e/notifications.mjs 12 项断言全绿(手工开单 → B 收到 → 点开跳转 → 未读归零) - 安卓:本机 JDK 25 与 Gradle 8.7 / AGP 8.7.3 不兼容,本次未做编译验证, 装回 JDK 17 后第一件事是 assembleDebug
This commit is contained in:
@@ -27,6 +27,7 @@ import com.dadao.task.ui.HomeScreen
|
||||
import com.dadao.task.ui.KnowledgeScreen
|
||||
import com.dadao.task.ui.LoginScreen
|
||||
import com.dadao.task.ui.MembersScreen
|
||||
import com.dadao.task.ui.NotificationsScreen
|
||||
import com.dadao.task.ui.RichTextEditorScreen
|
||||
import com.dadao.task.ui.TaskListScreen
|
||||
import com.dadao.task.ui.TicketsScreen
|
||||
@@ -71,6 +72,8 @@ private fun AppRoot(joinUri: String?) {
|
||||
var current by remember { mutableStateOf<Workspace?>(null) }
|
||||
var tab by remember { mutableStateOf(WorkspaceTab.Tasks) }
|
||||
var unread by remember { mutableStateOf(0) }
|
||||
// 通知页标了已读之后要立刻刷新角标:改这个数会触发下面的 LaunchedEffect
|
||||
var unreadRefresh by remember { mutableStateOf(0) }
|
||||
// 打开富文本编辑器时非空;关掉就恢复原来的界面
|
||||
var editing by remember { mutableStateOf<EditRequest?>(null) }
|
||||
// 编辑器保存成功的那条:回到工作区后重新打开它的详情,然后清掉
|
||||
@@ -105,8 +108,8 @@ private fun AppRoot(joinUri: String?) {
|
||||
.onFailure { pendingJoin = null }
|
||||
}
|
||||
|
||||
// 工单未读数:进工作区就拉一次,切页面时再刷。
|
||||
LaunchedEffect(current?.id, tab) {
|
||||
// 未读数:进工作区、切页面、或通知页标了已读(unreadRefresh)时刷新
|
||||
LaunchedEffect(current?.id, tab, unreadRefresh) {
|
||||
val ws = current ?: return@LaunchedEffect
|
||||
unread = api.unreadCount(ws.id)
|
||||
}
|
||||
@@ -184,7 +187,12 @@ private fun AppRoot(joinUri: String?) {
|
||||
onReopened = { reopenItem = null },
|
||||
)
|
||||
|
||||
WorkspaceTab.Tickets -> TicketsScreen(api = api, workspace = ws)
|
||||
WorkspaceTab.Tickets -> TicketsScreen(
|
||||
api = api,
|
||||
workspace = ws,
|
||||
focusId = reopenItem,
|
||||
onFocusHandled = { reopenItem = null },
|
||||
)
|
||||
WorkspaceTab.Knowledge -> KnowledgeScreen(
|
||||
api = api,
|
||||
workspace = ws,
|
||||
@@ -194,6 +202,18 @@ private fun AppRoot(joinUri: String?) {
|
||||
)
|
||||
|
||||
WorkspaceTab.Members -> MembersScreen(api = api, workspace = ws)
|
||||
|
||||
WorkspaceTab.Notifications -> NotificationsScreen(
|
||||
api = api,
|
||||
workspaceId = ws.id,
|
||||
onOpenItem = { itemId, isTicket ->
|
||||
// 通知点开后跳到对应列表并展开那一条;通知本身不带数据,页面自己回源
|
||||
reopenItem = itemId
|
||||
reopenKnowledge = false
|
||||
tab = if (isTicket) WorkspaceTab.Tickets else WorkspaceTab.Tasks
|
||||
},
|
||||
onUnreadChanged = { unreadRefresh += 1 },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user