268 lines
8.6 KiB
JavaScript
Raw Normal View History

/**
* channel-router.js
* 用户频道路由引擎 - 带过渡动画和状态持久化
* 集成ChannelState和动画控制
*/
// 依赖全局变量ChannelState 需要先加载
(function(global) {
'use strict';
// 默认配置
const defaultConfig = {
containerSelector: '#channel-content',
routes: {},
defaultRoute: '/home',
mode: 'fade', // 'fade' 或 'slide'
transitionDuration: 300
};
class ChannelRouter {
constructor(config) {
this.config = Object.assign({}, defaultConfig, config);
this.container = document.querySelector(this.config.containerSelector);
if (!this.container) {
throw new Error(`Container ${this.config.containerSelector} not found`);
}
// 确保容器有相对定位
this.container.style.position = 'relative';
this.container.classList.add('route-container');
// 当前活动路由
this.currentRoute = null;
this.currentPageElement = null;
// 防抖定时器
this.navTimer = null;
// 绑定方法
this.navigate = this.navigate.bind(this);
this.goBack = this.goBack.bind(this);
this.goForward = this.goForward.bind(this);
this.setMode = this.setMode.bind(this);
this.handlePopState = this.handlePopState.bind(this);
// 初始化模式
this.container.classList.add(`${this.config.mode}-mode`);
// 从ChannelState恢复上次的路由
this.initFromState();
// 监听浏览器前进后退
window.addEventListener('popstate', this.handlePopState);
console.log('[router] 初始化完成,模式:', this.config.mode);
}
// 从ChannelState恢复
initFromState() {
if (global.ChannelState) {
const state = global.ChannelState.getState();
const targetRoute = state.currentRoute || this.config.defaultRoute;
// 不触发pushState直接渲染
this.renderRoute(targetRoute, { replace: true, fromState: true });
console.log('[router] 从状态恢复路由:', targetRoute);
} else {
// 没有状态管理器,走默认
this.renderRoute(this.config.defaultRoute, { replace: true });
}
}
// 渲染路由(内部方法)
renderRoute(path, options = {}) {
const { replace = false, fromState = false } = options;
const route = this.config.routes[path];
if (!route) {
console.warn(`[router] 路由 ${path} 未定义使用404`);
// 可以跳转到404页面这里简单返回
return;
}
// 如果是相同路由且不是强制刷新,不重复渲染
if (this.currentRoute === path && !options.force) {
return;
}
// 创建新页面元素
const newPage = document.createElement('div');
newPage.className = `route-page ${this.config.mode === 'slide' ? 'slide-enter' : ''}`;
newPage.innerHTML = route.template || route.content || '';
// 如果有模块加载器,执行模块加载
if (global.ModuleLoader && route.module) {
// 这里简化,实际可能需要加载模块
console.log('[router] 加载模块:', route.module);
}
// 旧页面元素
const oldPage = this.currentPageElement;
// 设置新页面为激活状态
newPage.classList.add('active');
// 如果是滑入模式,根据方向添加额外类
if (this.config.mode === 'slide') {
// 通过history判断方向前进/后退
const direction = this.getDirection(path);
if (direction === 'back') {
this.container.classList.add('backward');
this.container.classList.remove('forward');
} else {
this.container.classList.add('forward');
this.container.classList.remove('backward');
}
}
// 添加新页面到容器
this.container.appendChild(newPage);
// 触发重绘以确保动画
newPage.offsetHeight;
// 如果有旧页面移除它的active类并添加退出动画类
if (oldPage) {
oldPage.classList.remove('active');
if (this.config.mode === 'slide') {
oldPage.classList.add('slide-exit');
}
}
// 动画结束后清理旧页面
const onTransitionEnd = (e) => {
if (e.target === newPage || e.target === oldPage) {
if (oldPage && oldPage.parentNode) {
oldPage.parentNode.removeChild(oldPage);
}
newPage.removeEventListener('transitionend', onTransitionEnd);
}
};
newPage.addEventListener('transitionend', onTransitionEnd);
// 更新当前路由
this.currentRoute = path;
this.currentPageElement = newPage;
// 更新导航菜单激活状态
this.updateActiveNav(path);
// 保存状态(如果不是从状态恢复来的)
if (!fromState && global.ChannelState) {
// 判断是push还是replace
if (replace) {
// 替换当前历史记录(不增加新记录)
// 状态管理需要相应处理替换当前记录而不是push
global.ChannelState.setCurrentRoute(path);
// 同时替换浏览器历史
if (!options.skipHistory) {
history.replaceState({ route: path }, '', `#${path}`);
}
} else {
// 正常跳转push到历史
global.ChannelState.pushHistory(path);
if (!options.skipHistory) {
history.pushState({ route: path }, '', `#${path}`);
}
}
} else if (!global.ChannelState) {
// 没有状态管理器,只处理浏览器历史
if (!replace && !options.skipHistory) {
history.pushState({ route: path }, '', `#${path}`);
} else if (replace && !options.skipHistory) {
history.replaceState({ route: path }, '', `#${path}`);
}
}
console.log(`[router] 导航到 ${path}${replace ? ' (replace)' : ''}`);
}
// 判断前进后退方向(简单实现:看是否在历史栈中)
getDirection(path) {
if (!global.ChannelState) return 'forward';
const state = global.ChannelState.getState();
const currentIndex = state.historyIndex;
const stack = state.historyStack;
// 如果path在历史栈中且在当前位置之后是后退需要更精确
// 这里简化:根据当前路由和目标的索引比较
if (this.currentRoute) {
const currentIdx = stack.indexOf(this.currentRoute);
const targetIdx = stack.indexOf(path);
if (targetIdx < currentIdx) return 'back';
}
return 'forward';
}
// 更新导航菜单激活样式
updateActiveNav(path) {
document.querySelectorAll('.channel-nav a').forEach(link => {
const href = link.getAttribute('href').replace('#', '');
if (href === path) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
// 公开导航方法
navigate(path, options = {}) {
// 防抖处理
if (this.navTimer) clearTimeout(this.navTimer);
this.navTimer = setTimeout(() => {
this.renderRoute(path, options);
this.navTimer = null;
}, 10); // 微小延迟确保快速点击不重叠
}
// 后退
goBack() {
if (global.ChannelState) {
const prev = global.ChannelState.goBack();
if (prev) {
this.renderRoute(prev, { fromState: true, skipHistory: true });
} else {
console.log('[router] 已在最前');
}
} else {
history.back();
}
}
// 前进
goForward() {
if (global.ChannelState) {
const next = global.ChannelState.goForward();
if (next) {
this.renderRoute(next, { fromState: true, skipHistory: true });
} else {
console.log('[router] 已在最后');
}
} else {
history.forward();
}
}
// 处理popstate事件浏览器前进后退
handlePopState(event) {
const route = event.state?.route || this.config.defaultRoute;
if (global.ChannelState) {
// 从状态中恢复索引但不需要重复push
this.renderRoute(route, { fromState: true, skipHistory: true });
} else {
this.renderRoute(route, { skipHistory: true });
}
}
// 切换动画模式
setMode(mode) {
if (mode !== 'fade' && mode !== 'slide') return;
this.container.classList.remove('fade-mode', 'slide-mode');
this.container.classList.add(`${mode}-mode`);
this.config.mode = mode;
console.log('[router] 切换动画模式为:', mode);
}
}
global.ChannelRouter = ChannelRouter;
})(window);