guanghulab/modules/m-channel/all-in-one.html

397 lines
17 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>光湖频道 · 一体化版</title>
<style>
.channel-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: system-ui, -apple-system, sans-serif;
}
.channel-nav {
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 2px solid #e5e7eb;
padding-bottom: 10px;
}
.channel-btn {
padding: 10px 20px;
border: none;
background: #f3f4f6;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
transition: all 0.2s;
}
.channel-btn:hover {
background: #e5e7eb;
}
.channel-btn.active {
background: #3b82f6;
color: white;
}
.channel-btn.visited {
position: relative;
}
.channel-btn.visited::after {
content: "✓";
position: absolute;
top: -5px;
right: -5px;
background: #10b981;
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.channel-content {
min-height: 400px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
transition: opacity 0.3s ease-in-out;
}
.channel-content.fade-out {
opacity: 0;
}
.channel-content.fade-in {
opacity: 1;
}
</style>
</head>
<body>
<div class="channel-container">
<nav class="channel-nav">
<button class="channel-btn active" data-channel="home">🏠 首页</button>
<button class="channel-btn" data-channel="module-a">📦 模块A</button>
<button class="channel-btn" data-channel="module-b">📦 模块B</button>
<button class="channel-btn" data-channel="module-c">📦 模块C</button>
<button class="channel-btn" data-channel="debug">🐞 调试面板</button>
</nav>
<main id="channel-content" class="channel-content">
<!-- 动态内容会加载到这里 -->
</main>
</div>
<script>
// ==================== 所有代码整合在一个文件里 ====================
// ---------- 模块注册表 ----------
window.ModuleRegistry = {
modules: {},
register: function(name, module) {
this.modules[name] = module;
console.log(`[registry] 模块 ${name} 已注册`);
},
get: function(name) {
return this.modules[name];
},
list: function() {
return Object.keys(this.modules);
}
};
// ---------- 事件总线 ----------
window.EventBus = {
listeners: {},
on: function(event, callback) {
if (!this.listeners[event]) this.listeners[event] = [];
this.listeners[event].push(callback);
console.log(`[事件总线] 订阅事件: ${event}`);
},
off: function(event, callback) {
if (!this.listeners[event]) return;
if (!callback) delete this.listeners[event];
else this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
console.log(`[事件总线] 取消订阅: ${event}`);
},
emit: function(event, data) {
console.log(`[事件总线] 发送事件: ${event}`, data);
if (!this.listeners[event]) return;
this.listeners[event].forEach(callback => {
try { callback(data); } catch (e) { console.error(`[事件总线] 执行回调出错: ${e}`); }
});
}
};
// ---------- 模块生命周期 ----------
window.ModuleLifecycle = {
activeModules: new Set(),
onLoad: function(moduleName) {
this.activeModules.add(moduleName);
console.log(`[生命周期] 模块加载: ${moduleName}`);
EventBus.emit('module:loaded', { module: moduleName, time: Date.now() });
},
onUnload: function(moduleName) {
this.activeModules.delete(moduleName);
console.log(`[生命周期] 模块卸载: ${moduleName}`);
EventBus.emit('module:unloaded', { module: moduleName, time: Date.now() });
},
getActiveModules: function() {
return Array.from(this.activeModules);
}
};
// ---------- 频道状态 ----------
window.ChannelState = {
STORAGE_KEY: 'hololake_channel_state',
saveState: function(state) {
const data = { ...state, timestamp: Date.now(), visited: state.visited || [] };
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
console.log('[state] 保存状态:', data);
},
restoreState: function() {
const saved = localStorage.getItem(this.STORAGE_KEY);
if (!saved) return null;
try {
const state = JSON.parse(saved);
console.log('[state] 恢复状态:', state);
return state;
} catch (e) {
console.error('[state] 解析失败:', e);
return null;
}
},
markVisited: function(channel) {
const state = this.restoreState() || { visited: [] };
if (!state.visited.includes(channel)) {
state.visited.push(channel);
this.saveState(state);
}
},
clearState: function() {
localStorage.removeItem(this.STORAGE_KEY);
console.log('[state] 已清除');
}
};
// ---------- 模块加载器 ----------
window.ModuleLoader = {
loadModule: function(moduleName, containerId) {
console.log(`[loader] 加载模块: ${moduleName}`);
const module = ModuleRegistry.get(moduleName);
if (!module) {
console.error(`[loader] 模块 ${moduleName} 未注册`);
return;
}
const container = document.getElementById(containerId);
if (!container) {
console.error(`[loader] 容器 ${containerId} 不存在`);
return;
}
if (module.init && typeof module.init === 'function') {
module.init(container);
} else {
container.innerHTML = `<div>模块 ${moduleName} 内容</div>`;
}
ModuleLifecycle.onLoad(moduleName);
},
unloadModule: function(moduleName) {
console.log(`[loader] 卸载模块: ${moduleName}`);
const module = ModuleRegistry.get(moduleName);
if (module && module.destroy && typeof module.destroy === 'function') {
module.destroy();
}
ModuleLifecycle.onUnload(moduleName);
}
};
// ---------- 路由 ----------
window.ChannelRouter = {
currentChannel: 'home',
contentEl: null,
init: function(contentElement) {
this.contentEl = contentElement;
console.log('[router] 初始化');
const savedState = ChannelState.restoreState();
if (savedState && savedState.currentChannel) {
this.currentChannel = savedState.currentChannel;
}
this.loadChannel(this.currentChannel);
if (savedState && savedState.visited) {
savedState.visited.forEach(channel => {
document.querySelectorAll('.channel-btn').forEach(btn => {
if (btn.dataset.channel === channel) btn.classList.add('visited');
});
});
}
},
navigateTo: function(channel) {
if (channel === this.currentChannel) return;
console.log(`[router] 导航到: ${channel}`);
this.contentEl.classList.add('fade-out');
setTimeout(() => {
this.loadChannel(channel);
this.contentEl.classList.remove('fade-out');
this.contentEl.classList.add('fade-in');
setTimeout(() => { this.contentEl.classList.remove('fade-in'); }, 300);
}, 300);
this.currentChannel = channel;
document.querySelectorAll('.channel-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.channel === channel) btn.classList.add('active');
});
ChannelState.saveState({ currentChannel: channel, visited: this.getVisitedChannels() });
},
loadChannel: function(channel) {
if (!this.contentEl) return;
let content = '';
switch(channel) {
case 'home':
content = '<h2>🏠 首页</h2><p>欢迎来到光湖频道</p>';
break;
case 'module-a':
content = '<h2>📦 模块A</h2><button id="sendMsgBtn">发送消息给模块B</button><div id="msgDisplay"></div>';
break;
case 'module-b':
content = '<h2>📦 模块B</h2><div id="receivedMsg">等待消息...</div>';
break;
case 'module-c':
content = '<h2>📦 模块C</h2><p>这是模块C的内容</p>';
break;
case 'debug':
this.loadDebugPanel();
return;
default:
content = '<h2>未知频道</h2>';
}
this.contentEl.innerHTML = content;
ModuleLifecycle.onLoad(channel);
if (channel === 'module-a') {
setTimeout(() => {
const btn = document.getElementById('sendMsgBtn');
if (btn) {
btn.addEventListener('click', () => {
EventBus.emit('module-a:message', { text: '你好模块B', from: '模块A' });
document.getElementById('msgDisplay').innerHTML = '✅ 消息已发送';
});
}
}, 100);
}
if (channel === 'module-b') {
this.subscribeModuleB();
}
},
loadDebugPanel: function() {
// 调试面板直接用内嵌 HTML避免 fetch 问题
this.contentEl.innerHTML = `
<div class="debug-panel">
<h2>🐞 调试面板</h2>
<div class="module-list"><strong>当前活跃模块:</strong> <span id="active-modules">加载中...</span></div>
<h3>事件总线聊天记录</h3>
<ul class="message-list" id="debug-messages" style="border:1px solid #ccc; padding:10px; height:200px; overflow-y:auto; background:#f9f9f9;"></ul>
<div style="display:flex; gap:10px; margin:10px 0;">
<input type="text" id="debug-input" placeholder="输入要发送的消息..." style="flex:1; padding:8px;">
<button id="debug-send" style="padding:8px 16px; background:#3b82f6; color:white; border:none; border-radius:4px;">发送测试消息</button>
</div>
<div style="display:flex; gap:10px;">
<button id="debug-clear" style="padding:8px 16px; background:#ef4444; color:white; border:none; border-radius:4px;">清除状态</button>
<button id="debug-refresh" onclick="location.reload()" style="padding:8px 16px; background:#6b7280; color:white; border:none; border-radius:4px;">刷新页面</button>
</div>
<p><small>消息会广播给所有订阅的模块</small></p>
</div>
`;
ModuleLifecycle.onLoad('debug');
this.initDebugPanel();
},
initDebugPanel: function() {
const msgList = document.getElementById('debug-messages');
if (msgList && window.debugMessages) {
window.debugMessages.forEach(msg => {
const li = document.createElement('li');
li.textContent = `[${msg.time}] ${msg.event}: ${JSON.stringify(msg.data)}`;
msgList.appendChild(li);
});
}
const sendBtn = document.getElementById('debug-send');
const input = document.getElementById('debug-input');
if (sendBtn && input) {
sendBtn.addEventListener('click', () => {
const text = input.value.trim();
if (text) {
EventBus.emit('debug:message', { text, from: '调试面板' });
input.value = '';
}
});
}
const clearBtn = document.getElementById('debug-clear');
if (clearBtn) {
clearBtn.addEventListener('click', () => {
ChannelState.clearState();
alert('状态已清除,刷新页面生效');
});
}
// 更新活跃模块
const updateActive = () => {
const span = document.getElementById('active-modules');
if (span) {
const modules = ModuleLifecycle.getActiveModules();
span.textContent = modules.length ? modules.join('、') : '无';
}
};
EventBus.on('module:loaded', updateActive);
EventBus.on('module:unloaded', updateActive);
updateActive();
},
subscribeModuleB: function() {
const handleMessage = (data) => {
const display = document.getElementById('receivedMsg');
if (display) {
display.innerHTML = `📩 收到消息: ${data.text} (来自 ${data.from})`;
}
};
EventBus.off('module-a:message');
EventBus.on('module-a:message', handleMessage);
},
getVisitedChannels: function() {
const visited = [];
document.querySelectorAll('.channel-btn.visited').forEach(btn => visited.push(btn.dataset.channel));
return visited;
}
};
// ---------- 应用启动 ----------
console.log('[app] 启动中...');
document.addEventListener('DOMContentLoaded', function() {
console.log('[app] DOM 已加载');
const contentEl = document.getElementById('channel-content');
if (!contentEl) {
console.error('[app] 找不到 #channel-content');
return;
}
if (window.ChannelRouter) {
ChannelRouter.init(contentEl);
}
document.querySelectorAll('.channel-btn').forEach(btn => {
btn.addEventListener('click', function() {
const channel = this.dataset.channel;
if (channel && window.ChannelRouter) {
ChannelRouter.navigateTo(channel);
this.classList.add('visited');
ChannelState.markVisited(channel);
}
});
});
});
// 拦截事件总线消息用于调试
const originalEmit = EventBus.emit;
EventBus.emit = function(event, data) {
if (!window.debugMessages) window.debugMessages = [];
window.debugMessages.push({ event, data, time: new Date().toLocaleTimeString() });
originalEmit.call(this, event, data);
};
console.log('[app] 事件总线拦截器已安装');
</script>
</body>
</html>