fix: make mileage fullscreen failures visible
This commit is contained in:
@@ -31,6 +31,7 @@ const HIGH_MILEAGE_ALERT_TARGETS = new Set([
|
||||
]);
|
||||
const HIGH_MILEAGE_ALERT_KM = 800;
|
||||
const ALL_MILEAGE_SOURCE_GROUPS: MileageSourceGroup[] = ['instrument', 'gps'];
|
||||
type MonitoringRequestParams = NonNullable<Parameters<typeof fetchMonitoring>[0]>;
|
||||
|
||||
const MILEAGE_SOURCE_META = {
|
||||
instrument: { label: '仪表数据', protocol: null },
|
||||
@@ -463,6 +464,9 @@ export default function MonitoringView() {
|
||||
const [fullscreenStats, setFullscreenStats] = useState<MonitoringStats>({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 });
|
||||
const [fullscreenRefresh, setFullscreenRefresh] = useState(0);
|
||||
const [fullscreenLoading, setFullscreenLoading] = useState(false);
|
||||
const [fullscreenError, setFullscreenError] = useState<string | null>(null);
|
||||
const [fullscreenSnapshotKey, setFullscreenSnapshotKey] = useState<string | null>(null);
|
||||
const [fullscreenUpdatedAt, setFullscreenUpdatedAt] = useState<string | null>(null);
|
||||
|
||||
// New filters from image
|
||||
const [filterPlates, setFilterPlates] = useState<string[]>(() => (
|
||||
@@ -479,6 +483,7 @@ export default function MonitoringView() {
|
||||
const [filterMileageRange, setFilterMileageRange] = useState({ min: '', max: '' });
|
||||
const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' });
|
||||
const [exporting, setExporting] = useState(false);
|
||||
const [exportStatus, setExportStatus] = useState<{ type: 'success' | 'error'; message: string } | null>(null);
|
||||
const [rangeStart, setRangeStart] = useState(() => drillContext.startDate || defaultMileageDate());
|
||||
const [rangeEnd, setRangeEnd] = useState(() => drillContext.endDate || defaultMileageDate());
|
||||
const [sourcePriority, setSourcePriority] = useState<MileageSourceGroup[]>(drillContext.sourcePriority);
|
||||
@@ -565,31 +570,54 @@ export default function MonitoringView() {
|
||||
return inAlertTarget && Math.max(0, v.dailyKm || 0) >= HIGH_MILEAGE_ALERT_KM;
|
||||
}, [filterTargetNames]);
|
||||
|
||||
const monitoringFilters = useMemo<MonitoringRequestParams>(() => ({
|
||||
search: searchTerm || undefined,
|
||||
dept: filterDept !== 'All' ? filterDept : undefined,
|
||||
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
|
||||
project: filterProject !== 'All' ? filterProject : undefined,
|
||||
entity: filterEntity !== 'All' ? filterEntity : undefined,
|
||||
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
|
||||
platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined,
|
||||
targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined,
|
||||
region: filterRegion !== 'All' ? filterRegion : undefined,
|
||||
plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined,
|
||||
mileageMin: appliedMileageRange.min || undefined,
|
||||
mileageMax: appliedMileageRange.max || undefined,
|
||||
startDate: rangeStart || undefined,
|
||||
endDate: rangeEnd || undefined,
|
||||
brands: filterBrands.length > 0 ? filterBrands : undefined,
|
||||
sourcePriority,
|
||||
}), [
|
||||
appliedMileageRange.max,
|
||||
appliedMileageRange.min,
|
||||
filterBrands,
|
||||
filterCustomer,
|
||||
filterDept,
|
||||
filterEntity,
|
||||
filterPlatePrefix,
|
||||
filterPlates,
|
||||
filterProject,
|
||||
filterRegion,
|
||||
filterRentStatus,
|
||||
filterTargetNames,
|
||||
rangeEnd,
|
||||
rangeStart,
|
||||
searchTerm,
|
||||
sourcePriority,
|
||||
]);
|
||||
const monitoringFilterKey = useMemo(() => JSON.stringify(monitoringFilters), [monitoringFilters]);
|
||||
const fullscreenHasSnapshot = fullscreenSnapshotKey === monitoringFilterKey;
|
||||
|
||||
// 加载首页数据
|
||||
const loadFirstPage = useCallback((showPageLoading = true, force = false) => {
|
||||
if (showPageLoading) setPageLoading(true);
|
||||
setPageError(null);
|
||||
return fetchMonitoring({
|
||||
...monitoringFilters,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
limit: PAGE_SIZE,
|
||||
page: 1,
|
||||
search: searchTerm || undefined,
|
||||
dept: filterDept !== 'All' ? filterDept : undefined,
|
||||
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
|
||||
project: filterProject !== 'All' ? filterProject : undefined,
|
||||
entity: filterEntity !== 'All' ? filterEntity : undefined,
|
||||
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
|
||||
platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined,
|
||||
targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined,
|
||||
region: filterRegion !== 'All' ? filterRegion : undefined,
|
||||
plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined,
|
||||
mileageMin: appliedMileageRange.min || undefined,
|
||||
mileageMax: appliedMileageRange.max || undefined,
|
||||
startDate: rangeStart || undefined,
|
||||
endDate: rangeEnd || undefined,
|
||||
brands: filterBrands.length > 0 ? filterBrands : undefined,
|
||||
sourcePriority,
|
||||
force,
|
||||
}).then(d => {
|
||||
setVehicles(d.vehicles);
|
||||
@@ -609,7 +637,7 @@ export default function MonitoringView() {
|
||||
}).finally(() => {
|
||||
if (showPageLoading) setPageLoading(false);
|
||||
});
|
||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, appliedMileageRange, rangeStart, rangeEnd, filterBrands, sourcePriority]);
|
||||
}, [monitoringFilters, rangeEnd, rangeStart, sortBy, sortOrder]);
|
||||
|
||||
const handleManualRefresh = useCallback(async () => {
|
||||
if (manualRefreshing) return;
|
||||
@@ -628,26 +656,11 @@ export default function MonitoringView() {
|
||||
setLoadingMore(true);
|
||||
setPageError(null);
|
||||
fetchMonitoring({
|
||||
...monitoringFilters,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
limit: PAGE_SIZE,
|
||||
page: nextPage,
|
||||
search: searchTerm || undefined,
|
||||
dept: filterDept !== 'All' ? filterDept : undefined,
|
||||
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
|
||||
project: filterProject !== 'All' ? filterProject : undefined,
|
||||
entity: filterEntity !== 'All' ? filterEntity : undefined,
|
||||
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
|
||||
platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined,
|
||||
targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined,
|
||||
region: filterRegion !== 'All' ? filterRegion : undefined,
|
||||
plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined,
|
||||
mileageMin: appliedMileageRange.min || undefined,
|
||||
mileageMax: appliedMileageRange.max || undefined,
|
||||
startDate: rangeStart || undefined,
|
||||
endDate: rangeEnd || undefined,
|
||||
brands: filterBrands.length > 0 ? filterBrands : undefined,
|
||||
sourcePriority,
|
||||
}).then(d => {
|
||||
setVehicles(prev => [...prev, ...d.vehicles]);
|
||||
setPage(nextPage);
|
||||
@@ -659,7 +672,7 @@ export default function MonitoringView() {
|
||||
scope: 'more',
|
||||
});
|
||||
}).finally(() => setLoadingMore(false));
|
||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, appliedMileageRange, rangeStart, rangeEnd, page, loadingMore, hasMore, filterBrands, sourcePriority]);
|
||||
}, [hasMore, loadingMore, monitoringFilters, page, sortBy, sortOrder]);
|
||||
|
||||
// 筛选/排序变化时重新加载
|
||||
useEffect(() => {
|
||||
@@ -678,36 +691,27 @@ export default function MonitoringView() {
|
||||
const handleDownload = useCallback(async () => {
|
||||
if (exporting) return;
|
||||
setExporting(true);
|
||||
setExportStatus(null);
|
||||
try {
|
||||
const d = await fetchMonitoring({
|
||||
...monitoringFilters,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
limit: 9999,
|
||||
page: 1,
|
||||
search: searchTerm || undefined,
|
||||
dept: filterDept !== 'All' ? filterDept : undefined,
|
||||
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
|
||||
project: filterProject !== 'All' ? filterProject : undefined,
|
||||
entity: filterEntity !== 'All' ? filterEntity : undefined,
|
||||
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
|
||||
platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined,
|
||||
targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined,
|
||||
region: filterRegion !== 'All' ? filterRegion : undefined,
|
||||
plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined,
|
||||
mileageMin: appliedMileageRange.min || undefined,
|
||||
mileageMax: appliedMileageRange.max || undefined,
|
||||
startDate: rangeStart || undefined,
|
||||
endDate: rangeEnd || undefined,
|
||||
brands: filterBrands.length > 0 ? filterBrands : undefined,
|
||||
sourcePriority,
|
||||
});
|
||||
exportMileageXlsx(d.vehicles, { startDate: d.dateRange?.start || rangeStart, endDate: d.dateRange?.end || rangeEnd, sortBy });
|
||||
setExportStatus({ type: 'success', message: `已导出 ${d.vehicles.length} 辆车辆` });
|
||||
} catch (err) {
|
||||
console.error('export failed', err);
|
||||
setExportStatus({
|
||||
type: 'error',
|
||||
message: err instanceof Error ? err.message : '导出失败,请稍后重试',
|
||||
});
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
}, [exporting, sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, appliedMileageRange, rangeStart, rangeEnd, filterBrands, sourcePriority]);
|
||||
}, [exporting, monitoringFilters, rangeEnd, rangeStart, sortBy, sortOrder]);
|
||||
|
||||
// 每分钟自动刷新
|
||||
useEffect(() => {
|
||||
@@ -742,31 +746,31 @@ export default function MonitoringView() {
|
||||
// 全屏时加载全部数据(无分页),筛选变化时重新加载
|
||||
useEffect(() => {
|
||||
if (!isFullscreen) return;
|
||||
let cancelled = false;
|
||||
setFullscreenLoading(true);
|
||||
setFullscreenError(null);
|
||||
fetchMonitoring({
|
||||
...monitoringFilters,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
limit: 9999,
|
||||
page: 1,
|
||||
search: searchTerm || undefined,
|
||||
dept: filterDept !== 'All' ? filterDept : undefined,
|
||||
customer: filterCustomer !== 'All' ? filterCustomer : undefined,
|
||||
rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined,
|
||||
platePrefix: filterPlatePrefix !== 'All' ? filterPlatePrefix : undefined,
|
||||
targetNames: filterTargetNames.length > 0 ? filterTargetNames : undefined,
|
||||
region: filterRegion !== 'All' ? filterRegion : undefined,
|
||||
plate: filterPlates.length > 0 ? filterPlates.join(',') : undefined,
|
||||
startDate: rangeStart || undefined,
|
||||
endDate: rangeEnd || undefined,
|
||||
brands: filterBrands.length > 0 ? filterBrands : undefined,
|
||||
sourcePriority,
|
||||
}).then(d => {
|
||||
if (cancelled) return;
|
||||
setFullscreenVehicles(d.vehicles);
|
||||
setFullscreenStats(d.stats);
|
||||
setFilterOptions(d.filters);
|
||||
setDataUpdatedAt(d.updatedAt);
|
||||
}).catch(() => {}).finally(() => setFullscreenLoading(false));
|
||||
}, [isFullscreen, sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, rangeStart, rangeEnd, fullscreenRefresh, filterBrands, sourcePriority]);
|
||||
setFullscreenUpdatedAt(d.updatedAt);
|
||||
setFullscreenSnapshotKey(monitoringFilterKey);
|
||||
}).catch(error => {
|
||||
if (cancelled) return;
|
||||
setFullscreenError(error instanceof Error ? error.message : '全屏监控加载失败,请稍后重试');
|
||||
}).finally(() => {
|
||||
if (!cancelled) setFullscreenLoading(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [fullscreenRefresh, isFullscreen, monitoringFilterKey, monitoringFilters, sortBy, sortOrder]);
|
||||
|
||||
// 全屏时禁止背景滚动
|
||||
useEffect(() => {
|
||||
@@ -804,6 +808,9 @@ export default function MonitoringView() {
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="里程全屏监控"
|
||||
className="fixed z-[100] bg-slate-950 flex flex-col overflow-hidden"
|
||||
style={
|
||||
forceLandscape
|
||||
@@ -827,13 +834,13 @@ export default function MonitoringView() {
|
||||
<h2 className="text-white font-bold text-xs">全屏监控</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-[10px]">
|
||||
<span className="text-slate-500">区间 <span className="text-white font-black">{Math.round(fullscreenStats.totalToday).toLocaleString()}</span> <span className="text-blue-400">km</span></span>
|
||||
<span className="text-slate-500">区间 <span className="text-white font-black">{fullscreenHasSnapshot ? Math.round(fullscreenStats.totalToday).toLocaleString() : '—'}</span> <span className="text-blue-400">km</span></span>
|
||||
<span className="text-slate-700">|</span>
|
||||
<span className="text-slate-500">累计 <span className="text-white font-black">{Math.round(fullscreenStats.totalAll).toLocaleString()}</span> <span className="text-blue-400">km</span></span>
|
||||
<span className="text-slate-500">累计 <span className="text-white font-black">{fullscreenHasSnapshot ? Math.round(fullscreenStats.totalAll).toLocaleString() : '—'}</span> <span className="text-blue-400">km</span></span>
|
||||
<span className="text-slate-700">|</span>
|
||||
<span className="text-slate-500">车辆 <span className="text-white font-black">{fullscreenStats.vehicleCount}</span> 台</span>
|
||||
<span className="text-slate-500">车辆 <span className="text-white font-black">{fullscreenHasSnapshot ? fullscreenStats.vehicleCount : '—'}</span> 台</span>
|
||||
<span className="text-slate-700">|</span>
|
||||
<span className="text-slate-500">均 <span className="text-white font-black">{(fullscreenStats.vehicleCount > 0 ? (sortBy === 'total' ? fullscreenStats.totalAll : fullscreenStats.totalToday) / fullscreenStats.vehicleCount : 0).toFixed(0)}</span> <span className="text-blue-400">km</span></span>
|
||||
<span className="text-slate-500">均 <span className="text-white font-black">{fullscreenHasSnapshot ? (fullscreenStats.vehicleCount > 0 ? (sortBy === 'total' ? fullscreenStats.totalAll : fullscreenStats.totalToday) / fullscreenStats.vehicleCount : 0).toFixed(0) : '—'}</span> <span className="text-blue-400">km</span></span>
|
||||
<span className="text-slate-700">|</span>
|
||||
<span className="text-slate-500">
|
||||
来源 <span className="text-white font-black">
|
||||
@@ -845,11 +852,14 @@ export default function MonitoringView() {
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => { setFullscreenRefresh(n => n + 1); }}
|
||||
disabled={fullscreenLoading}
|
||||
aria-label={fullscreenLoading ? '正在刷新全屏监控' : '刷新全屏监控'}
|
||||
title={fullscreenLoading ? '刷新中' : '刷新全屏监控'}
|
||||
className={`p-1.5 text-slate-500 hover:text-blue-400 transition-colors ${fullscreenLoading ? 'animate-spin' : ''}`}
|
||||
>
|
||||
<RotateCcw size={13} />
|
||||
</button>
|
||||
<button onClick={toggleFullscreen} className="p-1.5 text-slate-500 hover:text-white transition-colors">
|
||||
<button onClick={toggleFullscreen} aria-label="退出全屏监控" title="退出全屏" className="p-1.5 text-slate-500 hover:text-white transition-colors">
|
||||
<Minimize2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -879,9 +889,24 @@ export default function MonitoringView() {
|
||||
|
||||
{/* Table Area */}
|
||||
<div className="flex-1 overflow-hidden flex flex-col">
|
||||
{fullscreenError && fullscreenHasSnapshot && (
|
||||
<div role="alert" className="flex flex-shrink-0 items-center justify-between gap-3 border-b border-amber-500/20 bg-amber-500/10 px-3 py-1.5 text-[10px] font-bold text-amber-300">
|
||||
<span className="truncate" title={fullscreenError}>
|
||||
刷新失败,仍展示截至 {formatMileageSnapshotTime(fullscreenUpdatedAt)} 的数据
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFullscreenRefresh(value => value + 1)}
|
||||
disabled={fullscreenLoading}
|
||||
className="shrink-0 text-amber-200 underline underline-offset-2 disabled:opacity-50"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 overflow-auto relative">
|
||||
{fullscreenLoading && (
|
||||
{fullscreenLoading && fullscreenHasSnapshot && (
|
||||
<div className="absolute inset-0 bg-slate-950/60 z-20 flex items-center justify-center">
|
||||
<div className="flex items-center gap-2 text-slate-400 text-xs font-bold">
|
||||
<RotateCcw size={14} className="animate-spin" />
|
||||
@@ -889,7 +914,29 @@ export default function MonitoringView() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<table className="w-full text-left border-collapse">
|
||||
{!fullscreenHasSnapshot && (
|
||||
<div className="flex min-h-full items-center justify-center px-6 py-20">
|
||||
{fullscreenLoading ? (
|
||||
<div className="flex items-center gap-2 text-xs font-bold text-slate-400" role="status">
|
||||
<RotateCcw size={14} className="animate-spin" />
|
||||
加载全屏监控数据...
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-md text-center" role="alert">
|
||||
<p className="text-sm font-bold text-rose-300">全屏监控暂不可用</p>
|
||||
<p className="mt-1 text-[10px] text-slate-500">{fullscreenError || '未取得当前筛选条件下的数据'}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFullscreenRefresh(value => value + 1)}
|
||||
className="mt-3 rounded border border-slate-700 px-3 py-1.5 text-[10px] font-bold text-slate-300 hover:border-blue-500 hover:text-blue-300"
|
||||
>
|
||||
重新加载
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{fullscreenHasSnapshot && <table className="w-full text-left border-collapse">
|
||||
<thead className="sticky top-0 bg-slate-900 z-10">
|
||||
<tr className="border-b border-slate-800/60">
|
||||
<th className="px-3 py-2 text-[10px] font-bold text-slate-500 uppercase w-12 text-center">状态</th>
|
||||
@@ -1017,6 +1064,13 @@ export default function MonitoringView() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-800/30">
|
||||
{fullscreenVehicles.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-3 py-20 text-center text-xs font-bold text-slate-500">
|
||||
当前筛选条件下暂无车辆数据
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{fullscreenVehicles.map((v) => {
|
||||
const highMileageAlert = isHighMileageAlert(v);
|
||||
const statisticTime = vehicleStatisticTime(v, relativeNow);
|
||||
@@ -1058,7 +1112,7 @@ export default function MonitoringView() {
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</table>}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -1072,10 +1126,16 @@ export default function MonitoringView() {
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="truncate text-lg font-black leading-none text-slate-900">里程看板</h1>
|
||||
<button onClick={toggleFullscreen} className="p-1 text-slate-300 transition-colors hover:text-blue-600" title="全屏视图">
|
||||
<button onClick={toggleFullscreen} aria-label="打开全屏监控" className="p-1 text-slate-300 transition-colors hover:text-blue-600" title="全屏视图">
|
||||
<Maximize2 size={14} />
|
||||
</button>
|
||||
<button onClick={handleDownload} disabled={exporting} className="p-1 text-slate-300 transition-colors hover:text-blue-600 disabled:text-slate-200" title="下载当前筛选结果">
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
disabled={exporting}
|
||||
aria-label={exporting ? '正在导出当前筛选结果' : '导出当前筛选结果'}
|
||||
className="p-1 text-slate-300 transition-colors hover:text-blue-600 disabled:text-slate-200"
|
||||
title={exporting ? '导出中' : '下载当前筛选结果'}
|
||||
>
|
||||
{exporting ? <RotateCcw size={14} className="animate-spin" /> : <Download size={14} />}
|
||||
</button>
|
||||
</div>
|
||||
@@ -1120,6 +1180,32 @@ export default function MonitoringView() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{exportStatus && (
|
||||
<div
|
||||
role={exportStatus.type === 'error' ? 'alert' : 'status'}
|
||||
className={`flex items-center justify-between gap-3 border-t pt-2 text-[10px] font-bold ${
|
||||
exportStatus.type === 'error'
|
||||
? 'border-rose-100 text-rose-600'
|
||||
: 'border-emerald-100 text-emerald-600'
|
||||
}`}
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-1.5">
|
||||
{exportStatus.type === 'success' && <Check size={12} className="shrink-0" />}
|
||||
<span className="truncate" title={exportStatus.message}>{exportStatus.message}</span>
|
||||
</span>
|
||||
{exportStatus.type === 'error' && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDownload}
|
||||
disabled={exporting}
|
||||
className="shrink-0 underline underline-offset-2 disabled:opacity-50"
|
||||
>
|
||||
重试导出
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SourcePriorityControl priority={sourcePriority} onChange={setSourcePriority} />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user