|
|
|
@@ -46,6 +46,42 @@ const TABS = [
|
|
|
|
|
{ id: 'customer', label: '按客户' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function MarqueeBanner() {
|
|
|
|
|
const trackRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const innerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const [overflow, setOverflow] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const check = () => {
|
|
|
|
|
if (!trackRef.current || !innerRef.current) return;
|
|
|
|
|
setOverflow(innerRef.current.scrollWidth > trackRef.current.clientWidth);
|
|
|
|
|
};
|
|
|
|
|
check();
|
|
|
|
|
const ro = new ResizeObserver(check);
|
|
|
|
|
ro.observe(trackRef.current!);
|
|
|
|
|
return () => ro.disconnect();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const text = '⚠️ 因 OneOS 数据迁移未完成,当前状态暂不准确,预计下周完成对接后恢复正常。';
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="relative -mx-6 mb-4 bg-amber-50 border-y border-amber-200">
|
|
|
|
|
<div ref={trackRef} className="overflow-hidden">
|
|
|
|
|
<div className={`flex w-max py-2 ${overflow ? 'animate-marquee' : 'w-full justify-center'}`}>
|
|
|
|
|
<span ref={innerRef} className="inline-block whitespace-nowrap px-6 text-xs text-amber-700 font-medium">
|
|
|
|
|
{text}
|
|
|
|
|
</span>
|
|
|
|
|
{overflow && (
|
|
|
|
|
<span className="inline-block whitespace-nowrap px-6 text-xs text-amber-700 font-medium">
|
|
|
|
|
{text}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function AssetsModule() {
|
|
|
|
|
const [activeTab, setActiveTab] = useState<'overview' | 'department' | 'region' | 'customer'>('overview');
|
|
|
|
|
const [tabReady, setTabReady] = useState(true);
|
|
|
|
@@ -92,7 +128,7 @@ export default function AssetsModule() {
|
|
|
|
|
const [modalWeeklyDetail, setModalWeeklyDetail] = useState<WeeklyDetailItem[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
const [lastUpdate, setLastUpdate] = useState<string>('');
|
|
|
|
|
const [lastUpdate] = useState<string>('2026-06-03 23:59:59');
|
|
|
|
|
const [modalLoading, setModalLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Dept/Region/Customer data
|
|
|
|
@@ -162,7 +198,6 @@ export default function AssetsModule() {
|
|
|
|
|
setRegionData(region);
|
|
|
|
|
setCustomerData(cust);
|
|
|
|
|
setInventoryData(inv);
|
|
|
|
|
setLastUpdate(new Date().toLocaleString('zh-CN'));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
setError(e instanceof Error ? e.message : '数据加载失败');
|
|
|
|
|
} finally {
|
|
|
|
@@ -526,7 +561,7 @@ export default function AssetsModule() {
|
|
|
|
|
<div className="sticky top-0 z-40 -mx-6 -mt-6 mb-4 bg-white/95 backdrop-blur-sm border-b border-gray-100/80">
|
|
|
|
|
{/* Title row */}
|
|
|
|
|
<div className="relative flex items-center justify-center px-4 pt-3 pb-1">
|
|
|
|
|
<h1 className="hidden sm:block text-base font-semibold text-gray-800 tracking-wide">羚牛氢能车辆资产</h1>
|
|
|
|
|
<h1 className="hidden sm:block text-base font-semibold text-gray-800 tracking-wide">羚牛氢能-资产BI</h1>
|
|
|
|
|
{/* Right: status + theme */}
|
|
|
|
|
<div className="absolute right-4 top-1/2 -translate-y-1/2 flex items-center gap-2">
|
|
|
|
|
<div className="hidden sm:flex items-center gap-1 text-[10px] text-gray-400">
|
|
|
|
@@ -690,12 +725,15 @@ export default function AssetsModule() {
|
|
|
|
|
最后更新: {lastUpdate}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<span className="w-1 h-1 rounded-full bg-green-400 animate-pulse inline-block" />
|
|
|
|
|
每分钟更新一次
|
|
|
|
|
<span className="w-1 h-1 rounded-full bg-amber-400 inline-block" />
|
|
|
|
|
OneOS数据源接入中
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* OneOS 迁移提示滚动条 */}
|
|
|
|
|
<MarqueeBanner />
|
|
|
|
|
|
|
|
|
|
{/* Main Content Area */}
|
|
|
|
|
<div className="flex flex-col gap-6">
|
|
|
|
|
|
|
|
|
|