fix(data): 增加全局保存失败保护
This commit is contained in:
81
apps/web/components/ServerDataSaveErrorBanner.tsx
Normal file
81
apps/web/components/ServerDataSaveErrorBanner.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { AlertTriangle, X } from 'lucide-react';
|
||||
import {
|
||||
SERVER_DATA_SAVE_ERROR_EVENT,
|
||||
type ServerDataKey,
|
||||
type ServerDataSaveErrorDetail,
|
||||
} from '@/lib/server-data';
|
||||
|
||||
const KEY_LABELS: Record<ServerDataKey, string> = {
|
||||
'products-overview': '产品/项目/版本',
|
||||
requirements: '需求池',
|
||||
'version-plans': '版本计划',
|
||||
'dev-tasks': '开发任务',
|
||||
'test-cases': '测试用例',
|
||||
bugs: 'BUG',
|
||||
members: '成员/账号',
|
||||
'task-categories': '任务类型',
|
||||
'task-worklogs': '工时日志',
|
||||
'work-activities': '工作动态',
|
||||
'xiaobao-risk-insights': '小宝预警解读',
|
||||
'xiaobao-risk-snapshots': '小宝预警快照',
|
||||
'xiaobao-warning-views': '小宝预警查看状态',
|
||||
overtime: '加班记录',
|
||||
};
|
||||
|
||||
export function ServerDataSaveErrorBanner() {
|
||||
const [detail, setDetail] = useState<ServerDataSaveErrorDetail | null>(null);
|
||||
const dismissTimer = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
function clearDismissTimer() {
|
||||
if (!dismissTimer.current) return;
|
||||
window.clearTimeout(dismissTimer.current);
|
||||
dismissTimer.current = null;
|
||||
}
|
||||
|
||||
function handleSaveError(event: Event) {
|
||||
const next = (event as CustomEvent<ServerDataSaveErrorDetail>).detail;
|
||||
if (!next?.key) return;
|
||||
clearDismissTimer();
|
||||
setDetail(next);
|
||||
dismissTimer.current = window.setTimeout(() => setDetail(null), 10_000);
|
||||
}
|
||||
|
||||
window.addEventListener(SERVER_DATA_SAVE_ERROR_EVENT, handleSaveError);
|
||||
return () => {
|
||||
clearDismissTimer();
|
||||
window.removeEventListener(SERVER_DATA_SAVE_ERROR_EVENT, handleSaveError);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!detail) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
className="fixed right-4 top-4 z-[100] flex w-[min(420px,calc(100vw-32px))] items-start gap-3 rounded-lg border border-red-200 bg-white px-4 py-3 text-left shadow-lg"
|
||||
>
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-red-500" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-[13px] font-semibold text-red-700">保存失败</p>
|
||||
<p className="mt-0.5 text-[12px] leading-5 text-red-600">
|
||||
{KEY_LABELS[detail.key]}没有写入服务器。请检查外网 API/网络后重试,必要时刷新页面。
|
||||
</p>
|
||||
{detail.message && (
|
||||
<p className="mt-1 truncate text-[11px] text-red-500">{detail.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDetail(null)}
|
||||
className="rounded-md p-1 text-red-400 hover:bg-red-50 hover:text-red-600"
|
||||
aria-label="关闭保存失败提示"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user