"use client"; import Link from "next/link"; import { useEffect, useMemo, useState } from "react"; import workData from "../data/work-list.json"; import styles from "./WorkList.module.css"; type WorkItem = { id: string; code: string; name: string; kind: "project" | "milestone" | "task" | "subtask"; owner: { name: string; initial: string; tone: string }; status: string; priority: string; progress: number; startDate: string; dueDate: string; tags: string[]; children: WorkItem[]; }; type FlatRow = { item: WorkItem; level: number; parentId?: string }; const initialItems = workData.items as WorkItem[]; function parentIds(items: WorkItem[]): string[] { return items.flatMap((item) => item.children.length ? [item.id, ...parentIds(item.children)] : []); } function flatten(items: WorkItem[], expanded: Set, level = 0, parentId?: string): FlatRow[] { const rows: FlatRow[] = []; items.forEach((item) => { rows.push({ item, level, parentId }); if (item.children.length && expanded.has(item.id)) rows.push(...flatten(item.children, expanded, level + 1, item.id)); }); return rows; } function filterTree(items: WorkItem[], search: string, status: string, priority: string): WorkItem[] { return items.flatMap((item) => { const children = filterTree(item.children, search, status, priority); const matchesSearch = !search || `${item.name}${item.code}${item.owner.name}${item.tags.join("")}`.toLowerCase().includes(search.toLowerCase()); const matchesStatus = status === "全部状态" || item.status === status; const matchesPriority = priority === "全部优先级" || item.priority === priority; return (matchesSearch && matchesStatus && matchesPriority) || children.length ? [{ ...item, children }] : []; }); } function findItem(items: WorkItem[], id: string): WorkItem | undefined { for (const item of items) { if (item.id === id) return item; const child = findItem(item.children, id); if (child) return child; } } function insertChild(items: WorkItem[], parentId: string, child: WorkItem): WorkItem[] { return items.map((item) => item.id === parentId ? { ...item, children: [...item.children, child] } : { ...item, children: insertChild(item.children, parentId, child) }); } export default function WorkListPage() { const [items, setItems] = useState(initialItems); const [expanded, setExpanded] = useState>(() => new Set(parentIds(initialItems))); const [selected, setSelected] = useState>(new Set()); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("全部状态"); const [priorityFilter, setPriorityFilter] = useState("全部优先级"); const [activeTab, setActiveTab] = useState("全部工作"); const [detailId, setDetailId] = useState(null); const [createOpen, setCreateOpen] = useState(false); const [toast, setToast] = useState(""); useEffect(() => { const saved = window.localStorage.getItem("orbit-work-list"); if (saved) { try { setItems(JSON.parse(saved)); } catch { window.localStorage.removeItem("orbit-work-list"); } } }, []); useEffect(() => { window.localStorage.setItem("orbit-work-list", JSON.stringify(items)); }, [items]); const filtered = useMemo(() => filterTree(items, search, statusFilter, priorityFilter), [items, search, statusFilter, priorityFilter]); const rows = useMemo(() => flatten(filtered, expanded), [filtered, expanded]); const detailItem = detailId ? findItem(items, detailId) : undefined; const notify = (message: string) => { setToast(message); window.setTimeout(() => setToast(""), 2300); }; const toggleExpand = (id: string) => setExpanded((current) => { const next = new Set(current); if (next.has(id)) next.delete(id); else next.add(id); return next; }); const toggleSelect = (id: string) => setSelected((current) => { const next = new Set(current); if (next.has(id)) next.delete(id); else next.add(id); return next; }); const toggleAll = () => { if (rows.length > 0 && rows.every(({ item }) => selected.has(item.id))) setSelected(new Set()); else setSelected(new Set(rows.map(({ item }) => item.id))); }; const updateStatus = (id: string, status: string) => { const update = (source: WorkItem[]): WorkItem[] => source.map((item) => item.id === id ? { ...item, status } : { ...item, children: update(item.children) }); setItems(update); notify("工作状态已保存到本地"); }; const createItem = (name: string, parentId: string, priority: string) => { const parent = findItem(items, parentId); const item: WorkItem = { id: `task-${Date.now()}`, code: `TASK-${String(Date.now()).slice(-4)}`, name, kind: parent?.kind === "task" ? "subtask" : "task", owner: { name: "林知夏", initial: "林", tone: "blue" }, status: "待处理", priority, progress: 0, startDate: "2026-08-07", dueDate: "2026-08-21", tags: ["新任务"], children: [], }; setItems((current) => insertChild(current, parentId, item)); setExpanded((current) => new Set([...current, parentId])); setCreateOpen(false); notify("新任务已添加到工作列表"); }; return (
工作管理/工作列表

WORK MANAGEMENT

工作列表

用树形结构管理项目、里程碑、任务与子任务。

全部工作{workData.summary.total}包含 3 个项目

进行中{workData.summary.inProgress}本周新增 4 项

!

即将到期{workData.summary.dueSoon}7 天内需完成

已完成{workData.summary.completed}完成率 44%

{["全部工作", "我负责的", "我参与的", "已归档"].map((tab) => )}
显示 {rows.length} 项
{rows.map(({ item, level }) => ( ))} {rows.length === 0 && }
0 && rows.every(({ item }) => selected.has(item.id))} onChange={toggleAll} aria-label="选择全部" />工作项负责人状态优先级进度开始日期截止日期
toggleSelect(item.id)} aria-label={`选择${item.name}`} />
{level > 0 && } {item.children.length ? : } {item.kind === "project" ? "项" : item.kind === "milestone" ? "碑" : item.kind === "task" ? "任" : "子"} {item.tags.slice(0, 1).map((tag) => {tag})}
{item.owner.initial}{item.owner.name}
{item.status} {item.priority === "高" ? "↑" : item.priority === "低" ? "↓" : "="} {item.priority}
{item.progress}%
{item.startDate.slice(5).replace("-", "/")} {item.dueDate.slice(5).replace("-", "/")}
没有匹配的工作项

请调整搜索条件或筛选项。

共 36 条,每页 20 条
{selected.size > 0 &&
已选择 {selected.size}
} {detailItem && setDetailId(null)} updateStatus={updateStatus} notify={notify} />} {createOpen && setCreateOpen(false)} onCreate={createItem} />} {toast &&
{toast}
}
); } function DetailDrawer({ item, onClose, updateStatus, notify }: { item: WorkItem; onClose: () => void; updateStatus: (id: string, status: string) => void; notify: (message: string) => void }) { return
; } function CreateModal({ items, onClose, onCreate }: { items: WorkItem[]; onClose: () => void; onCreate: (name: string, parentId: string, priority: string) => void }) { const [name, setName] = useState(""); const [parentId, setParentId] = useState(items[0]?.id ?? ""); const [priority, setPriority] = useState("中"); const parents = flatten(items, new Set(parentIds(items))).filter(({ item }) => item.kind !== "subtask"); return
event.stopPropagation()} onSubmit={(event) => { event.preventDefault(); if (name.trim() && parentId) onCreate(name.trim(), parentId, priority); }}>

新建工作项

工作项将保存到当前浏览器的本地数据。

优先级{["高", "中", "低"].map((value) => )}
; }