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:
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams, useSearchParams } from "react-router-dom";
|
||||
import { AppShell } from "../components/AppShell";
|
||||
import { TaskDetail } from "../components/TaskDetail";
|
||||
import { Check, Inbox, Plus, Search } from "../components/icons";
|
||||
@@ -41,7 +41,9 @@ export function TasksPage() {
|
||||
const [draft, setDraft] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [openId, setOpenId] = useState<string | null>(null);
|
||||
// 从通知点进来时带 ?open=<id>,落地即展开那一条
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [openId, setOpenId] = useState<string | null>(searchParams.get("open"));
|
||||
const [activeIdx, setActiveIdx] = useState(0);
|
||||
const reqId = useRef(0);
|
||||
|
||||
@@ -333,7 +335,10 @@ export function TasksPage() {
|
||||
<TaskDetail
|
||||
wsId={wsId}
|
||||
itemId={openId}
|
||||
onClose={() => setOpenId(null)}
|
||||
onClose={() => {
|
||||
setOpenId(null);
|
||||
if (searchParams.get("open")) setSearchParams({}, { replace: true });
|
||||
}}
|
||||
onChanged={(updated) =>
|
||||
setItems((prev) => prev.map((it) => (it.id === updated.id ? updated : it)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user