功能修改
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { AlertTriangle, CheckCircle2, ChevronDown, ChevronUp, Clock3, ListChecks } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { OperationRecord } from "../contracts/workspace";
|
||||
import { projectOperationForTray } from "../contracts/operationsConsole";
|
||||
import { cx } from "../utils/classes";
|
||||
|
||||
interface OperationsTrayProps {
|
||||
operations: OperationRecord[];
|
||||
}
|
||||
|
||||
export function OperationsTray({ operations }: OperationsTrayProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const items = useMemo(() => operations.slice(0, 6).map(projectOperationForTray), [operations]);
|
||||
const pendingCount = operations.filter((operation) => operation.status === "pending").length;
|
||||
const failedCount = operations.filter((operation) => operation.status === "failed").length;
|
||||
|
||||
return (
|
||||
<section className={cx("operations-tray", open && "operations-tray-open")} aria-label="当前会话操作">
|
||||
<button
|
||||
type="button"
|
||||
className="operations-tray-trigger"
|
||||
aria-expanded={open}
|
||||
aria-controls="session-operations-panel"
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
>
|
||||
<span className="operations-tray-glyph" aria-hidden="true">
|
||||
<ListChecks size={17} />
|
||||
</span>
|
||||
<span className="operations-tray-copy">
|
||||
<strong>会话操作</strong>
|
||||
<span>{pendingCount > 0 ? `${pendingCount} 项处理中` : failedCount > 0 ? `${failedCount} 项失败` : `${operations.length} 条记录`}</span>
|
||||
</span>
|
||||
{open ? <ChevronDown size={15} aria-hidden="true" /> : <ChevronUp size={15} aria-hidden="true" />}
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div id="session-operations-panel" className="operations-tray-panel" role="region" aria-live="polite">
|
||||
<div className="operations-tray-heading">
|
||||
<strong>当前浏览器会话</strong>
|
||||
<span>持久任务与审计记录以 Platform 页面为准</span>
|
||||
</div>
|
||||
{items.length === 0 ? (
|
||||
<p className="operations-tray-empty">本会话尚未提交资源变更。</p>
|
||||
) : (
|
||||
<ol className="operations-tray-list">
|
||||
{items.map((item) => (
|
||||
<li key={item.id} className={cx("operations-tray-item", `operations-tray-item-${item.status}`)}>
|
||||
<span className="operations-tray-status" aria-hidden="true">
|
||||
{item.status === "pending" ? <Clock3 size={14} /> : item.status === "succeeded" ? <CheckCircle2 size={14} /> : <AlertTriangle size={14} />}
|
||||
</span>
|
||||
<span className="operations-tray-item-copy">
|
||||
<strong>{item.intent}</strong>
|
||||
<span>{item.targetLabel}</span>
|
||||
<span>{item.status === "failed" ? item.errorReason : item.message ?? operationStatusLabel(item.status)}</span>
|
||||
{item.diagnosticId && <code>{item.diagnosticId}</code>}
|
||||
</span>
|
||||
<time dateTime={item.updatedAt}>{formatOperationTime(item.updatedAt)}</time>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function operationStatusLabel(status: OperationRecord["status"]): string {
|
||||
if (status === "pending") {
|
||||
return "等待 Platform 响应";
|
||||
}
|
||||
return status === "succeeded" ? "Platform 已确认" : "Platform 请求失败";
|
||||
}
|
||||
|
||||
function formatOperationTime(value: string): string {
|
||||
const time = new Date(value);
|
||||
return Number.isNaN(time.getTime()) ? "时间未知" : time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
Reference in New Issue
Block a user