guanghulab/modules/m-channel/all-in-one.html
2026-05-10 13:12:44 +08:00

397 lines
17 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>