diff --git a/wwwroot/spa_router.js b/wwwroot/spa_router.js new file mode 100644 index 0000000..70b84b1 --- /dev/null +++ b/wwwroot/spa_router.js @@ -0,0 +1,223 @@ +/** + * 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(); } + }; + +})();