feat(web): 工单页与升级面板,修掉视图竞态与标签缺失导致的崩溃
- 新增 /w/:wsId/tickets:派给我的 / 我发起的、状态过滤、列表内联快操作 - 详情抽屉按角色显示受理/开工/提交/验收/驳回/改派/取消;任务态底部可升级成工单 - 导航抽成 WorkspaceNav,带未读角标(30 秒对齐,操作后立刻刷新) - 修竞态:切视图/切 tab 连发请求时,慢的回包会把新结果盖掉(reqId 序号丢弃过期响应) - 登录支持 ?next= 站内回跳,邀请链接不再'登录完掉回首页' - e2e/tickets.mjs:双人双上下文的完整闭环,8 项断言全绿
This commit is contained in:
+15
-6
@@ -1,4 +1,4 @@
|
||||
import type { ItemStatus } from "./types";
|
||||
import type { ItemStatus, TaskStatus } from "./types";
|
||||
|
||||
const PRIORITY_ALIASES: Record<string, number> = {
|
||||
"!0": 0,
|
||||
@@ -53,17 +53,26 @@ export const ROLE_LABEL: Record<string, string> = {
|
||||
guest: "访客",
|
||||
};
|
||||
|
||||
export const STATUS_LABEL: Record<ItemStatus, string> = {
|
||||
export const STATUS_LABEL: Record<string, string> = {
|
||||
// 任务
|
||||
inbox: "收件箱",
|
||||
todo: "待办",
|
||||
doing: "进行中",
|
||||
done: "已完成",
|
||||
blocked: "被阻塞",
|
||||
canceled: "已取消",
|
||||
// 工单(与任务的 done/canceled 共用)
|
||||
open: "待接收",
|
||||
accepted: "待办理",
|
||||
in_progress: "办理中",
|
||||
pending_verify: "待验收",
|
||||
};
|
||||
|
||||
/** 状态机前端影子:只用来置灰拖拽目标,服务端仍会再校验一次。 */
|
||||
export const ALLOWED: Record<ItemStatus, ItemStatus[]> = {
|
||||
/**
|
||||
* 任务状态机的前端影子:只用来置灰拖拽目标,服务端仍会再校验一次。
|
||||
* 工单的状态机在服务端(动作驱动),前端不做影子,直接看按钮。
|
||||
*/
|
||||
export const ALLOWED: Record<string, TaskStatus[]> = {
|
||||
inbox: ["todo", "canceled"],
|
||||
todo: ["inbox", "doing", "done", "canceled"],
|
||||
doing: ["todo", "done", "blocked", "canceled"],
|
||||
@@ -72,8 +81,8 @@ export const ALLOWED: Record<ItemStatus, ItemStatus[]> = {
|
||||
canceled: ["todo"],
|
||||
};
|
||||
|
||||
export function canTransition(from: ItemStatus, to: ItemStatus): boolean {
|
||||
return from === to || (ALLOWED[from] ?? []).includes(to);
|
||||
export function canTransition(from: string, to: string): boolean {
|
||||
return from === to || (ALLOWED[from] ?? []).includes(to as TaskStatus);
|
||||
}
|
||||
|
||||
export const PRIORITY_LABEL: Record<number, string> = {
|
||||
|
||||
+38
-1
@@ -39,7 +39,16 @@ export interface Invitation {
|
||||
expires_at: string;
|
||||
}
|
||||
|
||||
export type ItemStatus = "inbox" | "todo" | "doing" | "done" | "blocked" | "canceled";
|
||||
export type TaskStatus = "inbox" | "todo" | "doing" | "done" | "blocked" | "canceled";
|
||||
export type TicketStatus =
|
||||
| "open"
|
||||
| "accepted"
|
||||
| "in_progress"
|
||||
| "pending_verify"
|
||||
| "done"
|
||||
| "canceled";
|
||||
|
||||
export type ItemStatus = TaskStatus | TicketStatus;
|
||||
|
||||
export interface Tag {
|
||||
id: string;
|
||||
@@ -74,4 +83,32 @@ export interface Activity {
|
||||
|
||||
export interface ItemDetail extends Item {
|
||||
activities: Activity[];
|
||||
ticket?: TicketInfo;
|
||||
}
|
||||
|
||||
export interface TicketInfo {
|
||||
requester_id: string;
|
||||
assignee_id: string;
|
||||
parent_item_id: string | null;
|
||||
accepted_at: string | null;
|
||||
submitted_at: string | null;
|
||||
verified_at: string | null;
|
||||
verified_by: string | null;
|
||||
reject_count: number;
|
||||
resolution_md: string;
|
||||
}
|
||||
|
||||
export interface Ticket extends Item {
|
||||
ticket: TicketInfo;
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
kind: string;
|
||||
item_id: string | null;
|
||||
item_title: string | null;
|
||||
title: string;
|
||||
body: string;
|
||||
read_at: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user