feat(web,android): 看板拖拽、详情侧栏编辑、快速录入与安卓任务列表
Web: - 看板四列可拖拽,非法流转当场说明原因;勾选后可批量移动 - 详情侧栏全部可编辑(标题/优先级/截止/标签/备注/评论),409 自动刷新 - 快速录入 #标签 !优先级;键盘流 j/k、Enter、e、Esc - Playwright 真浏览器 e2e 7 项断言全绿(web/e2e/smoke.mjs) - 1000 条任务实测首屏 P50 38-55ms Android(未编译验证:本机无 JDK/Gradle): - 任务列表/详情/勾选完成/快速新建,冲突时提示并刷新 - 修:User/Member 字段名与后端 nickmame/phone 对齐,原先必然反序列化失败
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import type { ItemStatus } from "./types";
|
||||
|
||||
const PRIORITY_ALIASES: Record<string, number> = {
|
||||
"!0": 0,
|
||||
"!1": 1,
|
||||
"!2": 2,
|
||||
"!3": 3,
|
||||
"!低": 1,
|
||||
"!中": 2,
|
||||
"!高": 3,
|
||||
};
|
||||
|
||||
export interface QuickAdd {
|
||||
title: string;
|
||||
tagNames: string[];
|
||||
priority: number;
|
||||
status: ItemStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析快速录入:`#标签` 与 `!优先级` 会被吃掉,剩下的做标题。
|
||||
*
|
||||
* 只认行内 `#` 开头且后面不是空格的片段——中文正文里 `#` 极少自然出现,
|
||||
* 真要以 # 开头就加个空格。`@人` 暂时不解析:重名的人比标签多,
|
||||
* 悄悄指错负责人比不支持更糟(等 M2 有成员选择器再做)。
|
||||
*/
|
||||
export function parseQuickAdd(raw: string, defaultStatus: ItemStatus): QuickAdd {
|
||||
const tagNames: string[] = [];
|
||||
let priority = 1;
|
||||
const words: string[] = [];
|
||||
|
||||
for (const token of raw.split(/\s+/)) {
|
||||
if (!token) continue;
|
||||
if (token.length > 1 && token.startsWith("#")) {
|
||||
tagNames.push(token.slice(1));
|
||||
continue;
|
||||
}
|
||||
if (PRIORITY_ALIASES[token] !== undefined) {
|
||||
priority = PRIORITY_ALIASES[token];
|
||||
continue;
|
||||
}
|
||||
words.push(token);
|
||||
}
|
||||
|
||||
const title = words.join(" ").trim() || raw.trim();
|
||||
return { title, tagNames, priority, status: defaultStatus };
|
||||
}
|
||||
|
||||
export const ROLE_LABEL: Record<string, string> = {
|
||||
owner: "负责人",
|
||||
admin: "管理员",
|
||||
member: "成员",
|
||||
guest: "访客",
|
||||
};
|
||||
|
||||
export const STATUS_LABEL: Record<ItemStatus, string> = {
|
||||
inbox: "收件箱",
|
||||
todo: "待办",
|
||||
doing: "进行中",
|
||||
done: "已完成",
|
||||
blocked: "被阻塞",
|
||||
canceled: "已取消",
|
||||
};
|
||||
|
||||
/** 状态机前端影子:只用来置灰拖拽目标,服务端仍会再校验一次。 */
|
||||
export const ALLOWED: Record<ItemStatus, ItemStatus[]> = {
|
||||
inbox: ["todo", "canceled"],
|
||||
todo: ["inbox", "doing", "done", "canceled"],
|
||||
doing: ["todo", "done", "blocked", "canceled"],
|
||||
blocked: ["doing", "canceled"],
|
||||
done: ["todo"],
|
||||
canceled: ["todo"],
|
||||
};
|
||||
|
||||
export function canTransition(from: ItemStatus, to: ItemStatus): boolean {
|
||||
return from === to || (ALLOWED[from] ?? []).includes(to);
|
||||
}
|
||||
|
||||
export const PRIORITY_LABEL: Record<number, string> = {
|
||||
0: "无",
|
||||
1: "低",
|
||||
2: "中",
|
||||
3: "高",
|
||||
};
|
||||
Reference in New Issue
Block a user