/** * Bricks SPA Router * * 为bricks单页应用提供路由支持: * 1. urlwidget加载内容到sage_main_content时更新浏览器URL * 2. 浏览器刷新时根据URL恢复当前页面 * 3. 支持浏览器前进/后退按钮 * 4. 支持直接URL访问(深链接) * * URL格式: /?page=/module/index.ui * * 工作原理: * - 拦截bricks.buildUrlwidgetHandler,当target是sage_main_content时记录路由 * - 使用History API (pushState/replaceState)更新URL * - 监听popstate事件处理前进/后退 * - 页面加载时检查URL参数并恢复状态 */ (function() { 'use strict'; // 路由配置 var ROUTE_PARAM = 'page'; var MAIN_CONTENT_ID = 'sage_main_content'; // 路由状态 var currentRoute = null; var isPopState = false; var routerReady = false; // ── URL 操作 ── function getRouteFromURL() { var params = new URLSearchParams(window.location.search); return params.get(ROUTE_PARAM) || null; } function pushRoute(route) { var url = new URL(window.location); url.searchParams.set(ROUTE_PARAM, route); history.pushState({ route: route }, '', url.toString()); currentRoute = route; } function replaceRoute(route) { var url = new URL(window.location); if (route) { url.searchParams.set(ROUTE_PARAM, route); } else { url.searchParams.delete(ROUTE_PARAM); } history.replaceState({ route: route }, '', url.toString()); currentRoute = route; } // ── 路由加载 ── function waitForApp(maxWait) { return new Promise(function(resolve) { if (bricks && bricks.app) { resolve(true); return; } var waited = 0; var interval = setInterval(function() { waited += 100; if ((bricks && bricks.app) || waited >= maxWait) { clearInterval(interval); resolve(!!(bricks && bricks.app)); } }, 100); }); } function getMainContent() { if (!bricks || !bricks.app) return null; return bricks.getWidgetById(MAIN_CONTENT_ID, bricks.app); } /** * 程序化加载路由:构建urlwidget并放入sage_main_content */ async function loadRoute(route, pushHistory) { if (!route) return; var ready = await waitForApp(5000); if (!ready) { console.error('[SPA Router] bricks.app not ready'); return; } var mainContent = getMainContent(); if (!mainContent) { console.error('[SPA Router] #' + MAIN_CONTENT_ID + ' not found'); return; } // 避免重复加载 if (currentRoute === route && !isPopState) return; console.log('[SPA Router] Loading:', route); var desc = { widgettype: 'urlwidget', options: { url: route } }; try { var widget = await bricks.widgetBuild(desc, bricks.app); if (widget && !(widget instanceof bricks.Popup) && !(widget instanceof bricks.NewWindow)) { mainContent.clear_widgets(); mainContent.add_widget(widget); currentRoute = route; if (pushHistory && !isPopState) { pushRoute(route); } } } catch (e) { console.error('[SPA Router] Load failed:', route, e); } isPopState = false; } // ── 拦截 buildUrlwidgetHandler ── function installInterceptor() { var _orig = bricks.buildUrlwidgetHandler; bricks.buildUrlwidgetHandler = function(w, target, rtdata, desc) { var url = desc && desc.options ? desc.options.url : null; var targetId = target ? target.id : null; // 只拦截目标是sage_main_content的urlwidget if (targetId === MAIN_CONTENT_ID && url && routerReady) { // 获取原始handler var handler = _orig(w, target, rtdata, desc); if (typeof handler === 'function') { // 包装:执行原始handler后更新URL return async function() { await handler(); // 跳过已经是当前路由的情况 if (currentRoute !== url && !isPopState) { console.log('[SPA Router] Route changed:', url); pushRoute(url); } }; } } return _orig(w, target, rtdata, desc); }; console.log('[SPA Router] Interceptor installed'); } // ── popstate 处理 ── function onPopState(event) { var route = (event.state && event.state.route) || getRouteFromURL(); console.log('[SPA Router] popstate →', route); isPopState = true; if (route) { loadRoute(route, false); } } // ── 初始化 ── async function init() { console.log('[SPA Router] Initializing...'); // 等bricks.js加载完 if (typeof bricks === 'undefined') { await waitForApp(10000); } if (!bricks) { console.error('[SPA Router] bricks not found, aborting'); return; } installInterceptor(); window.addEventListener('popstate', onPopState); // 检查URL是否有初始路由 var initialRoute = getRouteFromURL(); if (initialRoute) { console.log('[SPA Router] Deep link:', initialRoute); // 等shell加载完再恢复 await waitForApp(5000); await new Promise(function(r) { setTimeout(r, 300); }); await loadRoute(initialRoute, false); // 用replaceState标记初始状态 replaceRoute(initialRoute); } else { // 记录初始状态 replaceRoute(null); } routerReady = true; console.log('[SPA Router] Ready'); } // 启动 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // 调试API window.BricksRouter = { loadRoute: function(route) { return loadRoute(route, true); }, current: function() { return currentRoute; }, back: function() { history.back(); }, forward: function() { history.forward(); } }; })();