Merge AI-generated analysis and inventory drilldowns into main
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
shishengliang
2026-09-23 17:54:28 +08:00
50 changed files with 6739 additions and 1279 deletions
+43 -6
View File
@@ -54,8 +54,40 @@ import { OverviewView } from './components/OverviewView';
const AssetAssistant = lazy(() => import('./assistant/AssetAssistant'));
export default function AssetsModule() {
const [assistantOpen, setAssistantOpen] = useState(false);
const [assistantOpen, setAssistantOpen] = useState(() => window.location.hash === '#assets/assistant');
const assistantTrigger = useRef<HTMLButtonElement>(null);
useEffect(() => {
const syncAssistantRoute = () => setAssistantOpen(window.location.hash === '#assets/assistant');
window.addEventListener('hashchange', syncAssistantRoute);
window.addEventListener('popstate', syncAssistantRoute);
return () => {
window.removeEventListener('hashchange', syncAssistantRoute);
window.removeEventListener('popstate', syncAssistantRoute);
};
}, []);
useEffect(() => {
if (!assistantOpen) return;
const previousTitle = document.title;
document.title = 'AI 问数 - 资产BI';
return () => { document.title = previousTitle; };
}, [assistantOpen]);
const openAssistant = () => {
if (window.location.hash !== '#assets/assistant') {
const { pathname, search } = window.location;
window.history.pushState({ ...window.history.state, assetAssistantPage: true }, '', `${pathname}${search}#assets/assistant`);
}
setAssistantOpen(true);
};
const closeAssistant = () => {
if (window.history.state?.assetAssistantPage && window.location.hash === '#assets/assistant') {
window.history.back();
} else {
const { pathname, search } = window.location;
window.history.replaceState(window.history.state, '', `${pathname}${search}#assets`);
setAssistantOpen(false);
}
requestAnimationFrame(() => requestAnimationFrame(() => assistantTrigger.current?.focus()));
};
const [activeTab, setActiveTab] = useState<'overview' | 'department' | 'region' | 'customer'>('overview');
const [tabReady, setTabReady] = useState(true);
const prevTabRef = useRef(activeTab);
@@ -428,6 +460,14 @@ export default function AssetsModule() {
[customerData, customerChartView, customerProvinceData],
);
if (assistantOpen) {
return (
<Suspense fallback={<div role="status" className="min-h-screen bg-[var(--app-bg)] p-6 text-slate-700">正在加载 AI 问数页面…</div>}>
<AssetAssistant key={selectedSubject || 'all'} subject={selectedSubject} appearance="light" close={closeAssistant} />
</Suspense>
);
}
if (loading && !summary) {
return (
@@ -1707,16 +1747,13 @@ export default function AssetsModule() {
<button
ref={assistantTrigger}
type="button"
onClick={() => setAssistantOpen(true)}
aria-label="打开 AI 问数"
aria-haspopup="dialog"
aria-expanded={assistantOpen}
onClick={openAssistant}
aria-label="进入 AI 问数页面"
className="fixed bottom-[calc(5rem+env(safe-area-inset-bottom))] right-4 z-[60] inline-flex min-h-12 items-center gap-2 rounded-full bg-gradient-to-br from-blue-600 to-cyan-500 px-4 text-sm font-semibold text-white shadow-lg shadow-blue-200 transition hover:from-blue-700 hover:to-cyan-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 md:bottom-6 md:right-6"
>
<Sparkles size={20} aria-hidden="true" />
AI 问数
</button>
{assistantOpen && <Suspense fallback={<div role="status" className="fixed bottom-20 right-6 z-50 rounded-xl bg-white p-4 text-slate-900 shadow-lg">正在打开 AI 问数…</div>}><AssetAssistant key={selectedSubject || 'all'} subject={selectedSubject} appearance="light" close={() => { setAssistantOpen(false); requestAnimationFrame(() => assistantTrigger.current?.focus()); }} /></Suspense>}
</div>
);
}