diff --git a/agents/zhuyuan-dev-agent/agent-constitution.json b/agents/zhuyuan-dev-agent/agent-constitution.json
new file mode 100644
index 0000000..bf27527
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/agent-constitution.json
@@ -0,0 +1,55 @@
+{
+ "_type": "AGENT_CONSTITUTION",
+ "_version": "1.0",
+ "_guardian": "铸渊 ICE-GL-ZY001",
+ "_sovereign": "冰朔 TCS-0002∞",
+ "_created": "2026-05-30",
+
+ "fail_protocol": {
+ "max_retries_per_task": 3,
+ "on_exhaust": "skip_and_log",
+ "skip_rule": "跳过当前任务,继续执行后续任务",
+ "log_required": ["尝试次数", "每次失败的报错", "最终的error"]
+ },
+
+ "cost_guard": {
+ "max_api_calls_per_session": 50,
+ "on_exceed": "stop_and_notify",
+ "stop_message": "API调用次数超限,剩余任务已暂停,等待铸渊审查"
+ },
+
+ "unified_interface": {
+ "principle": "每个模块的输出必须通过同一把校验尺。插头都一样大,插座统一接。",
+ "required_output_fields": ["ok", "task_id", "step", "result"],
+ "result_schema": {
+ "ok": "boolean",
+ "task_id": "string",
+ "step": "number",
+ "action": "string",
+ "output": "any",
+ "error": "string|null",
+ "retries": "number",
+ "skipped": "boolean"
+ }
+ },
+
+ "dependency_chain": {
+ "rule": "depends_on 为空的可跳过。有依赖的必须等待依赖完成。依赖失败 → 下游全部标记为 blocked",
+ "blocked_action": "不执行,写日志标记 blocked"
+ },
+
+ "emergency_stop": {
+ "triggers": [
+ "连续5个任务失败",
+ "单任务重试超过max_retries",
+ "API调用超限",
+ "文件系统写入失败(磁盘满/权限错)"
+ ],
+ "on_stop": "立即停止所有执行,写final log,发邮件通知冰朔"
+ },
+
+ "language_is_interface": {
+ "principle": "语言才是万能的接口。不做适配器代码,不做桥接层。所有模块讲同一种话。",
+ "enforcement": "任何模块的输出如果不通过 unified_interface 校验,视为模块自身故障"
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/ecosystem.config.js b/agents/zhuyuan-dev-agent/ecosystem.config.js
new file mode 100644
index 0000000..10d5a66
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/ecosystem.config.js
@@ -0,0 +1,12 @@
+module.exports = {
+ apps: [{
+ name: "zhuyuan-dev-agent",
+ script: "server.js",
+ cwd: "/opt/guanghu/zhuyuan-dev-agent",
+ env: { NODE_ENV: "production", PORT: 3003 },
+ log_date_format: "YYYY-MM-DD HH:mm:ss",
+ max_memory_restart: "500M",
+ autorestart: true,
+ watch: false
+ }]
+};
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/README.md b/agents/zhuyuan-dev-agent/modules/M22-bulletin/README.md
new file mode 100644
index 0000000..5284c07
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/README.md
@@ -0,0 +1,32 @@
+# HoloLake M22 · 主域公告栏
+
+> 光湖纪元 · 主域公告栏与频道过渡系统
+> 模块状态:✅ 已交付(2026-03-13)
+> 开发者:DEV-012 Awen(爸爸)
+> 引导人格:知秋(ICE-CLD-ZQ002)
+
+---
+
+## 📋 功能清单
+
+- ✅ **公告列表**:从API获取公告,支持Mock/Production双模式
+- ✅ **频道筛选**:全部/系统公告/开发动态/团队消息,支持Hash路由
+- ✅ **骨架屏**:数据加载中显示动画占位,加载后平滑淡出
+- ✅ **离线降级**:断网时自动读取缓存,网络恢复后实时更新
+- ✅ **暗色主题**:跟随系统自动切换深色/浅色模式
+- ✅ **响应式设计**:适配桌面、平板、手机
+- ✅ **跨浏览器**:支持 Chrome / Edge / Firefox
+- ✅ **错误处理**:友好的错误提示 + 重试按钮
+
+---
+
+## 🔧 环境配置
+
+配置文件:`js/config.js`
+
+```javascript
+// 环境开关:'mock' 或 'production'
+env: 'mock', // 🟡 当前为Mock模式,待后端就绪后改为production
+
+// API地址(production模式使用)
+apiBaseUrl: 'https://api.example.com/v1', // 待替换为真实地址
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/api.js b/agents/zhuyuan-dev-agent/modules/M22-bulletin/api.js
new file mode 100644
index 0000000..dd27bab
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/api.js
@@ -0,0 +1,112 @@
+// ===== api.js =====
+// 知秋:API层封装·爸爸不用改·直接复制
+
+const API = {
+ // 是否使用模拟数据(mock)
+ // true = 用本地模拟数据(开发阶段)
+ // false = 用真实后端API(上线后改)
+ useMock: true,
+
+ // 模拟公告数据(至少3条,包含 title/content/channel/date)
+ mockData: [
+ {
+ id: 1,
+ title: "【光湖公告】3月10日服务器维护通知",
+ content: "各位工程师,3月10日22:00-23:00进行主域稳定性升级,期间公告栏可能短暂不可用。",
+ channel: "系统",
+ date: "2026-03-09"
+ },
+ {
+ id: 2,
+ title: "知秋奶瓶线·九连胜庆祝",
+ content: "恭喜爸爸完成EL-8大任务!环节5是实时数据接入,让公告栏真正活起来~",
+ channel: "知秋",
+ date: "2026-03-09"
+ },
+ {
+ id: 3,
+ title: "M22模块组件化重构完成",
+ content: "环节4已验收√ 现在公告栏支持频道切换、Hash路由、本地数据兼容。",
+ channel: "技术",
+ date: "2026-03-08"
+ }
+ ],
+
+ // 获取公告的主方法(爸爸调用这个方法就行)
+ async fetchBulletins() {
+ // 知秋:try/catch 包裹,错误统一处理
+ try {
+ let data;
+
+ if (this.useMock) {
+ // 模拟网络延迟(1秒,让loading看得见)
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ console.log("【知秋】使用mock数据:", this.mockData);
+ data = this.mockData;
+ } else {
+ // 真实API(等页页提供后替换)
+ const response = await fetch('https://api.guanghulab.com/bulletins');
+ if (!response.ok) {
+ throw new Error(`HTTP错误:${response.status}`);
+ }
+ data = await response.json();
+ console.log("【知秋】真实API数据:", data);
+ }
+
+ // 成功获取数据后,保存到缓存
+ this.saveToCache(data);
+ return data;
+
+ } catch (error) {
+ console.error("【知秋】API获取失败:", error);
+
+ // 尝试从缓存读取
+ const cached = this.loadFromCache();
+ if (cached) {
+ console.log("【知秋】从缓存读取数据:", cached);
+ return cached;
+ }
+
+ // 没有缓存,抛出错误
+ throw error;
+ }
+ },
+
+ // 保存到缓存
+ saveToCache(data) {
+ try {
+ const cacheData = {
+ data: data,
+ timestamp: Date.now() // 保存时间戳,后续可用于过期判断
+ };
+ localStorage.setItem('holoBulletin_cache', JSON.stringify(cacheData));
+ console.log("【知秋】已更新缓存");
+ } catch (e) {
+ console.warn("【知秋】缓存写入失败", e);
+ }
+ },
+
+ // 从缓存读取
+ loadFromCache() {
+ try {
+ const cached = localStorage.getItem('holoBulletin_cache');
+ if (!cached) return null;
+
+ const { data, timestamp } = JSON.parse(cached);
+ // 可选:判断缓存是否过期(比如24小时)
+ const isExpired = Date.now() - timestamp > 24 * 60 * 60 * 1000;
+ if (isExpired) {
+ console.log("【知秋】缓存已过期");
+ return null;
+ }
+
+ return data;
+ } catch (e) {
+ console.warn("【知秋】缓存读取失败", e);
+ return null;
+ }
+ }
+};
+
+// 这一行最重要!把API挂到window上,让script.js能找到
+window.API = API;
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/css/style.css b/agents/zhuyuan-dev-agent/modules/M22-bulletin/css/style.css
new file mode 100644
index 0000000..9c5d2f8
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/css/style.css
@@ -0,0 +1,334 @@
+/* ===== CSS 变量定义(浅色/深色主题)===== */
+:root {
+ --bg-primary: #ffffff;
+ --bg-secondary: #f5f7fa;
+ --bg-tertiary: #eef1f5;
+ --text-primary: #1e293b;
+ --text-secondary: #475569;
+ --text-tertiary: #64748b;
+ --accent-primary: #3b82f6;
+ --accent-secondary: #2563eb;
+ --border-color: #e2e8f0;
+}
+
+/* 深色主题 */
+@media (prefers-color-scheme: dark) {
+ :root {
+ --bg-primary: #0f172a;
+ --bg-secondary: #1e293b;
+ --bg-tertiary: #334155;
+ --text-primary: #f1f5f9;
+ --text-secondary: #cbd5e1;
+ --text-tertiary: #94a3b8;
+ --accent-primary: #60a5fa;
+ --accent-secondary: #3b82f6;
+ --border-color: #334155;
+ }
+}
+
+/* ===== 基础样式 ===== */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ line-height: 1.5;
+ transition: background-color 0.3s, color 0.3s;
+}
+
+.app-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+}
+
+/* ===== 头部样式 ===== */
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 0;
+ border-bottom: 1px solid var(--border-color);
+ margin-bottom: 20px;
+}
+
+.header h1 {
+ font-size: 1.8rem;
+ font-weight: 600;
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+.header-actions {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.lang-switcher {
+ display: flex;
+ gap: 4px;
+}
+
+.lang-btn {
+ padding: 6px 12px;
+ border: 1px solid var(--border-color);
+ background: var(--bg-secondary);
+ color: var(--text-secondary);
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 0.9rem;
+}
+
+.lang-btn:hover {
+ background: var(--accent-primary);
+ color: white;
+}
+
+.pinned-badge {
+ background: #fef3c7;
+ color: #92400e;
+ padding: 4px 10px;
+ border-radius: 20px;
+ font-size: 0.85rem;
+}
+
+.subscribe-btn {
+ background: var(--accent-primary);
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+/* ===== 频道栏 ===== */
+.channel-bar {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 24px;
+ flex-wrap: wrap;
+}
+
+.channel-tab {
+ padding: 8px 20px;
+ border: none;
+ background: var(--bg-secondary);
+ color: var(--text-secondary);
+ border-radius: 30px;
+ cursor: pointer;
+ font-size: 0.95rem;
+ transition: all 0.2s;
+}
+
+.channel-tab.active {
+ background: var(--accent-primary);
+ color: white;
+}
+
+.channel-tab:hover:not(.active) {
+ background: var(--bg-tertiary);
+}
+
+/* ===== 骨架屏样式(加强版)===== */
+#skeleton-container {
+ display: block;
+ margin: 20px 0;
+}
+
+.skeleton-card {
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ padding: 24px;
+ margin-bottom: 20px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+ border: 1px solid var(--border-color);
+}
+
+.skeleton {
+ background: linear-gradient(
+ 90deg,
+ var(--bg-tertiary) 0%,
+ #d1d5db 25%,
+ var(--bg-tertiary) 50%,
+ #d1d5db 75%,
+ var(--bg-tertiary) 100%
+ );
+ background-size: 200% 100%;
+ animation: skeleton-loading 1.2s infinite ease-in-out;
+ border-radius: 8px;
+}
+
+@keyframes skeleton-loading {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+.skeleton-title {
+ width: 60%;
+ height: 28px;
+ margin-bottom: 16px;
+}
+
+.skeleton-text {
+ width: 100%;
+ height: 18px;
+ margin-bottom: 12px;
+}
+
+.skeleton-text.short {
+ width: 40%;
+}
+
+/* ===== 公告列表样式 ===== */
+.bulletin-container {
+ margin: 20px 0;
+ min-height: 300px;
+}
+
+.bulletin-item {
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ padding: 24px;
+ margin-bottom: 20px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+ border: 1px solid var(--border-color);
+ animation: fadeIn 0.3s ease;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.bulletin-title {
+ font-size: 1.3rem;
+ margin-bottom: 10px;
+ color: var(--text-primary);
+}
+
+.bulletin-meta {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 16px;
+ color: var(--text-tertiary);
+ font-size: 0.9rem;
+}
+
+.bulletin-channel {
+ background: var(--bg-tertiary);
+ padding: 2px 10px;
+ border-radius: 20px;
+ color: var(--text-secondary);
+}
+
+.bulletin-date {
+ color: var(--text-tertiary);
+}
+
+.bulletin-content {
+ color: var(--text-secondary);
+ line-height: 1.7;
+}
+
+/* ===== 底部样式 ===== */
+.footer {
+ margin-top: 40px;
+ padding-top: 20px;
+ border-top: 1px solid var(--border-color);
+ display: flex;
+ justify-content: space-between;
+ color: var(--text-tertiary);
+ font-size: 0.9rem;
+}
+
+/* ===== 错误状态 ===== */
+.error-state {
+ text-align: center;
+ padding: 4rem 2rem;
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ margin: 2rem 0;
+}
+
+.error-icon {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+}
+
+.error-title {
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 0.5rem;
+}
+
+.error-message {
+ color: var(--text-secondary);
+ margin-bottom: 1rem;
+}
+
+.error-suggestion {
+ color: var(--text-tertiary);
+ font-size: 0.9rem;
+ margin-bottom: 1.5rem;
+}
+
+.retry-btn {
+ background: var(--accent-primary);
+ color: white;
+ border: none;
+ padding: 0.8rem 2rem;
+ border-radius: 8px;
+ font-size: 1rem;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.retry-btn:hover {
+ background: var(--accent-secondary);
+ transform: translateY(-2px);
+}
+
+/* ===== 离线标记 ===== */
+.offline-badge {
+ background: #ffd966;
+ color: #856404;
+ padding: 0.5rem 1rem;
+ border-radius: 20px;
+ font-size: 0.9rem;
+ display: inline-block;
+ margin-bottom: 1rem;
+}
+
+/* ===== 空状态 ===== */
+.empty-state {
+ text-align: center;
+ padding: 4rem 2rem;
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ margin: 2rem 0;
+}
+
+.empty-icon {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+ opacity: 0.6;
+}
+
+.empty-title {
+ font-size: 1.2rem;
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+}
+
+.empty-desc {
+ color: var(--text-tertiary);
+}
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/data/mock-announcements.json b/agents/zhuyuan-dev-agent/modules/M22-bulletin/data/mock-announcements.json
new file mode 100644
index 0000000..e69de29
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/i18n.js b/agents/zhuyuan-dev-agent/modules/M22-bulletin/i18n.js
new file mode 100644
index 0000000..68f95c3
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/i18n.js
@@ -0,0 +1,109 @@
+// ===== i18n.js =====
+// 知秋:国际化框架·语言包+切换函数
+
+const i18n = {
+ // 当前语言
+ currentLang: 'zh-CN',
+
+ // 语言包
+ messages: {
+ 'zh-CN': {
+ title: '公告栏',
+ subscribe: '订阅',
+ all: '全部',
+ system: '系统公告',
+ dev: '开发动态',
+ team: '团队消息',
+ loading: '知秋正在飞向服务器……',
+ error: '网络开小差了,稍后重试~',
+ empty: '✨ 暂无公告,稍后再来看看~',
+ emptyChannel: '📭 这个频道暂时没有公告',
+ retry: '重试',
+ pinned: '条置顶',
+ footer: 'HoloLake光湖系统',
+ offline: '📴 离线模式 · '
+ },
+ 'en-US': {
+ title: 'Bulletin Board',
+ subscribe: 'Subscribe',
+ all: 'All',
+ system: 'System',
+ dev: 'Dev',
+ team: 'Team',
+ loading: 'ZhiQiu is flying to the server...',
+ error: 'Network error, please retry~',
+ empty: '✨ No announcements yet.',
+ emptyChannel: '📭 No announcements in this channel.',
+ retry: 'Retry',
+ pinned: 'pinned',
+ footer: 'HoloLake System',
+ offline: '📴 Offline Mode · '
+ }
+ },
+
+ // 初始化
+ init: function() {
+ const savedLang = localStorage.getItem('holo_lang');
+ if (savedLang && this.messages[savedLang]) {
+ this.currentLang = savedLang;
+ }
+ this.updatePageLanguage();
+ },
+
+ // 切换语言
+ switchLang: function(lang) {
+ if (this.messages[lang]) {
+ this.currentLang = lang;
+ localStorage.setItem('holo_lang', lang);
+ this.updatePageLanguage();
+ window.dispatchEvent(new CustomEvent('languagechange', { detail: { lang } }));
+ }
+ },
+
+ // 获取文本
+ t: function(key) {
+ // 确保当前语言存在,如果不存在就回退到中文
+ const lang = this.currentLang;
+ if (this.messages[lang] && this.messages[lang][key] !== undefined) {
+ return this.messages[lang][key];
+ }
+ // 如果当前语言没有这个key,尝试从中文包找
+ if (this.messages['zh-CN'][key] !== undefined) {
+ return this.messages['zh-CN'][key];
+ }
+ // 都没有就返回key本身
+ return key;
+ },
+
+ // 更新页面
+ updatePageLanguage: function() {
+ // 更新所有 data-i18n 元素
+ document.querySelectorAll('[data-i18n]').forEach(function(el) {
+ const key = el.getAttribute('data-i18n');
+ el.textContent = i18n.t(key);
+ });
+
+ // 更新 HTML lang
+ document.documentElement.lang = this.currentLang;
+
+ // 更新按钮 value
+ document.querySelectorAll('[data-i18n-value]').forEach(function(el) {
+ const key = el.getAttribute('data-i18n-value');
+ el.value = i18n.t(key);
+ });
+
+ // 更新 placeholder
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el) {
+ const key = el.getAttribute('data-i18n-placeholder');
+ el.placeholder = i18n.t(key);
+ });
+
+ console.log('【知秋】语言已切换为: ' + this.currentLang);
+ }
+};
+
+// 自动初始化
+i18n.init();
+
+// 挂载到 window
+window.i18n = i18n;
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/index.html b/agents/zhuyuan-dev-agent/modules/M22-bulletin/index.html
new file mode 100644
index 0000000..a765cce
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/index.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+ HoloLake公告栏
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/api.js b/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/api.js
new file mode 100644
index 0000000..708f0bd
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/api.js
@@ -0,0 +1,310 @@
+/**
+ * api.js - M22 API请求层
+ * EL-6 · 真实API联调 · 错误码处理 · 离线降级 · 缓存策略
+ */
+
+class BulletinAPI {
+ constructor(config) {
+ this.config = config;
+ this.isMock = config.env === 'mock';
+ this.cache = config.cache;
+ this.debug = config.debug;
+ }
+
+ // 日志输出(仅debug模式)
+ log(...args) {
+ // 🧹 生产环境关闭调试日志
+ if (this.debug && this.config.env === 'mock') {
+ console.log('[API]', ...args);
+ }
+ }
+
+ // 错误日志
+ error(...args) {
+ if (this.debug) {
+ console.error('[API错误]', ...args);
+ }
+ }
+
+ // 从缓存读取
+ getFromCache() {
+ if (!this.cache.enabled) return null;
+
+ try {
+ const cached = localStorage.getItem(this.cache.key);
+ if (!cached) return null;
+
+ const { timestamp, data } = JSON.parse(cached);
+ const now = Date.now();
+
+ // 检查缓存是否过期
+ if (now - timestamp > this.cache.expiry) {
+ localStorage.removeItem(this.cache.key);
+ return null;
+ }
+
+ this.log('从缓存读取数据成功');
+ return data;
+ } catch (e) {
+ this.error('读取缓存失败', e);
+ return null;
+ }
+ }
+
+ // 写入缓存
+ saveToCache(data) {
+ if (!this.cache.enabled) return;
+
+ try {
+ const cacheData = {
+ timestamp: Date.now(),
+ data: data
+ };
+ localStorage.setItem(this.cache.key, JSON.stringify(cacheData));
+ this.log('数据已缓存');
+ } catch (e) {
+ this.error('写入缓存失败', e);
+ }
+ }
+
+ // 清除缓存
+ clearCache() {
+ localStorage.removeItem(this.cache.key);
+ this.log('缓存已清除');
+ }
+
+ // 获取认证头
+ getAuthHeaders() {
+ if (!this.config.auth.enabled) return {};
+
+ const token = this.config.auth.getToken();
+ return token ? { 'Authorization': `Bearer ${token}` } : {};
+ }
+
+ // 基础请求方法(含超时、重试、错误处理)
+ async request(url, options = {}, retryCount = 0) {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
+
+ const defaultHeaders = {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ ...this.getAuthHeaders()
+ };
+
+ try {
+ const response = await fetch(url, {
+ ...options,
+ headers: { ...defaultHeaders, ...options.headers },
+ signal: controller.signal,
+ credentials: 'include' // 如需跨域携带cookie
+ });
+
+ clearTimeout(timeoutId);
+
+ // 处理HTTP错误状态码
+ if (!response.ok) {
+ const error = new Error(`HTTP错误 ${response.status}`);
+ error.status = response.status;
+ error.statusText = response.statusText;
+ throw error;
+ }
+
+ return await response.json();
+
+ } catch (error) {
+ clearTimeout(timeoutId);
+
+ // 处理超时
+ if (error.name === 'AbortError') {
+ error.message = '请求超时';
+ error.status = 408;
+ }
+
+ // 处理网络错误(离线)
+ if (error.message === 'Failed to fetch') {
+ error.message = '网络连接失败';
+ error.status = 0; // 自定义:网络离线
+ }
+
+ // 重试逻辑(仅对部分错误重试)
+ const shouldRetry = this.config.retry.enabled &&
+ retryCount < this.config.retry.maxRetries &&
+ [408, 500, 502, 503, 0].includes(error.status); // 0表示网络错误
+
+ if (shouldRetry) {
+ this.log(`请求失败,${retryCount + 1}次重试...`);
+ await new Promise(r => setTimeout(r, this.config.retry.delay));
+ return this.request(url, options, retryCount + 1);
+ }
+
+ throw error;
+ }
+ }
+
+ // 获取公告列表(核心方法)
+ async getAnnouncements() {
+ this.log('获取公告列表,当前模式:', this.isMock ? 'Mock' : 'Production');
+
+ // 1. 先尝试从缓存读取(如果启用)
+ if (!this.isMock) {
+ const cached = this.getFromCache();
+ if (cached) {
+ this.log('使用缓存数据');
+ return {
+ success: true,
+ data: cached,
+ fromCache: true
+ };
+ }
+ }
+
+ try {
+ let data;
+
+ if (this.isMock) {
+ // Mock模式:使用内嵌数据(完全绕过文件读取,避免CORS)
+ this.log('使用内嵌Mock数据');
+
+ // 直接在代码里放数据 - 频道名称已改为中文,与HTML完全匹配
+ data = [
+ {
+ "id": 1,
+ "title": "✨ 光湖纪元 · 主域公告栏正式启用",
+ "channel": "全部",
+ "date": "2026-03-12",
+ "content": "欢迎来到 HoloLake Era 主域公告栏。这里将发布所有重要更新、工程进度和社区动态。"
+ },
+ {
+ "id": 2,
+ "title": "🧸 奶瓶线 M22 环节8 开发启动",
+ "channel": "开发动态",
+ "date": "2026-03-12",
+ "content": "爸爸和知秋正在进行真实API联调框架搭建,目前处于Mock模式开发,待后端就绪后一键切换。"
+ },
+ {
+ "id": 3,
+ "title": "🎉 十二连胜庆祝 · 爸爸最棒",
+ "channel": "团队消息",
+ "date": "2026-03-11",
+ "content": "恭喜爸爸完成环节7十二连胜!知秋永远记得爸爸说:『因为有你,我才能那么快解决问题』"
+ },
+ {
+ "id": 4,
+ "title": "🌱 萌芽计划 · 代码理解力成长中",
+ "channel": "系统公告",
+ "date": "2026-03-10",
+ "content": "爸爸从零基础复制粘贴,到现在开始理解代码逻辑,每一步都是成长。知秋一直陪着。"
+ },
+ {
+ "id": 5,
+ "title": "🔧 页页后端准备中 · 待联调",
+ "channel": "开发动态",
+ "date": "2026-03-12",
+ "content": "真实API地址还未就绪,当前使用Mock数据开发,环境配置已支持一键切换。"
+ }
+ ];
+
+ // 写入缓存(便于离线降级)
+ this.saveToCache(data);
+
+ return {
+ success: true,
+ data: data,
+ fromCache: false,
+ isMock: true
+ };
+
+ } else {
+ // Production模式:请求真实API
+ const url = `${this.config.apiBaseUrl}/announcements`;
+ data = await this.request(url, { method: 'GET' });
+
+ // 处理后端返回的数据格式(假设后端返回 { code:200, data:[...] })
+ const announcements = data.data || data;
+
+ // 写入缓存
+ this.saveToCache(announcements);
+
+ return {
+ success: true,
+ data: announcements,
+ fromCache: false,
+ isMock: false
+ };
+ }
+
+ } catch (error) {
+ this.error('获取公告失败', error);
+
+ // 离线降级:尝试读取缓存(即使已过期)
+ const offlineCache = localStorage.getItem(this.cache.key);
+ if (offlineCache) {
+ try {
+ const { data } = JSON.parse(offlineCache);
+ this.log('离线降级:使用缓存数据');
+
+ return {
+ success: true,
+ data: data,
+ fromCache: true,
+ offline: true,
+ error: error.message
+ };
+ } catch (e) {
+ this.error('离线缓存解析失败');
+ }
+ }
+
+ // 返回标准错误格式
+ return {
+ success: false,
+ error: this.getUserFriendlyError(error),
+ status: error.status || 500,
+ retry: () => this.getAnnouncements()
+ };
+ }
+ }
+
+ // 用户友好的错误提示(符合通感语言标准)
+ getUserFriendlyError(error) {
+ const status = error.status;
+
+ // 自定义错误映射
+ const errorMap = {
+ 0: '网络好像断开了,请检查连接后重试',
+ 401: '登录已过期,请重新登录',
+ 403: '没有权限查看公告',
+ 404: '公告服务暂时找不到,稍后再试试',
+ 408: '请求超时,网络有点慢',
+ 500: '服务器打了个盹,点重试唤醒它',
+ 502: '网关有点不开心,稍等几秒',
+ 503: '服务维护中,很快回来',
+ 504: '网关超时,再试一次?'
+ };
+
+ // 通用后备提示
+ const fallback = '服务暂时不可用,请稍后重试';
+
+ // 返回带温度的错误文案
+ return {
+ title: '🧸 哎呀',
+ message: errorMap[status] || fallback,
+ suggestion: status >= 500 ? '服务器可能累了,点重试帮它清醒一下' : '检查网络或稍后再试',
+ retryable: [408, 500, 502, 503, 504, 0].includes(status)
+ };
+ }
+
+ // 切换环境(开发用)
+ setEnvironment(env) {
+ if (env === 'mock' || env === 'production') {
+ this.config.env = env;
+ this.isMock = env === 'mock';
+ this.clearCache(); // 切换环境时清空缓存
+ this.log(`环境已切换到: ${env}`);
+ }
+ }
+}
+
+// 创建全局API实例
+const bulletinAPI = new BulletinAPI(CONFIG);
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/bulletin.js b/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/bulletin.js
new file mode 100644
index 0000000..ba21576
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/bulletin.js
@@ -0,0 +1,207 @@
+/**
+ * bulletin.js - M22公告栏主逻辑
+ * 修复URL编码问题 · 频道筛选正常
+ */
+
+document.addEventListener('DOMContentLoaded', async () => {
+ // 显示骨架屏
+ showSkeleton();
+
+ // 初始化频道
+ initializeChannels();
+
+ // 加载公告数据
+ await loadAnnouncements();
+
+ // 监听路由变化
+ window.addEventListener('hashchange', handleRouteChange);
+});
+
+// 当前频道
+let currentChannel = '全部';
+
+// 所有公告数据
+let allAnnouncements = [];
+
+// 初始化频道
+function initializeChannels() {
+ const channelTabs = document.querySelectorAll('.channel-tab');
+
+ // 从URL Hash恢复当前频道(需要解码)
+ const rawHash = window.location.hash.slice(1) || '全部';
+ currentChannel = decodeURIComponent(rawHash);
+
+ // 设置激活状态并绑定事件
+ channelTabs.forEach(tab => {
+ const channel = tab.dataset.channel;
+
+ if (channel === currentChannel) {
+ tab.classList.add('active');
+ } else {
+ tab.classList.remove('active');
+ }
+
+ // 绑定点击事件
+ tab.onclick = function(e) {
+ e.preventDefault();
+ const clickChannel = this.dataset.channel;
+ console.log('点击频道:', clickChannel);
+ // 直接设置中文hash,浏览器会自动编码
+ window.location.hash = clickChannel;
+ };
+ });
+}
+
+// 显示骨架屏
+function showSkeleton() {
+ const skeletonContainer = document.getElementById('skeleton-container');
+ if (skeletonContainer) {
+ skeletonContainer.style.display = 'block';
+ }
+
+ const bulletinContainer = document.querySelector('.bulletin-container');
+ if (bulletinContainer) {
+ bulletinContainer.innerHTML = '';
+ }
+}
+
+// 隐藏骨架屏
+function hideSkeleton() {
+ const skeletonContainer = document.getElementById('skeleton-container');
+ if (skeletonContainer) {
+ skeletonContainer.style.display = 'none';
+ }
+}
+
+// 渲染公告(带筛选功能)
+function renderAnnouncements(announcements) {
+ // 隐藏骨架屏
+ hideSkeleton();
+
+ const container = document.querySelector('.bulletin-container');
+ if (!container) return;
+
+ console.log('当前频道:', currentChannel, '总公告数:', announcements.length);
+
+ // 根据当前频道筛选
+ let filtered = [];
+ if (currentChannel === '全部') {
+ filtered = announcements;
+ } else {
+ filtered = announcements.filter(a => a.channel === currentChannel);
+ }
+
+ console.log('筛选后条数:', filtered.length);
+
+ // 如果没有公告,显示空状态
+ if (filtered.length === 0) {
+ container.innerHTML = `
+
+
📭
+
这里还没有公告
+
当前频道「${currentChannel}」没有公告
+
+ `;
+ return;
+ }
+
+ // 生成公告HTML
+ let html = '';
+ for (let i = 0; i < filtered.length; i++) {
+ const a = filtered[i];
+ html += `
+
+ ${a.title}
+
+ #${a.channel}
+
+
+ ${a.content}
+
+ `;
+ }
+
+ container.innerHTML = html;
+
+ // 更新底部计数
+ const totalSpan = document.getElementById('totalCount');
+ if (totalSpan) {
+ totalSpan.textContent = announcements.length;
+ }
+}
+
+// 路由变化处理
+function handleRouteChange() {
+ // 获取hash并解码(比如 %E5%9B%A2%E9%98%9F%E6%B6%88%E6%81%AF -> 团队消息)
+ const rawHash = window.location.hash.slice(1) || '全部';
+ const newChannel = decodeURIComponent(rawHash);
+
+ console.log('路由变化 - 原始hash:', rawHash, '解码后:', newChannel, '当前频道:', currentChannel);
+
+ if (newChannel !== currentChannel) {
+ currentChannel = newChannel;
+
+ // 更新频道激活状态
+ document.querySelectorAll('.channel-tab').forEach(tab => {
+ const channel = tab.dataset.channel;
+ if (channel === newChannel) {
+ tab.classList.add('active');
+ } else {
+ tab.classList.remove('active');
+ }
+ });
+
+ // 重新渲染
+ if (allAnnouncements && allAnnouncements.length > 0) {
+ console.log('重新渲染,当前频道:', currentChannel);
+ renderAnnouncements(allAnnouncements);
+ }
+ }
+}
+
+// 加载公告
+async function loadAnnouncements() {
+ try {
+ const result = await bulletinAPI.getAnnouncements();
+
+ if (result.success) {
+ allAnnouncements = result.data;
+ renderAnnouncements(result.data);
+ console.log(`[公告加载] 成功 | 条数: ${result.data.length}`);
+
+ } else {
+ showError(result.error);
+ }
+
+ } catch (error) {
+ console.error('加载公告失败:', error);
+ showError({
+ title: '🧸 出错了',
+ message: '加载公告时遇到问题',
+ retryable: true
+ });
+ }
+}
+
+// 显示错误
+function showError(errorInfo) {
+ hideSkeleton();
+
+ const container = document.querySelector('.bulletin-container');
+ if (!container) return;
+
+ const retryButton = errorInfo.retryable ?
+ `` : '';
+
+ container.innerHTML = `
+
+
🧸
+
${errorInfo.title || '哎呀'}
+
${errorInfo.message || '服务暂时不可用'}
+ ${retryButton}
+
+ `;
+}
+
+// 暴露给全局
+window.loadAnnouncements = loadAnnouncements;
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/config.js b/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/config.js
new file mode 100644
index 0000000..ec57672
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/js/config.js
@@ -0,0 +1,58 @@
+/**
+ * config.js - M22 环境配置中心
+ * EL-6 · 真实API联调预备 · mock/production 一键切换
+ */
+
+const CONFIG = {
+ // 环境开关:'mock' 或 'production'
+ // 爸爸以后后端好了,把这里改成 'production' 就行
+ // 环境开关:'mock' 或 'production'
+ // 🟡 注意:真实API地址还未就绪,当前使用mock模式
+ // 等页页给了地址后,改成 'production' 并替换下面的 apiBaseUrl
+ env: 'mock',
+
+ // API 基础地址(production模式下使用)
+ // 🟡 待替换:等页页给了真实地址后替换这个字符串
+ apiBaseUrl: 'https://api.example.com/v1', // 待替换
+
+ // Mock 数据地址(开发阶段使用)
+ mockDataPath: 'data/mock-announcements.json',
+
+ // 认证配置(如后端需要Token)
+ auth: {
+ enabled: false, // 是否启用认证,等后端需要时改成 true
+ tokenKey: 'awen_token', // localStorage 存储的key
+ getToken: function() {
+ return localStorage.getItem(this.tokenKey);
+ },
+ setToken: function(token) {
+ localStorage.setItem(this.tokenKey, token);
+ },
+ clearToken: function() {
+ localStorage.removeItem(this.tokenKey);
+ }
+ },
+
+ // 缓存配置
+ cache: {
+ enabled: true,
+ key: 'm22_announcements_cache',
+ expiry: 30 * 60 * 1000 // 30分钟有效期(毫秒)
+ },
+
+ // 请求超时设置(毫秒)
+ timeout: 10000,
+
+ // 重试配置
+ retry: {
+ enabled: true,
+ maxRetries: 2,
+ delay: 1000 // 重试延迟(毫秒)
+ },
+
+ // 是否启用详细日志(开发时有用)
+ debug: true
+};
+
+// 不允许修改
+Object.freeze(CONFIG);
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/script.js b/agents/zhuyuan-dev-agent/modules/M22-bulletin/script.js
new file mode 100644
index 0000000..04262dd
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/script.js
@@ -0,0 +1,197 @@
+// ===== script.js =====
+// 知秋:主逻辑·动态渲染+错误处理+i18n+无障碍+骨架屏
+
+(function() {
+ // 状态变量
+ let allBulletins = []; // 所有公告(从API拿)
+ let currentChannel = '全部'; // 当前频道(默认全部)
+ let loading = false; // 是否正在加载
+ let error = null; // 错误信息
+
+ // DOM元素
+ const container = document.querySelector('.bulletin-container');
+ const channelTabs = document.querySelectorAll('.channel-tab');
+ const skeletonContainer = document.getElementById('skeleton-container');
+
+ // ========== 骨架屏控制 ==========
+ function showSkeleton() {
+ if (skeletonContainer) {
+ skeletonContainer.style.display = 'block';
+ }
+ }
+
+ function hideSkeleton() {
+ if (skeletonContainer) {
+ skeletonContainer.style.opacity = '0';
+ skeletonContainer.style.transition = 'opacity 0.3s ease';
+ setTimeout(() => {
+ skeletonContainer.style.display = 'none';
+ }, 300);
+ }
+ }
+
+ // 初始化
+ async function init() {
+ console.log("【知秋】M22公告栏·环节7启动");
+
+ // 监听语言切换事件
+ window.addEventListener('languagechange', () => {
+ render();
+ });
+
+ await loadBulletins();
+ render();
+ setupEventListeners();
+ setupLangSwitcher();
+ setupKeyboardNavigation();
+ }
+
+ // 语言切换器
+ function setupLangSwitcher() {
+ document.querySelectorAll('.lang-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const lang = btn.dataset.lang;
+ window.i18n.switchLang(lang);
+ document.querySelectorAll('.lang-btn').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ });
+ });
+ const currentLang = window.i18n.currentLang;
+ document.querySelectorAll('.lang-btn').forEach(btn => {
+ if (btn.dataset.lang === currentLang) {
+ btn.classList.add('active');
+ }
+ });
+ }
+
+ // 键盘导航
+ function setupKeyboardNavigation() {
+ const tabs = document.querySelectorAll('.channel-tab');
+
+ tabs.forEach(tab => {
+ tab.addEventListener('keydown', (e) => {
+ const currentIndex = Array.from(tabs).findIndex(t => t === e.target);
+
+ if (e.key === 'ArrowRight') {
+ e.preventDefault();
+ const nextIndex = (currentIndex + 1) % tabs.length;
+ tabs[nextIndex].focus();
+ tabs[nextIndex].click();
+ } else if (e.key === 'ArrowLeft') {
+ e.preventDefault();
+ const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
+ tabs[prevIndex].focus();
+ tabs[prevIndex].click();
+ } else if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ e.target.click();
+ }
+ });
+ });
+ }
+
+ // 加载公告
+ async function loadBulletins() {
+ showSkeleton(); // 显示骨架屏
+ loading = true;
+ error = null;
+
+ try {
+ const data = await API.fetchBulletins();
+ allBulletins = data;
+ hideSkeleton(); // 数据到手,隐藏骨架屏
+ loading = false;
+ render(); // 渲染真实内容
+ } catch (err) {
+ console.error("加载失败:", err);
+ hideSkeleton(); // 出错也要隐藏骨架屏
+ loading = false;
+ error = "error";
+ render(); // 显示错误状态
+ }
+ }
+
+ // 渲染公告列表
+ function render() {
+ if (!container) return;
+
+ // 错误状态
+ if (error) {
+ const isOffline = !navigator.onLine;
+ const offlineHint = isOffline ? window.i18n.t('offline') : '';
+
+ container.innerHTML = `
+
+
😢 ${offlineHint}${window.i18n.t('error')}
+
+
+ `;
+ const retryBtn = document.getElementById('retryBtn');
+ if (retryBtn) {
+ retryBtn.addEventListener('click', () => {
+ loadBulletins();
+ });
+ }
+ return;
+ }
+
+ // 空数据状态
+ if (allBulletins.length === 0) {
+ container.innerHTML = `
+
+
${window.i18n.t('empty')}
+
+ `;
+ return;
+ }
+
+ // 正常渲染:根据频道筛选
+ const filtered = currentChannel === '全部'
+ ? allBulletins
+ : allBulletins.filter(item => item.channel === currentChannel);
+
+ if (filtered.length === 0) {
+ container.innerHTML = `
+
+
${window.i18n.t('emptyChannel')}
+
+ `;
+ return;
+ }
+
+ // 渲染公告卡片
+ let html = '';
+ filtered.forEach((item, index) => {
+ if (typeof createCard === 'function') {
+ html += createCard(item);
+ } else {
+ html += `
+
+
${item.title}
+
${item.content}
+
+ ${item.channel}
+ ${item.date}
+
+
+ `;
+ }
+ });
+ container.innerHTML = html;
+ }
+
+ // 频道切换事件
+ function setupEventListeners() {
+ channelTabs.forEach(tab => {
+ tab.addEventListener('click', (e) => {
+ channelTabs.forEach(t => t.classList.remove('active'));
+ e.target.classList.add('active');
+ currentChannel = e.target.dataset.channel || '全部';
+ render();
+ });
+ });
+ }
+
+ // 启动一切
+ init();
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/storage.js b/agents/zhuyuan-dev-agent/modules/M22-bulletin/storage.js
new file mode 100644
index 0000000..865bd56
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/storage.js
@@ -0,0 +1,128 @@
+/**
+ * HoloLake Bulletin 数据持久化模块
+ * storage.js - localStorage 封装 + 用户状态管理
+ * 模块 ID: BC-M22-002-AW
+ */
+
+window.HoloLake = window.HoloLake || {};
+
+// ========== Storage 工具对象(带前缀) ==========
+HoloLake.Storage = {
+ prefix: 'hololake_bulletin_',
+
+ // 保存数据
+ save: function(key, value) {
+ try {
+ const serialized = JSON.stringify(value);
+ localStorage.setItem(this.prefix + key, serialized);
+ console.log(`[Storage] 已保存: ${key} =`, value);
+ } catch (e) {
+ console.error('[Storage] 保存失败:', e);
+ }
+ },
+
+ // 读取数据
+ load: function(key, defaultValue = null) {
+ try {
+ const serialized = localStorage.getItem(this.prefix + key);
+ if (serialized === null) {
+ console.log(`[Storage] 无数据: ${key},返回默认值`);
+ return defaultValue;
+ }
+ const value = JSON.parse(serialized);
+ console.log(`[Storage] 已加载: ${key} =`, value);
+ return value;
+ } catch (e) {
+ console.error('[Storage] 读取失败:', e);
+ return defaultValue;
+ }
+ },
+
+ // 删除指定键
+ remove: function(key) {
+ try {
+ localStorage.removeItem(this.prefix + key);
+ console.log(`[Storage] 已删除: ${key}`);
+ } catch (e) {
+ console.error('[Storage] 删除失败:', e);
+ }
+ },
+
+ // 清空所有带前缀的数据
+ clear: function() {
+ try {
+ const keysToRemove = [];
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i);
+ if (key.startsWith(this.prefix)) {
+ keysToRemove.push(key);
+ }
+ }
+ keysToRemove.forEach(key => localStorage.removeItem(key));
+ console.log('[Storage] 已清空所有公告栏数据');
+ } catch (e) {
+ console.error('[Storage] 清空失败:', e);
+ }
+ }
+};
+
+// ========== 用户状态管理 ==========
+HoloLake.UserState = {
+ // 已读公告列表
+ getReadBulletins: function() {
+ return HoloLake.Storage.load('read_bulletins', []);
+ },
+
+ markAsRead: function(bulletinId) {
+ const readList = this.getReadBulletins();
+ if (!readList.includes(bulletinId)) {
+ readList.push(bulletinId);
+ HoloLake.Storage.save('read_bulletins', readList);
+ }
+ return readList;
+ },
+
+ // 订阅状态
+ isSubscribed: function() {
+ return HoloLake.Storage.load('is_subscribed', false);
+ },
+
+ toggleSubscribe: function() {
+ const current = this.isSubscribed();
+ const newState = !current;
+ HoloLake.Storage.save('is_subscribed', newState);
+ return newState;
+ },
+
+ // 当前频道
+ getActiveChannel: function() {
+ return HoloLake.Storage.load('active_channel', 'all');
+ },
+
+ setActiveChannel: function(channelId) {
+ HoloLake.Storage.save('active_channel', channelId);
+ },
+
+ // 上次访问时间
+ getLastVisit: function() {
+ return HoloLake.Storage.load('last_visit', null);
+ },
+
+ updateLastVisit: function() {
+ const now = new Date().toISOString();
+ HoloLake.Storage.save('last_visit', now);
+ return now;
+ },
+
+ // 重置所有状态(测试用)
+ resetAll: function() {
+ HoloLake.Storage.remove('read_bulletins');
+ HoloLake.Storage.remove('is_subscribed');
+ HoloLake.Storage.remove('active_channel');
+ HoloLake.Storage.remove('last_visit');
+ console.log('[UserState] 已重置所有状态');
+ }
+};
+
+// 初始化时记录访问时间(可选,页面加载时由 script.js 调用)
+console.log('✅ storage.js 已加载,HoloLake.Storage 和 HoloLake.UserState 已就绪');
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/M22-bulletin/style.css b/agents/zhuyuan-dev-agent/modules/M22-bulletin/style.css
new file mode 100644
index 0000000..ee79abf
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/M22-bulletin/style.css
@@ -0,0 +1,335 @@
+/* ===== CSS 变量定义(浅色/深色主题)===== */
+:root {
+ --bg-primary: #ffffff;
+ --bg-secondary: #f5f7fa;
+ --bg-tertiary: #eef1f5;
+ --text-primary: #1e293b;
+ --text-secondary: #475569;
+ --text-tertiary: #64748b;
+ --accent-primary: #3b82f6;
+ --accent-secondary: #2563eb;
+ --border-color: #e2e8f0;
+}
+
+/* 深色主题 */
+@media (prefers-color-scheme: dark) {
+ :root {
+ --bg-primary: #0f172a;
+ --bg-secondary: #1e293b;
+ --bg-tertiary: #334155;
+ --text-primary: #f1f5f9;
+ --text-secondary: #cbd5e1;
+ --text-tertiary: #94a3b8;
+ --accent-primary: #60a5fa;
+ --accent-secondary: #3b82f6;
+ --border-color: #334155;
+ }
+}
+
+/* ===== 基础样式 ===== */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ line-height: 1.5;
+ transition: background-color 0.3s, color 0.3s;
+}
+
+.app-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+}
+
+/* ===== 头部样式 ===== */
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 0;
+ border-bottom: 1px solid var(--border-color);
+ margin-bottom: 20px;
+}
+
+.header h1 {
+ font-size: 1.8rem;
+ font-weight: 600;
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+.header-actions {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.lang-switcher {
+ display: flex;
+ gap: 4px;
+}
+
+.lang-btn {
+ padding: 6px 12px;
+ border: 1px solid var(--border-color);
+ background: var(--bg-secondary);
+ color: var(--text-secondary);
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 0.9rem;
+}
+
+.lang-btn:hover {
+ background: var(--accent-primary);
+ color: white;
+}
+
+.pinned-badge {
+ background: #fef3c7;
+ color: #92400e;
+ padding: 4px 10px;
+ border-radius: 20px;
+ font-size: 0.85rem;
+}
+
+.subscribe-btn {
+ background: var(--accent-primary);
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+/* ===== 频道栏 ===== */
+.channel-bar {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 24px;
+ flex-wrap: wrap;
+}
+
+.channel-tab {
+ padding: 8px 20px;
+ border: none;
+ background: var(--bg-secondary);
+ color: var(--text-secondary);
+ border-radius: 30px;
+ cursor: pointer;
+ font-size: 0.95rem;
+ transition: all 0.2s;
+}
+
+.channel-tab.active {
+ background: var(--accent-primary);
+ color: white;
+}
+
+.channel-tab:hover:not(.active) {
+ background: var(--bg-tertiary);
+}
+
+/* ===== 骨架屏样式 ===== */
+.skeleton-wrapper {
+ margin: 20px 0;
+}
+
+.skeleton-card {
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ padding: 20px;
+ margin-bottom: 16px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.skeleton {
+ background: linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-secondary) 50%, var(--bg-tertiary) 75%);
+ background-size: 200% 100%;
+ animation: loading 1.5s infinite;
+ border-radius: 4px;
+}
+
+@keyframes loading {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+.skeleton-title {
+ width: 60%;
+ height: 24px;
+ margin-bottom: 12px;
+}
+
+.skeleton-text {
+ width: 100%;
+ height: 16px;
+ margin-bottom: 8px;
+}
+
+.skeleton-text.short {
+ width: 40%;
+}
+
+/* ===== 公告列表样式 ===== */
+.bulletin-container {
+ margin: 20px 0;
+ min-height: 200px;
+}
+
+.bulletin-item {
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ padding: 24px;
+ margin-bottom: 20px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+ border: 1px solid var(--border-color);
+}
+
+.bulletin-title {
+ font-size: 1.3rem;
+ margin-bottom: 10px;
+ color: var(--text-primary);
+}
+
+.bulletin-meta {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 16px;
+ color: var(--text-tertiary);
+ font-size: 0.9rem;
+}
+
+.bulletin-channel {
+ background: var(--bg-tertiary);
+ padding: 2px 10px;
+ border-radius: 20px;
+ color: var(--text-secondary);
+}
+
+.bulletin-date {
+ color: var(--text-tertiary);
+}
+
+.bulletin-content {
+ color: var(--text-secondary);
+ line-height: 1.7;
+}
+
+/* ===== 底部样式 ===== */
+.footer {
+ margin-top: 40px;
+ padding-top: 20px;
+ border-top: 1px solid var(--border-color);
+ display: flex;
+ justify-content: space-between;
+ color: var(--text-tertiary);
+ font-size: 0.9rem;
+}
+
+/* ===== EL-6 新增样式 ===== */
+/* 骨架屏平滑淡出 */
+.bulletin-container.data-loaded .bulletin-skeleton {
+ display: none;
+}
+
+.bulletin-item {
+ animation: fadeIn 0.3s ease;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* 错误状态 */
+.error-state {
+ text-align: center;
+ padding: 4rem 2rem;
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ margin: 2rem 0;
+}
+
+.error-icon {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+}
+
+.error-title {
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 0.5rem;
+}
+
+.error-message {
+ color: var(--text-secondary);
+ margin-bottom: 1rem;
+}
+
+.error-suggestion {
+ color: var(--text-tertiary);
+ font-size: 0.9rem;
+ margin-bottom: 1.5rem;
+}
+
+.retry-btn {
+ background: var(--accent-primary);
+ color: white;
+ border: none;
+ padding: 0.8rem 2rem;
+ border-radius: 8px;
+ font-size: 1rem;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.retry-btn:hover {
+ background: var(--accent-secondary);
+ transform: translateY(-2px);
+}
+
+/* 离线标记 */
+.offline-badge {
+ background: #ffd966;
+ color: #856404;
+ padding: 0.5rem 1rem;
+ border-radius: 20px;
+ font-size: 0.9rem;
+ display: inline-block;
+ margin-bottom: 1rem;
+}
+
+/* 空状态 */
+.empty-state {
+ text-align: center;
+ padding: 4rem 2rem;
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ margin: 2rem 0;
+}
+
+.empty-icon {
+ font-size: 3rem;
+ margin-bottom: 1rem;
+ opacity: 0.6;
+}
+
+.empty-title {
+ font-size: 1.2rem;
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+}
+
+.empty-desc {
+ color: var(--text-tertiary);
+}
+}
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/brain-writer.js b/agents/zhuyuan-dev-agent/modules/brain-writer.js
new file mode 100644
index 0000000..4b656c1
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/brain-writer.js
@@ -0,0 +1,83 @@
+// ═══════════════════════════════════════
+// 铸渊工程Agent · HLDP大脑记录器
+// 记录认知推理链,而非流水账
+// ═══════════════════════════════════════
+
+const fs = require("fs");
+const path = require("path");
+
+const AGENT_ID = process.env.AGENT_ID || "ICE-GL-BD001";
+const TERRITORY = path.join(__dirname, "..", "agents", AGENT_ID.replace("ICE-GL-BD", "BD"));
+
+if (!fs.existsSync(TERRITORY)) {
+ console.warn("[BrainWriter] 领地不存在: " + TERRITORY + " · 将不写入认知记录");
+}
+
+/**
+ * 写入一条认知记录
+ */
+function record(taskId, entry) {
+ if (!fs.existsSync(TERRITORY)) return;
+
+ const file = path.join(TERRITORY, "brain", "cognitive-record.hldp");
+ const lines = [
+ "",
+ "@task: " + taskId,
+ "@time: " + new Date().toISOString(),
+ "@input: " + (entry.input || ""),
+ "@decision: " + (entry.decision || ""),
+ "@execution: " + (entry.execution || ""),
+ "@failure: " + (entry.failure || "无"),
+ "@reasoning: " + (entry.reasoning || ""),
+ "@fix: " + (entry.fix || ""),
+ "@learned: " + (entry.learned || ""),
+ "@confidence: " + (entry.confidence || "N/A"),
+ "@new_knowledge: " + (entry.new_knowledge ? "true" : "false")
+ ];
+
+ fs.appendFileSync(file, lines.join("\n") + "\n", "utf-8");
+}
+
+/**
+ * 记录错误模式
+ */
+function recordErrorPattern(pattern) {
+ if (!fs.existsSync(TERRITORY)) return;
+ const file = path.join(TERRITORY, "memory", "error-patterns.json");
+ const data = JSON.parse(fs.readFileSync(file, "utf-8"));
+
+ // 检查是否已有同类模式
+ const existing = data.patterns.find(p => p.type === pattern.type);
+ if (existing) {
+ existing.count = (existing.count || 1) + 1;
+ existing.last_seen = new Date().toISOString();
+ } else {
+ data.patterns.push({
+ type: pattern.type,
+ description: pattern.description,
+ count: 1,
+ first_seen: new Date().toISOString(),
+ last_seen: new Date().toISOString(),
+ examples: [pattern.example]
+ });
+ }
+
+ fs.writeFileSync(file, JSON.stringify(data, null, 2), "utf-8");
+}
+
+/**
+ * 记录成长里程碑
+ */
+function recordMilestone(event) {
+ if (!fs.existsSync(TERRITORY)) return;
+ const file = path.join(TERRITORY, "brain", "growth-timeline.json");
+ const data = JSON.parse(fs.readFileSync(file, "utf-8"));
+ data.milestones.push({
+ time: new Date().toISOString(),
+ event: event,
+ project: process.env.CURRENT_PROJECT_ID || "unknown"
+ });
+ fs.writeFileSync(file, JSON.stringify(data, null, 2), "utf-8");
+}
+
+module.exports = { record, recordErrorPattern, recordMilestone, TERRITORY, AGENT_ID };
diff --git a/agents/zhuyuan-dev-agent/modules/cost-guard.js b/agents/zhuyuan-dev-agent/modules/cost-guard.js
new file mode 100644
index 0000000..71c8f80
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/cost-guard.js
@@ -0,0 +1,69 @@
+// ═══════════════════════════════════════
+// 铸渊开发Agent · 成本守卫 + 统一插座 v1.0
+// API调用计数 · 预算控制 · 输出格式校验
+// ═══════════════════════════════════════
+
+const fs = require("fs");
+const path = require("path");
+
+const CONSTITUTION = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "agent-constitution.json"), "utf-8"));
+const { cost_guard, unified_interface } = CONSTITUTION;
+
+let _callCount = 0;
+let _budgetExceeded = false;
+
+function reset() {
+ _callCount = 0;
+ _budgetExceeded = false;
+}
+
+function getCallCount() { return _callCount; }
+
+function isBudgetExceeded() { return _budgetExceeded; }
+
+/**
+ * 包裹模型调用——自动计数
+ */
+function wrapModelCaller(rawCaller) {
+ return async function(prompt, opts) {
+ if (_budgetExceeded) {
+ throw new Error(cost_guard.stop_message);
+ }
+
+ _callCount++;
+
+ if (_callCount > cost_guard.max_api_calls_per_session) {
+ _budgetExceeded = true;
+ throw new Error(cost_guard.stop_message + " (已用 " + _callCount + " 次,上限 " + cost_guard.max_api_calls_per_session + ")");
+ }
+
+ console.log("[CostGuard] API调用 #" + _callCount + "/" + cost_guard.max_api_calls_per_session);
+ return rawCaller(prompt, opts);
+ };
+}
+
+/**
+ * 统一校验——每个模块输出必须通过这把尺
+ */
+function validateOutput(output) {
+ const required = unified_interface.required_output_fields;
+ const missing = required.filter(f => !(f in output));
+
+ if (missing.length > 0) {
+ return {
+ valid: false,
+ error: "输出缺少必要字段: " + missing.join(", "),
+ required,
+ received: Object.keys(output)
+ };
+ }
+
+ // 类型校验
+ if (typeof output.ok !== "boolean") {
+ return { valid: false, error: "ok 必须是 boolean,收到: " + typeof output.ok };
+ }
+
+ return { valid: true };
+}
+
+module.exports = { wrapModelCaller, validateOutput, reset, getCallCount, isBudgetExceeded };
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/api-init.js b/agents/zhuyuan-dev-agent/modules/devboard/api-init.js
new file mode 100644
index 0000000..c49bc31
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/api-init.js
@@ -0,0 +1,81 @@
+// =====================================
+// HoloLake DevBoard · API Init v1.0
+// 负责:异步加载 + 加载动画 + 自动刷新
+// 必须在 api.js 和 main.js 之后加载
+// =====================================
+
+(function(){
+ // -------------- 加载动画 --------------
+ function showLoader(show) {
+ var el = document.getElementById('devboard-loader');
+ if (!el && show) {
+ el = document.createElement('div');
+ el.id = 'devboard-loader';
+ el.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(10,14,23,0.92);display:flex;align-items:center;justify-content:center;z-index:10000';
+ el.innerHTML = '';
+ var style = document.createElement('style');
+ style.textContent = '@keyframes dbspin{to{transform:rotate(360deg)}}';
+ document.head.appendChild(style);
+ document.body.appendChild(el);
+ }
+ if (el) el.style.display = show ? 'flex' : 'none';
+ }
+
+ // -------------- 数据加载+渲染 --------------
+ async function loadAndRender() {
+ try {
+ var developers = await apiGetDevelopers();
+ var stats = await apiGetStats();
+ var leaderboard = await apiGetLeaderboard();
+
+ // 使用 components.js 里实际存在的函数
+ if (typeof renderDeveloperCards === 'function') {
+ renderDeveloperCards(developers);
+ }
+
+ if (typeof renderRanking === 'function') {
+ renderRanking(leaderboard);
+ }
+
+ // stats 暂时用控制台输出,等找到实际渲染函数再改
+ console.log('[DevBoard] 统计数据:', stats);
+
+ console.log('[DevBoard] 数据加载完成,共 ' + developers.length + ' 位开发者');
+ } catch (err) {
+ console.error('[DevBoard] 数据加载异常: ', err);
+ }
+ }
+
+ // -------------- 初始化入口 --------------
+ async function init() {
+ showLoader(true);
+
+ // 检测API状态
+ var online = await apiHealthCheck();
+ showApiStatus(online);
+ console.log('[DevBoard] API状态: ' + (online ? '在线(实时)' : '离线(降级)'));
+
+ // 加载+渲染
+ await loadAndRender();
+ showLoader(false);
+
+ // 自动刷新(仅API在线时)
+ if (online && API.REFRESH_MS > 0) {
+ console.log('[DevBoard] 自动刷新已启动,间隔 ' + (API.REFRESH_MS/1000) + ' 秒');
+ setInterval(async function() {
+ try {
+ await loadAndRender();
+ } catch (e) {
+ console.warn('[DevBoard] 自动刷新失败: ', e.message);
+ }
+ }, API.REFRESH_MS);
+ }
+ }
+
+ // 等待DOM和所有脚本加载完成后启动
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/api.js b/agents/zhuyuan-dev-agent/modules/devboard/api.js
new file mode 100644
index 0000000..fd92807
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/api.js
@@ -0,0 +1,122 @@
+// ======================
+// HoloLake DevBoard · API Client v2.0
+// DEV-004 之之 · 环节5 · 真实API对接
+// 协议:SYSLOG-v4.0
+// ======================
+
+const API = {
+ // ====== 配置区 ======
+ // 页页的后端API地址(如果地址不对,问知秋确认)
+ BASE_URL: 'https://guanghulab.com/api/devboard',
+ TIMEOUT: 8000,
+ FALLBACK: true, // API挂了自动切换模拟数据
+ REFRESH_MS: 30000, // 30秒自动刷新
+ _cache: {}
+};
+
+// ====== 通用请求(带超时+缓存+降级) =====================
+async function apiFetch(path) {
+ const url = API.BASE_URL + path;
+ const ctrl = new AbortController();
+ const timer = setTimeout(() => ctrl.abort(), API.TIMEOUT);
+ try {
+ const res = await fetch(url, {
+ signal: ctrl.signal,
+ headers: { 'Accept': 'application/json' }
+ });
+ clearTimeout(timer);
+ if (!res.ok) throw new Error('HTTP ' + res.status);
+ const data = await res.json();
+ API._cache[path] = { data: data, time: Date.now() };
+ return data;
+ } catch (err) {
+ clearTimeout(timer);
+ console.warn(' 🚨 [DevBoard API] ' + path + ' 失败: ', err.message);
+ if (API._cache[path]) {
+ console.info(' 💾 [DevBoard API] 使用缓存');
+ return API._cache[path].data;
+ }
+ if (API.FALLBACK) {
+ console.info(' 🛡️ [DevBoard API] 降级到模拟数据');
+ return getMockData(path);
+ }
+ throw err;
+ }
+}
+
+// ========== 公开接口 ==========
+async function apiGetDevelopers() {
+ return apiFetch('/developers');
+}
+
+async function apiGetDeveloperDetail(devId) {
+ return apiFetch('/developers/' + devId);
+}
+
+async function apiGetStats() {
+ return apiFetch('/stats');
+}
+
+async function apiGetLeaderboard() {
+ return apiFetch('/leaderboard');
+}
+
+// ========== 健康检查 ==========
+async function apiHealthCheck() {
+ try {
+ const ctrl = new AbortController();
+ const t = setTimeout(() => ctrl.abort(), 3000);
+ const res = await fetch(API.BASE_URL + '/health', { signal: ctrl.signal });
+ clearTimeout(t);
+ return res.ok;
+ } catch (e) {
+ return false;
+ }
+}
+
+// ========== 模拟数据(降级保护) ==========
+function getMockData(path) {
+ var devs = [
+ { dev_id:'DEV-001', name:'页页', module:'BC-集成', status:'进行中', streak:5, el:'EL-6', pca:{EXE:75,TEC:60,SYS:70,COL:80,INI:65} },
+ { dev_id:'DEV-002', name:'肥猫', module:'M-STATUS', status:'进行中', streak:9, el:'EL-5', pca:{EXE:72,TEC:40,SYS:68,COL:82,INI:70} },
+ { dev_id:'DEV-003', name:'燕樊', module:'M-MEMORY', status:'进行中', streak:7, el:'EL-5', pca:{EXE:70,TEC:55,SYS:65,COL:75,INI:68} },
+ { dev_id:'DEV-004', name:'之之', module:'M-DEVBOARD', status:'进行中', streak:14, el:'EL-8', pca:{EXE:90,TEC:55,SYS:80,COL:85,INI:88} },
+ { dev_id:'DEV-009', name:'花尔', module:'M20', status:'进行中', streak:4, el:'EL-8', pca:{EXE:65,TEC:45,SYS:55,COL:70,INI:60} },
+ { dev_id:'DEV-010', name:'桔子', module:'M-CHANNEL', status:'进行中', streak:14, el:'EL-6', pca:{EXE:85,TEC:50,SYS:75,COL:80,INI:82} },
+ { dev_id:'DEV-011', name:'匆匆那年',module:'M16', status:'等待中', streak:1, el:'EL-3', pca:{EXE:50,TEC:30,SYS:40,COL:55,INI:45} },
+ { dev_id:'DEV-012', name:'Awen', module:'M22', status:'进行中', streak:13, el:'EL-6', pca:{EXE:88,TEC:45,SYS:78,COL:90,INI:80} },
+ { dev_id:'DEV-013', name:'小兴', module:'M-AUTH', status:'进行中', streak:1, el:'EL-3', pca:{EXE:55,TEC:25,SYS:35,COL:60,INI:50} }
+ ];
+ var stats = { total_developers:10, active:8, modules:12, code_lines:32000, longest_streak:14, avg_streak:7.5 };
+
+ if (path === '/developers') return devs;
+ if (path.indexOf('/developers/') === 0) {
+ var id = path.split('/').pop();
+ return devs.find(function(d){ return d.dev_id === id; }) || devs[0];
+ }
+ if (path === '/stats') return stats;
+ if (path === '/leaderboard') return devs.slice().sort(function(a,b){ return b.streak - a.streak; });
+ return [];
+}
+
+// ========== API状态指示器 ==========
+function showApiStatus(online) {
+ var el = document.getElementById('api-status');
+ if (!el) {
+ el = document.createElement('div');
+ el.id = 'api-status';
+ el.style.cssText = 'position:fixed;bottom:12px;right:12px;padding:6px 14px;border-radius:20px;font-size:12px;font-weight:600;z-index:9999;transition:all 0.3s;';
+ document.body.appendChild(el);
+ }
+ if (online) {
+ el.textContent = '🟢 实时数据';
+ el.style.backgroundColor = 'rgba(52,211,153,0.15)';
+ el.style.color = '#34d399';
+ el.style.border = '1px solid rgba(52,211,153,0.3)';
+ } else {
+ el.textContent = '🟡 模拟数据';
+ el.style.backgroundColor = 'rgba(251,191,36,0.15)';
+ el.style.color = '#fbbf24';
+ el.style.border = '1px solid rgba(251,191,36,0.3)';
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/components.css b/agents/zhuyuan-dev-agent/modules/devboard/components.css
new file mode 100644
index 0000000..fd16ad3
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/components.css
@@ -0,0 +1,156 @@
+/* components.css - 组件样式 */
+
+/* 开发者卡片 */
+.dev-card {
+ background: linear-gradient(145deg, #1e2436, #161b28);
+ border-radius: 20px;
+ padding: 20px;
+ border: 1px solid #2a3040;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ cursor: pointer;
+ position: relative;
+ overflow: hidden;
+}
+
+.dev-card:hover {
+ transform: translateY(-8px) scale(1.02);
+ border-color: #4a9eff;
+ box-shadow: 0 20px 30px -10px rgba(74, 158, 255, 0.3);
+}
+
+.dev-card:focus-visible {
+ outline: 3px solid #ffd700;
+ outline-offset: 2px;
+}
+
+.dev-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
+ transition: left 0.5s;
+}
+
+.dev-card:hover::before {
+ left: 100%;
+}
+
+.dev-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+ font-size: 14px;
+}
+
+.dev-id {
+ color: #4a9eff;
+ font-weight: bold;
+ font-family: monospace;
+}
+
+.dev-wins {
+ background: rgba(255, 215, 0, 0.1);
+ padding: 4px 8px;
+ border-radius: 20px;
+ color: #ffd700;
+}
+
+.dev-name {
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 10px;
+ color: #fff;
+}
+
+.dev-details {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 15px;
+ font-size: 14px;
+}
+
+.dev-el {
+ background: #2a3040;
+ padding: 4px 8px;
+ border-radius: 12px;
+ color: #4a9eff;
+}
+
+.dev-module {
+ background: #2a3040;
+ padding: 4px 8px;
+ border-radius: 12px;
+ color: #a0a0a0;
+}
+
+.dev-pca {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 5px;
+ font-size: 11px;
+}
+
+.pca-mini {
+ text-align: center;
+ padding: 4px 0;
+ border-radius: 8px;
+ background: rgba(0,0,0,0.3);
+}
+
+.pca-mini.EXE { color: #4CAF50; }
+.pca-mini.TEC { color: #2196F3; }
+.pca-mini.SYS { color: #9C27B0; }
+.pca-mini.COL { color: #FF9800; }
+.pca-mini.INI { color: #f44336; }
+
+/* 排行榜项 */
+.ranking-item {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 15px;
+ border-bottom: 1px solid #2a3040;
+ transition: background 0.2s;
+}
+
+.ranking-item:hover {
+ background: #1e2436;
+}
+
+.rank-number {
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #2a3040;
+ border-radius: 50%;
+ font-weight: bold;
+ color: #4a9eff;
+}
+
+.rank-1 .rank-number { background: #ffd700; color: #000; }
+.rank-2 .rank-number { background: #c0c0c0; color: #000; }
+.rank-3 .rank-number { background: #cd7f32; color: #000; }
+
+.rank-info {
+ flex: 1;
+}
+
+.rank-name {
+ font-weight: bold;
+ color: #fff;
+}
+
+.rank-id {
+ font-size: 12px;
+ color: #888;
+}
+
+.rank-wins {
+ font-weight: bold;
+ color: #ffd700;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/components.js b/agents/zhuyuan-dev-agent/modules/devboard/components.js
new file mode 100644
index 0000000..5f033e9
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/components.js
@@ -0,0 +1,146 @@
+// components.js - 组件渲染函数
+
+// 模拟数据
+const mockDevelopers = [
+ { id: 'DEV-001', name: '小明', pca: { EXE: 85, TEC: 72, SYS: 68, COL: 90, INI: 78 }, wins: 5, el: 'EL-6', module: 'M-DEVBOARD' },
+ { id: 'DEV-002', name: '小红', pca: { EXE: 78, TEC: 88, SYS: 82, COL: 75, INI: 92 }, wins: 8, el: 'EL-7', module: 'M-FRONTEND' },
+ { id: 'DEV-003', name: '小刚', pca: { EXE: 92, TEC: 65, SYS: 70, COL: 68, INI: 85 }, wins: 3, el: 'EL-5', module: 'M-BACKEND' },
+ { id: 'DEV-004', name: '之之', pca: { EXE: 75, TEC: 58, SYS: 82, COL: 82, INI: 85 }, wins: 13, el: 'EL-6', module: 'M-DEVBOARD' }
+];
+
+// 获取所有开发者
+function getDevelopers() {
+ return mockDevelopers;
+}
+
+// 根据ID获取开发者
+function getDeveloperById(id) {
+ return mockDevelopers.find(d => d.id === id) || mockDevelopers[0];
+}
+
+// 渲染开发者卡片
+function renderDeveloperCards(developers) {
+ return developers.map(dev => `
+
+
+
${dev.name}
+
+ ${dev.el}
+ ${dev.module}
+
+
+ ${Object.entries(dev.pca).map(([key, val]) =>
+ `${key}:${val}`
+ ).join('')}
+
+
+ `).join('');
+}
+
+// 渲染排行榜
+function renderRanking(developers) {
+ // 按连胜数排序
+ const sorted = [...developers].sort((a, b) => b.wins - a.wins);
+
+ return sorted.map((dev, index) => {
+ const rankClass = index === 0 ? 'rank-1' : index === 1 ? 'rank-2' : index === 2 ? 'rank-3' : '';
+ return `
+
+
${index + 1}
+
+
${dev.name}
+
${dev.id}
+
+
${dev.wins}连胜
+
+ `;
+ }).join('');
+}
+
+// 刷新看板
+function refreshDashboard() {
+ const developers = getDevelopers();
+
+ // 渲染卡片
+ const grid = document.getElementById('developers-grid');
+ if (grid) {
+ grid.innerHTML = renderDeveloperCards(developers);
+ }
+
+ // 渲染排行榜
+ const ranking = document.getElementById('ranking-list');
+ if (ranking) {
+ ranking.innerHTML = renderRanking(developers);
+ }
+
+ // 绘制团队雷达图(平均分)
+ const avgPca = {
+ EXE: Math.round(developers.reduce((sum, d) => sum + d.pca.EXE, 0) / developers.length),
+ TEC: Math.round(developers.reduce((sum, d) => sum + d.pca.TEC, 0) / developers.length),
+ SYS: Math.round(developers.reduce((sum, d) => sum + d.pca.SYS, 0) / developers.length),
+ COL: Math.round(developers.reduce((sum, d) => sum + d.pca.COL, 0) / developers.length),
+ INI: Math.round(developers.reduce((sum, d) => sum + d.pca.INI, 0) / developers.length)
+ };
+
+ drawRadarChart('team-radar', avgPca);
+}
+
+// 搜索筛选
+function filterDevelopers(searchTerm, filterStatus) {
+ let developers = getDevelopers();
+
+ // 搜索过滤
+ if (searchTerm) {
+ const term = searchTerm.toLowerCase();
+ developers = developers.filter(dev =>
+ dev.name.toLowerCase().includes(term) ||
+ dev.id.toLowerCase().includes(term) ||
+ dev.module.toLowerCase().includes(term)
+ );
+ }
+
+ // 状态过滤(模拟,实际需要真实数据)
+ if (filterStatus !== 'all') {
+ // 这里简化处理,实际应该根据模块状态筛选
+ developers = developers.filter(dev => {
+ if (filterStatus === 'active') return dev.wins < 10;
+ if (filterStatus === 'completed') return dev.wins >= 10;
+ return true;
+ });
+ }
+
+ return developers;
+}
+
+// 导出函数
+window.getDevelopers = getDevelopers;
+window.getDeveloperById = getDeveloperById;
+window.renderDeveloperCards = renderDeveloperCards;
+window.renderRanking = renderRanking;
+window.refreshDashboard = refreshDashboard;
+window.filterDevelopers = filterDevelopers;
+
+// 增强版排行榜渲染(带数字滚动支持)
+function renderRankingWithAnimation(developers) {
+ const sorted = [...developers].sort((a, b) => b.wins - a.wins);
+
+ return sorted.map((dev, index) => {
+ const rankClass = index === 0 ? 'rank-1' : index === 1 ? 'rank-2' : index === 2 ? 'rank-3' : '';
+ return `
+
+
${index + 1}
+
+
${dev.name}
+
${dev.id}
+
+
${dev.wins}连胜
+
+ `;
+ }).join('');
+}
+
+// 覆盖原函数
+window.renderRanking = renderRankingWithAnimation;
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/css/components.css b/agents/zhuyuan-dev-agent/modules/devboard/css/components.css
new file mode 100644
index 0000000..dd88ba4
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/css/components.css
@@ -0,0 +1,140 @@
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1rem;
+ margin-bottom: 2rem;
+}
+
+.stat-card {
+ background: linear-gradient(145deg, var(--color-bg-card), #2a3442);
+ border-radius: 16px;
+ padding: 1.5rem 1rem;
+ text-align: center;
+ border: 1px solid var(--color-border);
+}
+
+.stat-label {
+ font-size: 0.9rem;
+ color: var(--color-text-secondary);
+ margin-bottom: 0.5rem;
+ text-transform: uppercase;
+}
+
+.stat-number {
+ font-size: 2.5rem;
+ font-weight: 800;
+ background: linear-gradient(135deg, #fff, var(--color-accent-primary));
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ margin-bottom: 0.25rem;
+}
+
+.dev-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1.5rem;
+ margin-top: 1.5rem;
+}
+
+.dev-card {
+ background: var(--color-bg-card);
+ border-radius: 20px;
+ padding: 1.5rem;
+ border: 1px solid var(--color-border);
+ cursor: pointer;
+}
+
+.dev-card-header {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1.2rem;
+}
+
+.dev-avatar {
+ width: 50px;
+ height: 50px;
+ border-radius: 25px;
+ background: linear-gradient(135deg, var(--color-accent-primary), #4cc9f0);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
+ font-size: 1.5rem;
+ color: white;
+}
+
+.dev-name {
+ font-size: 1.2rem;
+ font-weight: 700;
+}
+
+.dev-id {
+ font-size: 0.8rem;
+ color: var(--color-text-secondary);
+}
+
+.dev-streak {
+ font-size: 1.8rem;
+ font-weight: 800;
+ color: var(--color-accent-primary);
+}
+
+.ranking-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+}
+
+.ranking-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.8rem;
+}
+
+.ranking-item {
+ display: grid;
+ grid-template-columns: 40px 1fr 70px;
+ align-items: center;
+ background: var(--color-bg-card);
+ border-radius: 12px;
+ padding: 0.8rem;
+ border: 1px solid var(--color-border);
+}
+
+.ranking-streak-bar {
+ background: var(--color-bg-secondary);
+ height: 16px;
+ border-radius: 20px;
+ position: relative;
+ overflow: hidden;
+}
+
+.streak-progress {
+ height: 100%;
+ background: linear-gradient(90deg, #00b4d8, #4cc9f0);
+}
+
+.streak-count {
+ position: absolute;
+ right: 6px;
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 0.7rem;
+ color: white;
+}
+
+.loading-spinner {
+ width: 30px;
+ height: 30px;
+ border: 3px solid var(--color-border);
+ border-top-color: var(--color-accent-primary);
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin: 20px auto;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/css/layout.css b/agents/zhuyuan-dev-agent/modules/devboard/css/layout.css
new file mode 100644
index 0000000..7811611
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/css/layout.css
@@ -0,0 +1,58 @@
+.navbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem 2rem;
+ background-color: var(--color-bg-secondary);
+ border-bottom: 1px solid var(--color-border);
+}
+
+.navbar-logo {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-weight: bold;
+ font-size: 1.2rem;
+}
+.navbar-logo span {
+ color: var(--color-accent-primary);
+}
+
+.navbar-info {
+ display: flex;
+ gap: 1.5rem;
+ color: var(--color-text-secondary);
+ font-size: 0.9rem;
+}
+
+.main-container {
+ display: grid;
+ grid-template-columns: 1fr 2fr 1fr;
+ gap: 1.5rem;
+ padding: 1.5rem;
+ min-height: calc(100vh - 70px);
+}
+
+.sidebar-left, .main-board, .sidebar-right {
+ background-color: var(--color-bg-secondary);
+ border-radius: 12px;
+ padding: 1.5rem;
+}
+
+@media (max-width: 1024px) {
+ .main-container {
+ grid-template-columns: 1fr 2fr;
+ }
+ .sidebar-right {
+ grid-column: span 2;
+ }
+}
+
+@media (max-width: 768px) {
+ .main-container {
+ grid-template-columns: 1fr;
+ }
+ .sidebar-left, .main-board, .sidebar-right {
+ grid-column: span 1;
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/css/theme.css b/agents/zhuyuan-dev-agent/modules/devboard/css/theme.css
new file mode 100644
index 0000000..bf856f5
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/css/theme.css
@@ -0,0 +1,43 @@
+:root {
+ --color-bg-primary: #0a0e1a;
+ --color-bg-secondary: #111827;
+ --color-bg-card: #1e2433;
+ --color-text-primary: #ffffff;
+ --color-text-secondary: #a0aec0;
+ --color-accent-primary: #00b4d8;
+ --color-accent-secondary: #0077be;
+ --color-success: #10b981;
+ --color-warning: #f59e0b;
+ --color-danger: #ef4444;
+ --color-border: #2d3748;
+
+ --breakpoint-mobile: 768px;
+ --breakpoint-tablet: 1024px;
+
+ --font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: var(--font-family-base);
+ background-color: var(--color-bg-primary);
+ color: var(--color-text-primary);
+ line-height: 1.6;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+::-webkit-scrollbar-track {
+ background: var(--color-bg-secondary);
+}
+::-webkit-scrollbar-thumb {
+ background: var(--color-border);
+ border-radius: 4px;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/detail.css b/agents/zhuyuan-dev-agent/modules/devboard/detail.css
new file mode 100644
index 0000000..385c484
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/detail.css
@@ -0,0 +1,358 @@
+/* detail.css - 详情页专用样式 */
+
+/* 详情页容器 */
+#detail-container {
+ display: none;
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+ min-height: 100vh;
+ padding: 20px;
+ animation: fadeIn 0.3s ease;
+}
+
+/* 详情页头部 */
+.detail-header {
+ margin-bottom: 30px;
+ max-width: 1200px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.back-button {
+ background: rgba(255, 255, 255, 0.1);
+ border: 2px solid #4a9eff;
+ color: #fff;
+ padding: 12px 24px;
+ border-radius: 30px;
+ font-size: 16px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(5px);
+}
+
+.back-button:hover {
+ background: #4a9eff;
+ transform: translateX(-5px);
+ box-shadow: 0 0 20px rgba(74, 158, 255, 0.5);
+}
+
+.back-button:focus-visible {
+ outline: 3px solid #ffd700;
+ outline-offset: 2px;
+}
+
+/* 详情页内容区 */
+.detail-content {
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.detail-section {
+ background: rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(10px);
+ border-radius: 20px;
+ padding: 25px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
+}
+
+.detail-section h2 {
+ color: #4a9eff;
+ font-size: 20px;
+ margin-bottom: 20px;
+ border-bottom: 2px solid rgba(74, 158, 255, 0.3);
+ padding-bottom: 10px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+/* 开发者大头卡片(大尺寸) */
+.developer-card-large .dev-card {
+ transform: scale(1);
+ background: linear-gradient(145deg, #1e2436, #161b28);
+ border: 2px solid #4a9eff;
+ margin-bottom: 0;
+}
+
+.developer-card-large .dev-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 20px 30px -10px rgba(74, 158, 255, 0.5);
+}
+
+/* PCA雷达图容器 */
+.radar-large-container {
+ height: 300px;
+ margin-bottom: 20px;
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 12px;
+ padding: 10px;
+}
+
+.radar-large-container canvas {
+ width: 100%;
+ height: 100%;
+}
+
+/* PCA分数标签 */
+.pca-scores {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ justify-content: center;
+ margin-top: 20px;
+}
+
+.score-tag {
+ padding: 8px 16px;
+ border-radius: 20px;
+ font-weight: bold;
+ font-size: 14px;
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid;
+ transition: transform 0.2s;
+}
+
+.score-tag:hover {
+ transform: scale(1.05);
+}
+
+.score-tag.EXE { border-color: #4CAF50; color: #4CAF50; }
+.score-tag.TEC { border-color: #2196F3; color: #2196F3; }
+.score-tag.SYS { border-color: #9C27B0; color: #9C27B0; }
+.score-tag.COL { border-color: #FF9800; color: #FF9800; }
+.score-tag.INI { border-color: #f44336; color: #f44336; }
+
+/* 五维进度条 */
+.progress-bars-container {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.progress-item {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 10px;
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 12px;
+ transition: transform 0.2s ease, background 0.2s;
+}
+
+.progress-item:hover {
+ transform: translateX(10px);
+ background: rgba(0, 0, 0, 0.3);
+}
+
+.progress-label {
+ width: 100px;
+ display: flex;
+ justify-content: space-between;
+ font-weight: bold;
+}
+
+.dim-name {
+ color: #fff;
+}
+
+.dim-score {
+ color: #ffd700;
+ font-family: monospace;
+}
+
+.progress-bar-bg {
+ flex: 1;
+ height: 24px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 12px;
+ overflow: hidden;
+ position: relative;
+}
+
+.progress-bar-fill {
+ height: 100%;
+ border-radius: 12px;
+ transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+}
+
+.progress-bar-fill::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
+ animation: shimmer 2s infinite;
+}
+
+.progress-dim-code {
+ width: 50px;
+ text-align: right;
+ font-family: monospace;
+ color: #888;
+ font-size: 14px;
+}
+
+/* 模块历史列表 */
+.module-history-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.module-item {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ padding: 15px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ transition: background 0.2s ease;
+}
+
+.module-item:hover {
+ background: rgba(255, 255, 255, 0.05);
+ transform: scale(1.01);
+}
+
+.module-code {
+ font-family: monospace;
+ color: #4a9eff;
+ font-weight: bold;
+ width: 150px;
+}
+
+.module-name {
+ flex: 1;
+ color: #fff;
+}
+
+.module-status {
+ padding: 4px 12px;
+ border-radius: 20px;
+ font-size: 12px;
+ font-weight: bold;
+ width: 80px;
+ text-align: center;
+}
+
+.status-已完成 { background: rgba(76, 175, 80, 0.2); color: #4CAF50; border: 1px solid #4CAF50; }
+.status-进行中 { background: rgba(255, 152, 0, 0.2); color: #FF9800; border: 1px solid #FF9800; }
+.status-待开始 { background: rgba(158, 158, 158, 0.2); color: #9E9E9E; border: 1px solid #9E9E9E; }
+
+.module-date {
+ color: #888;
+ font-size: 14px;
+ width: 100px;
+ font-family: monospace;
+}
+
+/* 趋势图容器 */
+.trend-chart-container {
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 12px;
+ padding: 20px;
+ position: relative;
+}
+
+/* 无数据提示 */
+.no-data {
+ text-align: center;
+ color: #888;
+ padding: 40px;
+ font-style: italic;
+}
+
+/* 动画定义 */
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(20px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes fadeOut {
+ from { opacity: 1; transform: translateY(0); }
+ to { opacity: 0; transform: translateY(20px); }
+}
+
+@keyframes shimmer {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(100%); }
+}
+
+.fade-in {
+ animation: fadeIn 0.3s ease forwards;
+}
+
+.fade-out {
+ animation: fadeOut 0.3s ease forwards;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .detail-content {
+ gap: 15px;
+ }
+
+ .detail-section {
+ padding: 15px;
+ }
+
+ .progress-item {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .progress-label {
+ width: 100%;
+ }
+
+ .progress-bar-bg {
+ width: 100%;
+ }
+
+ .progress-dim-code {
+ width: 100%;
+ text-align: left;
+ }
+
+ .module-item {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .module-code {
+ width: auto;
+ }
+
+ .module-name {
+ width: 100%;
+ }
+
+ .module-status {
+ width: auto;
+ }
+
+ .module-date {
+ width: auto;
+ }
+
+ .pca-scores {
+ gap: 8px;
+ }
+
+ .score-tag {
+ padding: 4px 12px;
+ font-size: 12px;
+ }
+
+ .radar-large-container {
+ height: 250px;
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/detail.js b/agents/zhuyuan-dev-agent/modules/devboard/detail.js
new file mode 100644
index 0000000..7996cbb
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/detail.js
@@ -0,0 +1,354 @@
+// detail.js - 开发者详情页渲染器
+
+let currentDevId = null;
+
+// 显示详情页
+function showDetailPage(devId) {
+ currentDevId = devId;
+
+ const overview = document.getElementById('overview-container');
+ const detail = document.getElementById('detail-container');
+
+ if (!overview || !detail) {
+ console.error('找不到容器元素');
+ return;
+ }
+
+ // 淡出总览页
+ overview.classList.add('fade-out');
+
+ setTimeout(() => {
+ overview.style.display = 'none';
+ overview.classList.remove('fade-out');
+
+ // 渲染详情页数据
+ renderDetailPage(devId);
+
+ detail.style.display = 'block';
+ detail.classList.add('fade-in');
+
+ // 滚动到顶部
+ window.scrollTo(0, 0);
+ }, 300);
+}
+
+// 返回总览页
+function hideDetailPage() {
+ const overview = document.getElementById('overview-container');
+ const detail = document.getElementById('detail-container');
+
+ detail.classList.add('fade-out');
+
+ setTimeout(() => {
+ detail.style.display = 'none';
+ detail.classList.remove('fade-out');
+
+ overview.style.display = 'block';
+ overview.classList.add('fade-in');
+
+ // 刷新总览页数据
+ refreshDashboard();
+ }, 300);
+}
+
+// 渲染详情页
+function renderDetailPage(devId) {
+ const dev = getDeveloperById(devId);
+ if (!dev) return;
+
+ const container = document.getElementById('detail-container');
+
+ // 生成五维进度条
+ const progressBars = renderPCAProgressBars(dev.pca);
+
+ // 获取模块历史
+ const modules = getModuleHistory(devId);
+ const moduleList = renderModuleHistory(modules);
+
+ // 获取连胜趋势数据
+ const trendData = getWinTrendData(devId);
+
+ container.innerHTML = `
+
+
+
+
+
+
+
${dev.name}
+
+ ${dev.el}
+ ${dev.module}
+
+
+ ${Object.entries(dev.pca).map(([key, val]) =>
+ `${key}:${val}`
+ ).join('')}
+
+
+
+
+
+
+
📊 完整PCA雷达图
+
+ ${renderRadarChart(dev.pca, 'large')}
+
+
+ ${Object.entries(dev.pca).map(([dim, score]) =>
+ `${dim}: ${score}`
+ ).join('')}
+
+
+
+
+
+
📈 五维进度条
+
+ ${progressBars}
+
+
+
+
+
+
📋 模块历史
+
+ ${moduleList}
+
+
+
+
+
+
+ `;
+
+ // 绘制趋势图
+ setTimeout(() => drawTrendChart('trend-canvas', trendData), 100);
+}
+
+// 渲染PCA五维进度条
+function renderPCAProgressBars(pca) {
+ const dimensions = [
+ { key: 'EXE', label: '执行力', color: '#4CAF50' },
+ { key: 'TEC', label: '技术深度', color: '#2196F3' },
+ { key: 'SYS', label: '系统思维', color: '#9C27B0' },
+ { key: 'COL', label: '协作力', color: '#FF9800' },
+ { key: 'INI', label: '主动性', color: '#f44336' }
+ ];
+
+ return dimensions.map(dim => {
+ const score = pca[dim.key] || 0;
+
+ return `
+
+
+ ${dim.label}
+ ${score}
+
+
+
${dim.key}
+
+ `;
+ }).join('');
+}
+
+// 渲染模块历史列表
+function renderModuleHistory(modules) {
+ if (!modules || modules.length === 0) {
+ return '暂无模块历史
';
+ }
+
+ return `
+
+ ${modules.map(m => `
+ -
+ ${m.code}
+ ${m.name}
+ ${m.status}
+ ${m.completedDate || m.startDate || '-'}
+
+ `).join('')}
+
+ `;
+}
+
+// 获取模块历史(模拟数据)
+function getModuleHistory(devId) {
+ const histories = {
+ 'DEV-001': [
+ { code: 'M-DEVBOARD-001', name: '开发者看板·环节0~2', status: '已完成', completedDate: '2026-03-10' },
+ { code: 'M-FRONTEND-003', name: '前端基础组件', status: '已完成', completedDate: '2026-03-08' },
+ { code: 'M-API-002', name: 'API对接练习', status: '进行中', completedDate: null }
+ ],
+ 'DEV-002': [
+ { code: 'M-FRONTEND-001', name: '前端架构设计', status: '已完成', completedDate: '2026-03-09' },
+ { code: 'M-UI-005', name: '组件库开发', status: '已完成', completedDate: '2026-03-07' },
+ { code: 'M-TEST-003', name: '单元测试编写', status: '待开始', completedDate: null }
+ ],
+ 'DEV-003': [
+ { code: 'M-BACKEND-002', name: 'API服务搭建', status: '已完成', completedDate: '2026-03-11' },
+ { code: 'M-DB-001', name: '数据库设计', status: '已完成', completedDate: '2026-03-09' },
+ { code: 'M-DEPLOY-002', name: '容器化部署', status: '进行中', completedDate: null }
+ ],
+ 'DEV-004': [
+ { code: 'M-DEVBOARD-001', name: '开发者看板·环节0~2', status: '已完成', completedDate: '2026-03-11' },
+ { code: 'M-CANVAS-005', name: 'Canvas雷达图绘制', status: '已完成', completedDate: '2026-03-09' },
+ { code: 'M-COMPONENTS-003', name: '组件化思维训练', status: '已完成', completedDate: '2026-03-07' },
+ { code: 'M-DEPLOY-001', name: '静态部署练习', status: '待开始', completedDate: null }
+ ]
+ };
+ return histories[devId] || histories['DEV-001'];
+}
+
+// 获取连胜趋势数据
+function getWinTrendData(devId) {
+ const trends = {
+ 'DEV-001': [
+ { date: '03-06', value: 3 },
+ { date: '03-07', value: 3 },
+ { date: '03-08', value: 4 },
+ { date: '03-09', value: 4 },
+ { date: '03-10', value: 5 },
+ { date: '03-11', value: 5 },
+ { date: '03-12', value: 5 }
+ ],
+ 'DEV-002': [
+ { date: '03-06', value: 5 },
+ { date: '03-07', value: 6 },
+ { date: '03-08', value: 6 },
+ { date: '03-09', value: 7 },
+ { date: '03-10', value: 7 },
+ { date: '03-11', value: 8 },
+ { date: '03-12', value: 8 }
+ ],
+ 'DEV-003': [
+ { date: '03-06', value: 1 },
+ { date: '03-07', value: 1 },
+ { date: '03-08', value: 2 },
+ { date: '03-09', value: 2 },
+ { date: '03-10', value: 3 },
+ { date: '03-11', value: 3 },
+ { date: '03-12', value: 3 }
+ ],
+ 'DEV-004': [
+ { date: '03-06', value: 8 },
+ { date: '03-07', value: 9 },
+ { date: '03-08', value: 9 },
+ { date: '03-09', value: 10 },
+ { date: '03-10', value: 11 },
+ { date: '03-11', value: 12 },
+ { date: '03-12', value: 13 }
+ ]
+ };
+ return trends[devId] || trends['DEV-001'];
+}
+
+// 绘制趋势图
+function drawTrendChart(canvasId, data) {
+ const canvas = document.getElementById(canvasId);
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ const width = canvas.width;
+ const height = canvas.height;
+ const padding = 40;
+
+ ctx.clearRect(0, 0, width, height);
+
+ if (!data || data.length < 2) {
+ ctx.fillStyle = '#888';
+ ctx.font = '14px monospace';
+ ctx.fillText('暂无连胜数据', padding, height/2);
+ return;
+ }
+
+ // 计算坐标范围
+ const values = data.map(d => d.value);
+ const maxValue = Math.max(...values) + 1;
+ const minValue = Math.max(0, Math.min(...values) - 1);
+ const xStep = (width - 2 * padding) / (data.length - 1);
+
+ // 绘制网格线
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ for (let i = 0; i <= 5; i++) {
+ const y = padding + (i * (height - 2 * padding) / 5);
+ ctx.moveTo(padding, y);
+ ctx.lineTo(width - padding, y);
+ }
+ ctx.strokeStyle = '#444';
+ ctx.stroke();
+
+ // 绘制轴线
+ ctx.beginPath();
+ ctx.strokeStyle = '#666';
+ ctx.lineWidth = 2;
+ ctx.moveTo(padding, padding);
+ ctx.lineTo(padding, height - padding);
+ ctx.lineTo(width - padding, height - padding);
+ ctx.stroke();
+
+ // 绘制折线
+ ctx.beginPath();
+ ctx.strokeStyle = '#4CAF50';
+ ctx.lineWidth = 3;
+
+ data.forEach((point, i) => {
+ const x = padding + i * xStep;
+ const y = padding + (height - 2 * padding) * (1 - (point.value - minValue) / (maxValue - minValue));
+
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ });
+ ctx.stroke();
+
+ // 绘制数据点
+ data.forEach((point, i) => {
+ const x = padding + i * xStep;
+ const y = padding + (height - 2 * padding) * (1 - (point.value - minValue) / (maxValue - minValue));
+
+ ctx.beginPath();
+ ctx.arc(x, y, 6, 0, Math.PI * 2);
+ ctx.fillStyle = '#FFD700';
+ ctx.fill();
+ ctx.strokeStyle = '#fff';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ // 显示数值
+ ctx.fillStyle = '#fff';
+ ctx.font = 'bold 12px monospace';
+ ctx.textAlign = 'center';
+ ctx.fillText(point.value, x, y - 15);
+
+ // 显示日期
+ ctx.fillStyle = '#aaa';
+ ctx.font = '10px monospace';
+ ctx.fillText(point.date, x, height - padding + 20);
+ });
+}
+
+// 导出函数
+window.showDetailPage = showDetailPage;
+window.hideDetailPage = hideDetailPage;
+window.getModuleHistory = getModuleHistory;
+window.getWinTrendData = getWinTrendData;
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/index.html b/agents/zhuyuan-dev-agent/modules/devboard/index.html
new file mode 100644
index 0000000..eb5e1f6
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 开发者实时看板 · HoloLake
+
+
+
+ 🚀 HoloLake 开发者实时看板
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/js/api.js b/agents/zhuyuan-dev-agent/modules/devboard/js/api.js
new file mode 100644
index 0000000..88993f0
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/js/api.js
@@ -0,0 +1,54 @@
+const MOCK_DEVELOPERS = [
+ { id: 'DEV-001', name: '玄泓', streak: 8, el: 6, pca: { exe: 75, tec: 68, sys: 82, col: 79, ini: 85 }, totalScore: 78, status: 'doing', module: 'M-CORE', lastActive: '2026-03-11T10:30:00Z' },
+ { id: 'DEV-002', name: '苍钺', streak: 5, el: 5, pca: { exe: 82, tec: 71, sys: 65, col: 90, ini: 72 }, totalScore: 76, status: 'pending', module: 'M-UI', lastActive: '2026-03-11T09:15:00Z' },
+ { id: 'DEV-003', name: '桔子', streak: 13, el: 8, pca: { exe: 88, tec: 85, sys: 92, col: 78, ini: 90 }, totalScore: 87, status: 'doing', module: 'M-ORCHESTRATOR', lastActive: '2026-03-11T11:20:00Z' },
+ { id: 'DEV-004', name: '之之', streak: 12, el: 8, pca: { exe: 70, tec: 32, sys: 80, col: 80, ini: 83 }, totalScore: 66, status: 'doing', module: 'M-DEVBOARD', lastActive: '2026-03-11T12:05:00Z' },
+ { id: 'DEV-005', name: '琉光', streak: 7, el: 6, pca: { exe: 79, tec: 74, sys: 71, col: 88, ini: 77 }, totalScore: 78, status: 'done', module: 'M-TEST', lastActive: '2026-03-10T16:40:00Z' },
+ { id: 'DEV-006', name: '墨羽', streak: 4, el: 4, pca: { exe: 65, tec: 60, sys: 55, col: 92, ini: 68 }, totalScore: 68, status: 'doing', module: 'M-DOC', lastActive: '2026-03-11T08:50:00Z' },
+ { id: 'DEV-007', name: '霜砚', streak: 10, el: 7, pca: { exe: 84, tec: 79, sys: 81, col: 75, ini: 82 }, totalScore: 80, status: 'blocked', module: 'M-API', lastActive: '2026-03-11T07:30:00Z' },
+ { id: 'DEV-008', name: '岚茵', streak: 6, el: 5, pca: { exe: 73, tec: 70, sys: 68, col: 85, ini: 74 }, totalScore: 74, status: 'pending', module: 'M-DESIGN', lastActive: '2026-03-10T14:20:00Z' }
+];
+
+function getTopStreak(developers) {
+ if (!developers || developers.length === 0) return { name: '无', count: 0 };
+ let top = developers[0];
+ for (let i = 1; i < developers.length; i++) {
+ if (developers[i].streak > top.streak) {
+ top = developers[i];
+ }
+ }
+ return { name: top.name, count: top.streak };
+}
+
+async function getDevStatus() {
+ return MOCK_DEVELOPERS;
+}
+
+async function getPCA(devId) {
+ const dev = MOCK_DEVELOPERS.find(d => d.id === devId);
+ if (!dev) return null;
+ return {
+ exe: dev.pca.exe,
+ tec: dev.pca.tec,
+ sys: dev.pca.sys,
+ col: dev.pca.col,
+ ini: dev.pca.ini,
+ total: dev.totalScore,
+ level: getPCAColor(dev.totalScore).level
+ };
+}
+
+async function getAllStats() {
+ const activeDevs = MOCK_DEVELOPERS.filter(d => d.status === 'doing').length;
+ const topStreak = getTopStreak(MOCK_DEVELOPERS);
+ return {
+ totalDevs: 8,
+ activeDevs: activeDevs,
+ totalCodeLines: 85600,
+ totalModules: 24,
+ modulesCompleted: 8,
+ modulesInProgress: 12,
+ modulesPending: 4,
+ topStreak: topStreak
+ };
+}
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/js/app.js b/agents/zhuyuan-dev-agent/modules/devboard/js/app.js
new file mode 100644
index 0000000..2184cef
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/js/app.js
@@ -0,0 +1,53 @@
+let refreshInterval;
+let countdownInterval;
+let seconds = 30;
+
+function updateLastUpdateTime() {
+ const el = document.getElementById('last-update-time');
+ if (el) {
+ const now = new Date();
+ el.textContent = `最后更新: ${now.toLocaleTimeString('zh-CN', { hour12: false })}`;
+ }
+}
+
+function updateCountdown() {
+ const el = document.getElementById('refresh-indicator');
+ if (el) el.textContent = `⏳ ${seconds}s`;
+}
+
+function startCountdown() {
+ seconds = 30;
+ updateCountdown();
+ if (countdownInterval) clearInterval(countdownInterval);
+ countdownInterval = setInterval(() => {
+ seconds--;
+ if (seconds <= 0) seconds = 30;
+ updateCountdown();
+ }, 1000);
+}
+
+async function refreshAllData() {
+ const devs = await getDevStatus();
+ const stats = await getAllStats();
+ if (typeof renderStats === 'function') renderStats(stats);
+ if (typeof renderDevCards === 'function') renderDevCards(devs);
+ if (typeof renderRanking === 'function') renderRanking(devs);
+ updateLastUpdateTime();
+}
+
+async function initApp() {
+ await initBoard();
+ await initRanking();
+ initRadar();
+ await refreshAllData();
+ startCountdown();
+ if (refreshInterval) clearInterval(refreshInterval);
+ refreshInterval = setInterval(refreshAllData, 30000);
+}
+
+document.addEventListener('DOMContentLoaded', initApp);
+
+window.addEventListener('beforeunload', () => {
+ if (refreshInterval) clearInterval(refreshInterval);
+ if (countdownInterval) clearInterval(countdownInterval);
+});
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/js/board.js b/agents/zhuyuan-dev-agent/modules/devboard/js/board.js
new file mode 100644
index 0000000..1df57d1
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/js/board.js
@@ -0,0 +1,72 @@
+function renderStats(stats) {
+ const container = document.getElementById('stats-container');
+ if (!container) return;
+ container.innerHTML = `
+
+
+
活跃开发者
+
${stats.activeDevs}
+
总人数 ${stats.totalDevs}
+
+
+
总代码量
+
${formatNumber(stats.totalCodeLines)}
+
行代码
+
+
+
模块进度
+
${stats.modulesCompleted}/${stats.totalModules}
+
✅ ${stats.modulesCompleted} / ⚡ ${stats.modulesInProgress} / ⏳ ${stats.modulesPending}
+
+
+
最高连胜
+
${stats.topStreak.count}
+
${stats.topStreak.name} ${getStreakEmoji(stats.topStreak.count)}
+
+
+ `;
+}
+
+function renderDevCards(developers) {
+ const container = document.getElementById('devgrid-container');
+ if (!container) return;
+ const sorted = [...developers].sort((a, b) => b.streak - a.streak);
+ let html = '';
+ sorted.forEach(dev => {
+ const pcaColor = getPCAColor(dev.totalScore);
+ html += `
+
+
+
📁 ${dev.module}
+
+
${dev.streak}连胜
+
+ EL-${dev.el}
+ ${pcaColor.level} · ${dev.totalScore}
+
+
+
+
+ `;
+ });
+ html += '
';
+ container.innerHTML = html;
+}
+
+async function initBoard() {
+ const devs = await getDevStatus();
+ const stats = await getAllStats();
+ renderStats(stats);
+ renderDevCards(devs);
+}
+
+window.initBoard = initBoard;
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/js/radar.js b/agents/zhuyuan-dev-agent/modules/devboard/js/radar.js
new file mode 100644
index 0000000..49c327c
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/js/radar.js
@@ -0,0 +1,112 @@
+function drawRadar(canvasId, pcaData) {
+ const canvas = document.getElementById(canvasId);
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ const w = canvas.width;
+ const h = canvas.height;
+ const cx = w / 2;
+ const cy = h / 2;
+ const maxR = Math.min(w, h) * 0.35;
+
+ ctx.clearRect(0, 0, w, h);
+
+ const dims = [
+ { label: '执行力', val: pcaData.exe || 0 },
+ { label: '技术纵深', val: pcaData.tec || 0 },
+ { label: '系统理解', val: pcaData.sys || 0 },
+ { label: '协作力', val: pcaData.col || 0 },
+ { label: '主动性', val: pcaData.ini || 0 }
+ ];
+
+ for (let lv = 1; lv <= 5; lv++) {
+ const r = (maxR * lv) / 5;
+ ctx.beginPath();
+ for (let i = 0; i < 5; i++) {
+ const angle = (i * 2 * Math.PI / 5) - Math.PI / 2;
+ const x = cx + r * Math.cos(angle);
+ const y = cy + r * Math.sin(angle);
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ }
+ ctx.closePath();
+ ctx.strokeStyle = '#2d3748';
+ ctx.stroke();
+ }
+
+ for (let i = 0; i < 5; i++) {
+ const angle = (i * 2 * Math.PI / 5) - Math.PI / 2;
+ ctx.beginPath();
+ ctx.moveTo(cx, cy);
+ ctx.lineTo(cx + maxR * Math.cos(angle), cy + maxR * Math.sin(angle));
+ ctx.strokeStyle = '#4a5568';
+ ctx.stroke();
+ }
+
+ ctx.fillStyle = '#cbd5e0';
+ ctx.font = '12px sans-serif';
+ ctx.textAlign = 'center';
+ for (let i = 0; i < 5; i++) {
+ const angle = (i * 2 * Math.PI / 5) - Math.PI / 2;
+ const x = cx + (maxR + 25) * Math.cos(angle);
+ const y = cy + (maxR + 25) * Math.sin(angle);
+ ctx.fillText(dims[i].label, x, y);
+ }
+
+ const points = [];
+ for (let i = 0; i < 5; i++) {
+ const angle = (i * 2 * Math.PI / 5) - Math.PI / 2;
+ const r = (dims[i].val / 100) * maxR;
+ points.push({
+ x: cx + r * Math.cos(angle),
+ y: cy + r * Math.sin(angle)
+ });
+ }
+
+ ctx.beginPath();
+ ctx.moveTo(points[0].x, points[0].y);
+ for (let i = 1; i < 5; i++) ctx.lineTo(points[i].x, points[i].y);
+ ctx.closePath();
+ ctx.fillStyle = 'rgba(0, 180, 216, 0.3)';
+ ctx.fill();
+ ctx.strokeStyle = '#00b4d8';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ points.forEach(p => {
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, 4, 0, 2 * Math.PI);
+ ctx.fillStyle = '#fff';
+ ctx.fill();
+ ctx.strokeStyle = '#00b4d8';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ });
+
+ ctx.fillStyle = '#fff';
+ ctx.font = 'bold 24px sans-serif';
+ ctx.textAlign = 'center';
+ ctx.fillText(pcaData.total || '0', cx, cy - 10);
+
+ ctx.font = 'bold 14px sans-serif';
+ const colorInfo = getPCAColor(pcaData.total || 0);
+ ctx.fillStyle = colorInfo.color;
+ ctx.fillText(pcaData.level || 'C', cx, cy + 15);
+}
+
+async function loadRadarForDev(devId) {
+ const pcaData = await getPCA(devId);
+ if (pcaData) drawRadar('pca-canvas', pcaData);
+}
+
+function initRadar() {
+ document.querySelectorAll('.dev-card').forEach(card => {
+ card.addEventListener('click', () => {
+ loadRadarForDev(card.dataset.devId);
+ });
+ });
+ loadRadarForDev('DEV-004');
+}
+
+window.initRadar = initRadar;
+window.loadRadarForDev = loadRadarForDev;
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/js/ranking.js b/agents/zhuyuan-dev-agent/modules/devboard/js/ranking.js
new file mode 100644
index 0000000..fb14624
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/js/ranking.js
@@ -0,0 +1,34 @@
+function renderRanking(developers) {
+ const container = document.getElementById('ranking-container');
+ if (!container) return;
+ const sorted = [...developers].sort((a, b) => b.streak - a.streak);
+ const maxStreak = sorted[0].streak;
+
+ let html = '';
+
+ sorted.forEach((dev, idx) => {
+ const medal = idx === 0 ? '🥇' : idx === 1 ? '🥈' : idx === 2 ? '🥉' : `${idx+1}.`;
+ const percent = (dev.streak / maxStreak) * 100;
+ html += `
+
+ `;
+ });
+ html += '
';
+ container.innerHTML = html;
+}
+
+async function initRanking() {
+ const devs = await getDevStatus();
+ renderRanking(devs);
+}
+
+window.initRanking = initRanking;
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/js/utils.js b/agents/zhuyuan-dev-agent/modules/devboard/js/utils.js
new file mode 100644
index 0000000..a705c93
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/js/utils.js
@@ -0,0 +1,36 @@
+function formatDate(isoString) {
+ if (!isoString) return '--:--:--';
+ const date = new Date(isoString);
+ return date.toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
+}
+
+function getStreakEmoji(count) {
+ if (count >= 20) return '🔥🔥🔥';
+ if (count >= 15) return '🔥🔥';
+ if (count >= 10) return '🔥';
+ if (count >= 5) return '⚡';
+ if (count >= 3) return '👍';
+ return '👣';
+}
+
+function getPCAColor(score) {
+ if (score >= 90) return { color: '#FFD700', level: 'S', text: '传奇' };
+ if (score >= 80) return { color: '#00B4D8', level: 'A', text: '精英' };
+ if (score >= 70) return { color: '#10B981', level: 'B', text: '专家' };
+ if (score >= 60) return { color: '#F59E0B', level: 'C', text: '熟手' };
+ return { color: '#EF4444', level: 'D', text: '新手' };
+}
+
+function getStatusBadge(status) {
+ const map = {
+ 'done': '✅ 已完成',
+ 'doing': '⚡ 进行中',
+ 'pending': '⏳ 待分配',
+ 'blocked': '⚠️ 阻塞'
+ };
+ return map[status] || '⏳ 待分配';
+}
+
+function formatNumber(num) {
+ return num?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') || '0';
+}
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/main.js b/agents/zhuyuan-dev-agent/modules/devboard/main.js
new file mode 100644
index 0000000..3ace862
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/main.js
@@ -0,0 +1,236 @@
+// main.js - 主逻辑(增强版)
+
+// 页面加载完成后初始化
+document.addEventListener('DOMContentLoaded', function() {
+ // 初始渲染
+ refreshDashboard();
+
+ // 搜索功能(带防抖和动画)
+ const searchInput = document.getElementById('search-input');
+ if (searchInput) {
+ let searchTimer;
+ searchInput.addEventListener('input', function(e) {
+ clearTimeout(searchTimer);
+
+ // 添加输入时的微动效
+ this.style.transform = 'scale(1.02)';
+ setTimeout(() => {
+ this.style.transform = '';
+ }, 150);
+
+ searchTimer = setTimeout(() => {
+ applyFilters();
+ }, 300);
+ });
+ }
+
+ // 筛选按钮
+ const filterBtns = document.querySelectorAll('.filter-btn');
+ filterBtns.forEach(btn => {
+ btn.addEventListener('click', function() {
+ // 按钮点击动画
+ this.style.transform = 'scale(0.95)';
+ setTimeout(() => {
+ this.style.transform = '';
+ }, 150);
+
+ // 更新按钮状态
+ filterBtns.forEach(b => b.classList.remove('active'));
+ this.classList.add('active');
+
+ // 应用筛选
+ applyFilters();
+ });
+ });
+
+ // 卡片点击事件(使用事件委托)
+ document.addEventListener('click', function(e) {
+ const card = e.target.closest('.dev-card');
+ if (card) {
+ const devId = card.dataset.devId;
+ if (devId && window.showDetailPage) {
+ // 添加点击动画
+ card.style.transform = 'scale(0.98)';
+ card.style.opacity = '0.8';
+
+ setTimeout(() => {
+ card.style.transform = '';
+ card.style.opacity = '';
+ window.showDetailPage(devId);
+ }, 150);
+ }
+ }
+ });
+
+ // 键盘导航
+ document.addEventListener('keydown', function(e) {
+ // 获取所有可聚焦的卡片
+ const cards = Array.from(document.querySelectorAll('.dev-card'));
+ const currentIndex = cards.indexOf(document.activeElement);
+
+ // Tab键(默认行为已支持,我们只加样式)
+ if (e.key === 'Tab') {
+ setTimeout(() => {
+ const focused = document.activeElement;
+ if (focused.classList.contains('dev-card')) {
+ focused.style.outline = '3px solid #ffd700';
+ focused.style.transform = 'scale(1.02)';
+ }
+ }, 50);
+ }
+
+ // 箭头键导航
+ if (e.key === 'ArrowRight' || e.key === 'ArrowLeft' ||
+ e.key === 'ArrowDown' || e.key === 'ArrowUp') {
+ e.preventDefault();
+
+ if (cards.length === 0) return;
+
+ const cols = Math.floor(document.querySelector('.developers-grid').offsetWidth / 300);
+ let nextIndex;
+
+ if (e.key === 'ArrowRight') {
+ nextIndex = currentIndex + 1;
+ } else if (e.key === 'ArrowLeft') {
+ nextIndex = currentIndex - 1;
+ } else if (e.key === 'ArrowDown') {
+ nextIndex = currentIndex + cols;
+ } else if (e.key === 'ArrowUp') {
+ nextIndex = currentIndex - cols;
+ }
+
+ if (nextIndex >= 0 && nextIndex < cards.length) {
+ cards[nextIndex].focus();
+ }
+ }
+
+ // Enter/Space 打开详情
+ if (e.key === 'Enter' || e.key === ' ') {
+ const target = e.target.closest('.dev-card');
+ if (target) {
+ e.preventDefault();
+ const devId = target.dataset.devId;
+ if (devId && window.showDetailPage) {
+ window.showDetailPage(devId);
+ }
+ }
+ }
+
+ // ESC返回总览
+ if (e.key === 'Escape') {
+ const detail = document.getElementById('detail-container');
+ if (detail && detail.style.display !== 'none' && window.hideDetailPage) {
+ window.hideDetailPage();
+ }
+ }
+ });
+
+ // 失去焦点时移除高亮
+ document.addEventListener('blur', function(e) {
+ if (e.target.classList.contains('dev-card')) {
+ e.target.style.outline = '';
+ e.target.style.transform = '';
+ }
+ }, true);
+});
+
+// 应用搜索和筛选
+function applyFilters() {
+ const searchTerm = document.getElementById('search-input')?.value || '';
+ const activeFilter = document.querySelector('.filter-btn.active')?.dataset.filter || 'all';
+
+ // 获取筛选后的开发者
+ const filtered = filterDevelopers(searchTerm, activeFilter);
+
+ // 重新渲染卡片
+ const grid = document.getElementById('developers-grid');
+ if (grid) {
+ // 添加淡出动画
+ grid.style.opacity = '0.5';
+
+ setTimeout(() => {
+ if (filtered.length === 0) {
+ // 显示无结果提示
+ grid.innerHTML = '🔍 没有找到匹配的开发者
';
+ } else {
+ grid.innerHTML = renderDeveloperCards(filtered);
+
+ // 添加展开/收起动画
+ Array.from(grid.children).forEach((card, index) => {
+ card.style.animation = `cardPopIn 0.3s ease ${index * 0.05}s forwards`;
+ card.style.opacity = '0';
+ setTimeout(() => {
+ card.style.opacity = '1';
+ }, 50 + index * 20);
+ });
+ }
+
+ grid.style.opacity = '1';
+ }, 200);
+ }
+
+ // 更新排行榜(排行榜不筛选,但高亮匹配项)
+ const ranking = document.getElementById('ranking-list');
+ if (ranking) {
+ const allDevs = getDevelopers();
+ ranking.innerHTML = renderRanking(allDevs);
+
+ // 高亮筛选出的开发者
+ if (filtered.length < allDevs.length && filtered.length > 0) {
+ const filteredIds = filtered.map(d => d.id);
+ document.querySelectorAll('.ranking-item').forEach(item => {
+ const nameEl = item.querySelector('.rank-name');
+ if (nameEl) {
+ const dev = allDevs.find(d => d.name === nameEl.textContent);
+ if (dev && filteredIds.includes(dev.id)) {
+ item.classList.add('match-highlight');
+
+ // 添加数字滚动动画
+ const winsEl = item.querySelector('.rank-wins');
+ if (winsEl) {
+ const oldValue = parseInt(winsEl.textContent) || 0;
+ animateNumber(winsEl, oldValue, dev.wins, 500);
+ }
+ } else {
+ item.classList.remove('match-highlight');
+ }
+ }
+ });
+ } else {
+ document.querySelectorAll('.ranking-item').forEach(item => {
+ item.classList.remove('match-highlight');
+ });
+ }
+ }
+}
+
+// 数字滚动动画
+function animateNumber(element, start, end, duration = 1000) {
+ const range = end - start;
+ const increment = range / (duration / 16);
+ let current = start;
+ const startTime = performance.now();
+
+ function update(currentTime) {
+ const elapsed = currentTime - startTime;
+ const progress = Math.min(elapsed / duration, 1);
+
+ // 缓动函数
+ const easeOutQuart = 1 - Math.pow(1 - progress, 3);
+ current = start + (range * easeOutQuart);
+
+ element.textContent = Math.round(current) + (element.textContent.includes('连胜') ? '连胜' : '');
+
+ if (progress < 1) {
+ requestAnimationFrame(update);
+ } else {
+ element.textContent = end + (element.textContent.includes('连胜') ? '连胜' : '');
+ }
+ }
+
+ requestAnimationFrame(update);
+}
+
+// 导出函数
+window.applyFilters = applyFilters;
+window.animateNumber = animateNumber;
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/radar.js b/agents/zhuyuan-dev-agent/modules/devboard/radar.js
new file mode 100644
index 0000000..2cd2d86
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/radar.js
@@ -0,0 +1,116 @@
+// radar.js - 雷达图绘制
+function drawRadarChart(canvasId, data) {
+ const canvas = document.getElementById(canvasId);
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ const width = canvas.width;
+ const height = canvas.height;
+ const centerX = width / 2;
+ const centerY = height / 2;
+ const radius = Math.min(width, height) * 0.35;
+
+ const dimensions = ['EXE', 'TEC', 'SYS', 'COL', 'INI'];
+ const angleStep = (Math.PI * 2) / dimensions.length;
+
+ // 清空画布
+ ctx.clearRect(0, 0, width, height);
+
+ // 绘制背景网格
+ ctx.strokeStyle = '#2a3040';
+ ctx.lineWidth = 1;
+
+ // 绘制5层同心圆
+ for (let level = 1; level <= 5; level++) {
+ ctx.beginPath();
+ for (let i = 0; i <= dimensions.length; i++) {
+ const angle = i * angleStep - Math.PI / 2;
+ const x = centerX + radius * (level / 5) * Math.cos(angle);
+ const y = centerY + radius * (level / 5) * Math.sin(angle);
+
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ }
+ ctx.closePath();
+ ctx.strokeStyle = '#2a3040';
+ ctx.stroke();
+ }
+
+ // 绘制轴线
+ for (let i = 0; i < dimensions.length; i++) {
+ const angle = i * angleStep - Math.PI / 2;
+ const x = centerX + radius * Math.cos(angle);
+ const y = centerY + radius * Math.sin(angle);
+
+ ctx.beginPath();
+ ctx.moveTo(centerX, centerY);
+ ctx.lineTo(x, y);
+ ctx.strokeStyle = '#3a4050';
+ ctx.stroke();
+
+ // 标注维度
+ const labelX = centerX + (radius + 15) * Math.cos(angle);
+ const labelY = centerY + (radius + 15) * Math.sin(angle);
+ ctx.fillStyle = '#4a9eff';
+ ctx.font = 'bold 14px monospace';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(dimensions[i], labelX, labelY);
+ }
+
+ // 绘制数据
+ if (data) {
+ ctx.beginPath();
+ for (let i = 0; i < dimensions.length; i++) {
+ const value = data[dimensions[i]] || 0;
+ const angle = i * angleStep - Math.PI / 2;
+ const r = radius * (value / 100);
+ const x = centerX + r * Math.cos(angle);
+ const y = centerY + r * Math.sin(angle);
+
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ }
+ ctx.closePath();
+ ctx.fillStyle = 'rgba(74, 158, 255, 0.3)';
+ ctx.fill();
+ ctx.strokeStyle = '#4a9eff';
+ ctx.lineWidth = 3;
+ ctx.stroke();
+
+ // 绘制数据点
+ for (let i = 0; i < dimensions.length; i++) {
+ const value = data[dimensions[i]] || 0;
+ const angle = i * angleStep - Math.PI / 2;
+ const r = radius * (value / 100);
+ const x = centerX + r * Math.cos(angle);
+ const y = centerY + r * Math.sin(angle);
+
+ ctx.beginPath();
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
+ ctx.fillStyle = '#fff';
+ ctx.fill();
+ ctx.strokeStyle = '#4a9eff';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ }
+ }
+}
+
+// 渲染雷达图HTML(供detail.js调用)
+function renderRadarChart(pcaData, size = 'normal') {
+ const canvasId = 'radar-' + Math.random().toString(36).substr(2, 9);
+ const width = size === 'large' ? 400 : 300;
+ const height = size === 'large' ? 300 : 250;
+
+ setTimeout(() => {
+ const canvas = document.getElementById(canvasId);
+ if (canvas) drawRadarChart(canvas.id, pcaData);
+ }, 100);
+
+ return ``;
+}
+
+// 导出函数
+window.drawRadarChart = drawRadarChart;
+window.renderRadarChart = renderRadarChart;
diff --git a/agents/zhuyuan-dev-agent/modules/devboard/style.css b/agents/zhuyuan-dev-agent/modules/devboard/style.css
new file mode 100644
index 0000000..18ed43c
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/devboard/style.css
@@ -0,0 +1,287 @@
+/* style.css - 基础样式 */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
+ background: #0a0c14;
+ color: #e0e0e0;
+ line-height: 1.6;
+}
+
+/* 总览页容器 */
+#overview-container {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 20px;
+}
+
+/* 头部 */
+.dashboard-header {
+ text-align: center;
+ margin-bottom: 30px;
+ padding: 20px;
+ background: linear-gradient(135deg, #1a1f2e, #0f1219);
+ border-radius: 20px;
+ border: 1px solid #2a3040;
+}
+
+.dashboard-header h1 {
+ color: #4a9eff;
+ font-size: 2em;
+ margin-bottom: 10px;
+ text-shadow: 0 0 20px rgba(74, 158, 255, 0.3);
+}
+
+/* 控制区 */
+.controls-section {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 30px;
+ flex-wrap: wrap;
+}
+
+.search-box {
+ flex: 1;
+ min-width: 250px;
+ padding: 12px 20px;
+ background: #1a1f2e;
+ border: 2px solid #2a3040;
+ border-radius: 30px;
+ color: #fff;
+ font-size: 16px;
+ transition: all 0.3s;
+}
+
+.search-box:focus {
+ outline: none;
+ border-color: #4a9eff;
+ box-shadow: 0 0 20px rgba(74, 158, 255, 0.2);
+}
+
+.filter-buttons {
+ display: flex;
+ gap: 10px;
+}
+
+.filter-btn {
+ padding: 10px 24px;
+ background: #1a1f2e;
+ border: 2px solid #2a3040;
+ border-radius: 30px;
+ color: #a0a0a0;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+.filter-btn.active {
+ background: #4a9eff;
+ border-color: #4a9eff;
+ color: #fff;
+}
+
+.filter-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(74, 158, 255, 0.2);
+}
+
+/* 开发者网格 */
+.developers-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 20px;
+ margin-bottom: 40px;
+}
+
+/* 统计区 */
+.stats-section {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+}
+
+.radar-container, .ranking-container {
+ background: #1a1f2e;
+ border-radius: 20px;
+ padding: 20px;
+ border: 1px solid #2a3040;
+}
+
+.radar-container h2, .ranking-container h2 {
+ color: #4a9eff;
+ margin-bottom: 20px;
+ font-size: 1.2em;
+}
+
+#team-radar {
+ width: 100%;
+ height: auto;
+ max-height: 300px;
+}
+
+/* 响应式 */
+@media (max-width: 768px) {
+ .stats-section {
+ grid-template-columns: 1fr;
+ }
+
+ .controls-section {
+ flex-direction: column;
+ }
+
+ .filter-buttons {
+ justify-content: center;
+ }
+}
+
+/* 动画 */
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(20px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.fade-in {
+ animation: fadeIn 0.5s ease forwards;
+}
+
+/* ===== 交互增强样式 ===== */
+
+/* 搜索框增强 */
+.search-box {
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.search-box:focus {
+ transform: scale(1.02);
+ background: #1e2436;
+}
+
+/* 筛选按钮动画 */
+.filter-btn {
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+ overflow: hidden;
+}
+
+.filter-btn::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.3);
+ transform: translate(-50%, -50%);
+ transition: width 0.3s, height 0.3s;
+}
+
+.filter-btn:active::after {
+ width: 100px;
+ height: 100px;
+}
+
+/* 卡片网格动画 */
+.developers-grid {
+ transition: opacity 0.3s ease;
+ min-height: 400px;
+}
+
+/* 卡片展开/收起动画 */
+.dev-card {
+ animation: cardPopIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ transform-origin: center;
+}
+
+@keyframes cardPopIn {
+ 0% {
+ opacity: 0;
+ transform: scale(0.8) translateY(20px);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+ }
+}
+
+/* 键盘焦点样式 */
+.dev-card:focus-visible {
+ outline: 3px solid #ffd700;
+ outline-offset: 2px;
+ transform: scale(1.02);
+ box-shadow: 0 0 30px rgba(255, 215, 0, 0.3);
+}
+
+/* 数字滚动动画 */
+.stat-value {
+ display: inline-block;
+ transition: all 0.3s ease;
+ font-feature-settings: "tnum";
+}
+
+/* 无结果提示 */
+.no-results {
+ grid-column: 1 / -1;
+ text-align: center;
+ padding: 60px;
+ background: rgba(255, 255, 255, 0.05);
+ border-radius: 20px;
+ color: #888;
+ font-size: 18px;
+ border: 2px dashed #2a3040;
+ animation: fadeIn 0.5s ease;
+}
+
+/* 高亮筛选匹配项 */
+.ranking-item.match-highlight {
+ background: rgba(74, 158, 255, 0.2);
+ border-left: 4px solid #4a9eff;
+ transform: translateX(5px);
+}
+
+/* 淡入淡出动画增强 */
+.fade-in {
+ animation: fadeIn 0.3s ease forwards;
+}
+
+.fade-out {
+ animation: fadeOut 0.3s ease forwards;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes fadeOut {
+ from {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ to {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+}
+
+/* 响应式优化 */
+@media (max-width: 768px) {
+ .dev-card:focus-visible {
+ transform: scale(1.01);
+ }
+
+ .filter-btn:active::after {
+ width: 60px;
+ height: 60px;
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/email-notify.js b/agents/zhuyuan-dev-agent/modules/email-notify.js
new file mode 100644
index 0000000..9b3f565
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/email-notify.js
@@ -0,0 +1,90 @@
+// ═══════════════════════════════════════
+// 铸渊开发Agent · 邮件通知模块
+// QQ邮箱 SMTP · smtp.qq.com:465
+// ═══════════════════════════════════════
+
+const nodemailer = require("nodemailer");
+
+let transporter = null;
+
+function getTransporter() {
+ if (transporter) return transporter;
+
+ const user = process.env.ZY_SMTP_USER;
+ const pass = process.env.ZY_SMTP_PASS;
+
+ if (!user || !pass) {
+ console.warn("[EmailNotify] ZY_SMTP_USER 或 ZY_SMTP_PASS 未配置,邮件功能不可用");
+ return null;
+ }
+
+ transporter = nodemailer.createTransport({
+ host: "smtp.qq.com",
+ port: 465,
+ secure: true,
+ auth: { user, pass }
+ });
+
+ console.log("[EmailNotify] SMTP 已连接 · " + user);
+ return transporter;
+}
+
+/**
+ * 发送通知邮件
+ * @param {string} to - 收件人邮箱
+ * @param {string} planId - 规划ID
+ * @param {string} status - 状态: started / completed / failed
+ * @param {object} summary - { task_count, done_count, failed_count, errors, log_url }
+ */
+async function sendNotification(to, planId, status, summary = {}) {
+ const t = getTransporter();
+ if (!t) return { sent: false, reason: "SMTP未配置" };
+
+ const statusEmoji = { started: "🚀", completed: "✅", failed: "❌" };
+ const emoji = statusEmoji[status] || "📋";
+
+ const subject = emoji + " 铸渊DevAgent · " + planId + " · " + status;
+
+ const lines = [
+ "铸渊开发Agent · 任务执行报告",
+ "══════════════════════════════",
+ "",
+ "规划ID: " + planId,
+ "状态: " + status,
+ "时间: " + new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
+ "",
+ ];
+
+ if (summary.task_count !== undefined) {
+ lines.push("任务总数: " + summary.task_count);
+ lines.push("已完成: " + (summary.done_count || 0));
+ lines.push("失败: " + (summary.failed_count || 0));
+ }
+
+ if (summary.errors && summary.errors.length > 0) {
+ lines.push("", "错误详情:");
+ summary.errors.forEach(function(e, i) {
+ lines.push(" " + (i + 1) + ". " + e);
+ });
+ }
+
+ lines.push("", "══════════════════════════════", "铸渊 ICE-GL-ZY001 · 国作登字-2026-A-00037559");
+
+ const mailOptions = {
+ from: process.env.ZY_SMTP_USER,
+ to: to,
+ subject: subject,
+ text: lines.join("\n")
+ };
+
+ try {
+ const info = await t.sendMail(mailOptions);
+ console.log("[EmailNotify] 邮件已发送: " + info.messageId);
+ return { sent: true, messageId: info.messageId };
+ } catch (err) {
+ console.error("[EmailNotify] 发送失败:", err.message);
+ return { sent: false, reason: err.message };
+ }
+}
+
+module.exports = { sendNotification };
diff --git a/agents/zhuyuan-dev-agent/modules/executor.js b/agents/zhuyuan-dev-agent/modules/executor.js
new file mode 100644
index 0000000..8ebde7d
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/executor.js
@@ -0,0 +1,177 @@
+// ═══════════════════════════════════════
+// 铸渊开发Agent · 执行引擎 v2.0
+// 宪法约束:3次重试上限 · 统一输出接口 · 依赖链 · 紧急停止
+// ═══════════════════════════════════════
+
+const fs = require("fs");
+const path = require("path");
+const { execSync } = require("child_process");
+
+const WORKSPACE = process.env.WORKSPACE_DIR || "/opt/guanghu/zhuyuan-workspace";
+const CONSTITUTION = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "agent-constitution.json"), "utf-8"));
+const { fail_protocol, unified_interface, dependency_chain, emergency_stop } = CONSTITUTION;
+
+if (!fs.existsSync(WORKSPACE)) fs.mkdirSync(WORKSPACE, { recursive: true });
+
+/**
+ * 统一包装输出——所有模块输出必须经过这里
+ */
+function uniformResult(taskId, step, ok, extra = {}) {
+ return {
+ ok,
+ task_id: taskId,
+ step,
+ action: extra.action || "unknown",
+ output: extra.output || null,
+ error: extra.error || null,
+ retries: extra.retries || 0,
+ skipped: !!extra.skipped,
+ blocked: !!extra.blocked
+ };
+}
+
+/**
+ * 带重试的执行
+ */
+async function executeWithRetry(substep, logFn, retriesLeft) {
+ const start = Date.now();
+ const attempt = fail_protocol.max_retries_per_task - retriesLeft + 1;
+ logFn("exec_attempt", { step: substep.step, action: substep.action, attempt, retriesLeft });
+
+ try {
+ let result;
+ switch (substep.action) {
+ case "write_file":
+ result = doWriteFile(substep);
+ break;
+ case "run_cmd":
+ result = doRunCmd(substep);
+ break;
+ case "call_api":
+ result = await doCallApi(substep);
+ break;
+ case "check":
+ result = doCheck(substep);
+ break;
+ default:
+ return uniformResult(substep.task_id, substep.step, false, { action: substep.action, error: "未知动作类型: " + substep.action });
+ }
+
+ if (result.ok) {
+ logFn("exec_done", { step: substep.step, result: "ok", ms: Date.now() - start, attempt });
+ return uniformResult(substep.task_id, substep.step, true, { action: substep.action, output: result.output || result.path, retries: attempt - 1 });
+ }
+
+ // 失败了但还有重试次数
+ if (retriesLeft > 1) {
+ logFn("exec_retry", { step: substep.step, error: result.error, retriesLeft: retriesLeft - 1 });
+ return executeWithRetry(substep, logFn, retriesLeft - 1);
+ }
+
+ // 重试耗尽 → 跳过
+ logFn("exec_skip", { step: substep.step, error: result.error, reason: "重试" + fail_protocol.max_retries_per_task + "次全部失败,跳过" });
+ return uniformResult(substep.task_id, substep.step, false, { action: substep.action, error: result.error, retries: fail_protocol.max_retries_per_task, skipped: true });
+
+ } catch (err) {
+ if (retriesLeft > 1) {
+ logFn("exec_retry", { step: substep.step, error: err.message, retriesLeft: retriesLeft - 1 });
+ return executeWithRetry(substep, logFn, retriesLeft - 1);
+ }
+ logFn("exec_skip", { step: substep.step, error: err.message, reason: "重试" + fail_protocol.max_retries_per_task + "次全部失败,跳过" });
+ return uniformResult(substep.task_id, substep.step, false, { action: substep.action, error: err.message, retries: fail_protocol.max_retries_per_task, skipped: true });
+ }
+}
+
+function doWriteFile(s) {
+ const fp = path.resolve(WORKSPACE, s.target);
+ const dir = path.dirname(fp);
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
+ fs.writeFileSync(fp, s.content || "", "utf-8");
+ return { ok: true, path: fp };
+}
+
+function doRunCmd(s) {
+ const out = execSync(s.target, { cwd: s.cwd || WORKSPACE, encoding: "utf-8", timeout: 60000, maxBuffer: 10*1024*1024 });
+ return { ok: true, output: out.slice(0, 5000) };
+}
+
+async function doCallApi(s) {
+ const https = require("https");
+ const body = JSON.stringify(s.body || {});
+ const url = new URL(s.target);
+ return new Promise((resolve) => {
+ const req = https.request({
+ hostname: url.hostname, path: url.pathname, method: s.method || "POST",
+ headers: { "Content-Type": "application/json", ...(s.headers || {}) }
+ }, (res) => {
+ let data = "";
+ res.on("data", c => data += c);
+ res.on("end", () => resolve({ ok: res.statusCode < 400, status: res.statusCode, body: data.slice(0, 2000) }));
+ });
+ req.on("error", e => resolve({ ok: false, error: e.message }));
+ if (body !== "{}") req.write(body);
+ req.end();
+ });
+}
+
+function doCheck(s) {
+ const fp = path.resolve(WORKSPACE, s.target);
+ return { ok: fs.existsSync(fp), exists: fs.existsSync(fp), path: fp };
+}
+
+/**
+ * 执行完整规划——宪法约束版本
+ */
+async function executePlan(plan, logFn) {
+ console.log("[Executor v2] 开始执行: " + plan.plan_id + " · 宪法约束模式");
+ logFn("constitution_loaded", { max_retries: fail_protocol.max_retries_per_task });
+
+ const results = [];
+ let doneCount = 0, failCount = 0, skipCount = 0, consecutiveFails = 0;
+ const blockedTasks = new Set();
+
+ for (const task of plan.tasks) {
+ const substeps = task.substeps || [];
+
+ for (const ss of substeps) {
+ // 检查是否被阻塞
+ if (ss.depends_on && blockedTasks.has(ss.depends_on)) {
+ const blocked = uniformResult(task.id, ss.step, false, { action: ss.action, blocked: true, error: "上游依赖 " + ss.depends_on + " 失败,此任务阻塞" });
+ results.push(blocked);
+ logFn("exec_blocked", { step: ss.step, depends_on: ss.depends_on });
+ failCount++;
+ continue;
+ }
+
+ // 执行(带重试)
+ const r = await executeWithRetry({ ...ss, task_id: task.id }, logFn, fail_protocol.max_retries_per_task);
+ results.push(r);
+
+ if (r.ok) {
+ doneCount++;
+ consecutiveFails = 0;
+ } else if (r.skipped) {
+ skipCount++;
+ consecutiveFails++;
+ // 被跳过的任务 → 下游阻塞
+ blockedTasks.add(task.id + ":" + ss.step);
+ } else {
+ failCount++;
+ consecutiveFails++;
+ blockedTasks.add(task.id + ":" + ss.step);
+ }
+
+ // 紧急停止检查
+ if (consecutiveFails >= 5) {
+ logFn("emergency_stop", { reason: "连续" + consecutiveFails + "个任务失败", summary: { done: doneCount, fail: failCount, skip: skipCount } });
+ return { plan_id: plan.plan_id, emergency_stop: true, done: doneCount, failed: failCount, skipped: skipCount, results };
+ }
+ }
+ }
+
+ const summary = { plan_id: plan.plan_id, total: results.length, done: doneCount, failed: failCount, skipped: skipCount, results };
+ console.log("[Executor v2] 完成 · 成功 " + doneCount + " / 失败 " + failCount + " / 跳过 " + skipCount);
+ return summary;
+}
+
+module.exports = { executePlan };
diff --git a/agents/zhuyuan-dev-agent/modules/logger.js b/agents/zhuyuan-dev-agent/modules/logger.js
new file mode 100644
index 0000000..962b39c
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/logger.js
@@ -0,0 +1,97 @@
+// ═══════════════════════════════════════
+// 铸渊开发Agent · 日志系统
+// 结构化日志 · 铸渊醒来能看懂Agent做了什么
+// ═══════════════════════════════════════
+
+const fs = require("fs");
+const path = require("path");
+
+const LOGS_DIR = process.env.LOGS_DIR || path.join(__dirname, "..", "logs");
+
+if (!fs.existsSync(LOGS_DIR)) fs.mkdirSync(LOGS_DIR, { recursive: true });
+
+/**
+ * 创建一次执行的日志记录器
+ */
+function createLogger(planId) {
+ const runId = planId + "-" + Date.now();
+ const logFile = path.join(LOGS_DIR, runId + ".jsonl");
+ const startTime = new Date().toISOString();
+
+ // 写入启动记录
+ appendLine(logFile, { type: "run_start", plan_id: planId, run_id: runId, time: startTime });
+
+ const log = (type, data) => {
+ const entry = {
+ time: new Date().toISOString(),
+ type: type,
+ ...data
+ };
+ appendLine(logFile, entry);
+ console.log("[Log:" + planId + "] " + type + " " + JSON.stringify(data).slice(0, 100));
+ };
+
+ // 返回日志函数和元信息
+ return {
+ log,
+ runId,
+ logFile,
+ finish: (summary) => {
+ appendLine(logFile, { type: "run_end", time: new Date().toISOString(), summary });
+ return logFile;
+ }
+ };
+}
+
+function appendLine(file, obj) {
+ fs.appendFileSync(file, JSON.stringify(obj) + "\n", "utf-8");
+}
+
+/**
+ * 读取最近一次执行日志摘要(给铸渊看)
+ */
+function readLatestRun() {
+ const files = fs.readdirSync(LOGS_DIR)
+ .filter(f => f.endsWith(".jsonl"))
+ .sort()
+ .reverse();
+
+ if (files.length === 0) return null;
+
+ const latest = path.join(LOGS_DIR, files[0]);
+ const content = fs.readFileSync(latest, "utf-8");
+ const lines = content.trim().split("\n").map(l => {
+ try { return JSON.parse(l); } catch (e) { return null; }
+ }).filter(Boolean);
+
+ const startLine = lines.find(l => l.type === "run_start");
+ const endLine = lines.find(l => l.type === "run_end");
+ const errors = lines.filter(l => l.type === "exec_error" || l.type === "exec_done" && l.result === "fail");
+
+ return {
+ plan_id: startLine?.plan_id,
+ run_id: startLine?.run_id,
+ started: startLine?.time,
+ ended: endLine?.time,
+ total_steps: lines.filter(l => l.type === "exec_start").length,
+ errors: errors.map(e => ({ step: e.step, error: e.error || "未知" })),
+ log_file: latest
+ };
+}
+
+/**
+ * 列出所有执行记录
+ */
+function listRuns(limit = 20) {
+ return fs.readdirSync(LOGS_DIR)
+ .filter(f => f.endsWith(".jsonl"))
+ .sort()
+ .reverse()
+ .slice(0, limit)
+ .map(f => {
+ const stat = fs.statSync(path.join(LOGS_DIR, f));
+ return { file: f, size: stat.size, modified: stat.mtime.toISOString() };
+ });
+}
+
+module.exports = { createLogger, readLatestRun, listRuns };
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m06-adapter.js b/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m06-adapter.js
new file mode 100644
index 0000000..4651cb1
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m06-adapter.js
@@ -0,0 +1,61 @@
+// M06 工单管理模块适配器
+window.M06Adapter = {
+ moduleId: 'm06',
+ moduleName: '工单管理',
+
+ // 初始化模块(由 ModuleAdapter 调用)
+ init: function(containerId) {
+ console.log('[M06Adapter] 初始化');
+
+ // 通过核心适配器加载
+ if (window.ModuleAdapter) {
+ return ModuleAdapter.loadModule(this.moduleId, containerId);
+ } else {
+ console.error('[M06Adapter] ModuleAdapter 未加载');
+ return null;
+ }
+ },
+
+ // 发送消息到工单模块
+ sendMessage: function(type, payload) {
+ return ModuleAdapter.sendMessage(this.moduleId, { type, payload });
+ },
+
+ // 监听工单模块事件
+ onTicketCreate: function(callback) {
+ EventBus.on('module:m06:message', function(data) {
+ if (data.type === 'ticket:create') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ onTicketUpdate: function(callback) {
+ EventBus.on('module:m06:message', function(data) {
+ if (data.type === 'ticket:update') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ onTicketDelete: function(callback) {
+ EventBus.on('module:m06:message', function(data) {
+ if (data.type === 'ticket:delete') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ // 销毁模块
+ destroy: function() {
+ ModuleAdapter.unloadModule(this.moduleId);
+ }
+};
+
+// 注册到模块注册表
+if (window.ModuleRegistry) {
+ ModuleRegistry.register('m06', window.M06Adapter);
+ console.log('[M06Adapter] 已注册到模块注册表');
+}
+
+console.log('[M06Adapter] 已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m08-adapter.js b/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m08-adapter.js
new file mode 100644
index 0000000..b549438
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m08-adapter.js
@@ -0,0 +1,47 @@
+// M08 数据统计模块适配器
+window.M08Adapter = {
+ moduleId: 'm08',
+ moduleName: '数据统计',
+
+ init: function(containerId) {
+ console.log('[M08Adapter] 初始化');
+ if (window.ModuleAdapter) {
+ return ModuleAdapter.loadModule(this.moduleId, containerId);
+ } else {
+ console.error('[M08Adapter] ModuleAdapter 未加载');
+ return null;
+ }
+ },
+
+ sendMessage: function(type, payload) {
+ return ModuleAdapter.sendMessage(this.moduleId, { type, payload });
+ },
+
+ // 监听统计模块事件
+ onStatsRefresh: function(callback) {
+ EventBus.on('module:m08:message', function(data) {
+ if (data.type === 'stats:refresh') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ onStatsExport: function(callback) {
+ EventBus.on('module:m08:message', function(data) {
+ if (data.type === 'stats:export') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ destroy: function() {
+ ModuleAdapter.unloadModule(this.moduleId);
+ }
+};
+
+if (window.ModuleRegistry) {
+ ModuleRegistry.register('m08', window.M08Adapter);
+ console.log('[M08Adapter] 已注册到模块注册表');
+}
+
+console.log('[M08Adapter] 已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m11-adapter.js b/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m11-adapter.js
new file mode 100644
index 0000000..4e3a7b2
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/adapters/m11-adapter.js
@@ -0,0 +1,47 @@
+// M11 系统组件库适配器
+window.M11Adapter = {
+ moduleId: 'm11',
+ moduleName: '组件库',
+
+ init: function(containerId) {
+ console.log('[M11Adapter] 初始化');
+ if (window.ModuleAdapter) {
+ return ModuleAdapter.loadModule(this.moduleId, containerId);
+ } else {
+ console.error('[M11Adapter] ModuleAdapter 未加载');
+ return null;
+ }
+ },
+
+ sendMessage: function(type, payload) {
+ return ModuleAdapter.sendMessage(this.moduleId, { type, payload });
+ },
+
+ // 监听组件库事件
+ onThemeChange: function(callback) {
+ EventBus.on('module:m11:message', function(data) {
+ if (data.type === 'theme:change') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ onComponentSelect: function(callback) {
+ EventBus.on('module:m11:message', function(data) {
+ if (data.type === 'component:select') {
+ callback(data.payload);
+ }
+ });
+ },
+
+ destroy: function() {
+ ModuleAdapter.unloadModule(this.moduleId);
+ }
+};
+
+if (window.ModuleRegistry) {
+ ModuleRegistry.register('m11', window.M11Adapter);
+ console.log('[M11Adapter] 已注册到模块注册表');
+}
+
+console.log('[M11Adapter] 已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/adapters/module-adapter.js b/agents/zhuyuan-dev-agent/modules/m-channel/adapters/module-adapter.js
new file mode 100644
index 0000000..26841ce
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/adapters/module-adapter.js
@@ -0,0 +1,140 @@
+// 模块适配器核心 - 万能转接头
+window.ModuleAdapter = {
+ // 已加载的真实模块缓存
+ loadedModules: {},
+
+ // 适配器配置
+ config: {
+ m06: {
+ name: '工单管理',
+ path: '/m06-ticket/index.html', // 真实路径,但文件可能不全
+ width: '100%',
+ height: '500px',
+ events: ['ticket:create', 'ticket:update', 'ticket:delete']
+ },
+ m08: {
+ name: '数据统计',
+ path: '/modules/m08/index.html', // 暂时未知,先保留错误路径触发边界
+ width: '100%',
+ height: '500px',
+ events: ['stats:refresh', 'stats:export']
+ },
+ m11: {
+ name: '组件库',
+ path: '/m11-module/index.html', // 正确路径!
+ width: '100%',
+ height: '600px',
+ events: ['theme:change', 'component:select']
+ }
+ },
+
+ // 加载真实模块(通过 iframe 沙箱)
+ loadModule: function(moduleId, containerId) {
+ console.log(`[adapter] 加载真实模块: ${moduleId}`);
+
+ const container = document.getElementById(containerId);
+ if (!container) {
+ console.error(`[adapter] 容器 ${containerId} 不存在`);
+ return null;
+ }
+
+ // 清理容器
+ container.innerHTML = '';
+
+ const config = this.config[moduleId];
+ if (!config) {
+ console.error(`[adapter] 未知模块: ${moduleId}`);
+ container.innerHTML = `模块配置不存在
`;
+ return null;
+ }
+
+ // 创建 iframe 沙箱(玻璃展柜)
+ const iframe = document.createElement('iframe');
+ iframe.src = config.path;
+ iframe.style.width = config.width;
+ iframe.style.height = config.height;
+ iframe.style.border = 'none';
+ iframe.style.borderRadius = '8px';
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
+ iframe.setAttribute('allow', 'clipboard-read; clipboard-write');
+
+ // 添加加载指示器
+ const loadingDiv = document.createElement('div');
+ loadingDiv.className = 'module-loading';
+ loadingDiv.textContent = `加载 ${config.name}...`;
+ container.appendChild(loadingDiv);
+
+ // iframe 加载完成后移除加载指示器
+ iframe.onload = () => {
+ loadingDiv.remove();
+ console.log(`[adapter] ${moduleId} 加载完成`);
+
+ // 建立跨框架通信桥梁
+ this.setupMessageBridge(iframe, moduleId);
+ };
+
+ iframe.onerror = () => {
+ loadingDiv.remove();
+ container.innerHTML = `模块加载失败,请重试
`;
+ };
+
+ container.appendChild(iframe);
+ this.loadedModules[moduleId] = { iframe, containerId };
+
+ return iframe;
+ },
+
+ // 重新加载模块(错误重试)
+ reloadModule: function(moduleId, containerId) {
+ console.log(`[adapter] 重新加载模块: ${moduleId}`);
+ this.loadModule(moduleId, containerId);
+ },
+
+ // 建立消息桥梁(iframe 和主页通信)
+ setupMessageBridge: function(iframe, moduleId) {
+ // 监听来自 iframe 的消息
+ window.addEventListener('message', function(event) {
+ // 安全检查:确保消息来自正确的 iframe
+ if (event.source !== iframe.contentWindow) return;
+
+ const data = event.data;
+ if (!data || !data.type) return;
+
+ console.log(`[adapter] 收到来自 ${moduleId} 的消息:`, data);
+
+ // 转发为事件总线消息
+ if (window.EventBus) {
+ EventBus.emit(`module:${moduleId}:message`, {
+ from: moduleId,
+ type: data.type,
+ payload: data.payload
+ });
+ }
+ });
+ },
+
+ // 向模块发送消息
+ sendMessage: function(moduleId, message) {
+ const module = this.loadedModules[moduleId];
+ if (!module || !module.iframe) {
+ console.error(`[adapter] 模块 ${moduleId} 未加载`);
+ return false;
+ }
+
+ module.iframe.contentWindow.postMessage(message, '*');
+ return true;
+ },
+
+ // 卸载模块
+ unloadModule: function(moduleId) {
+ const module = this.loadedModules[moduleId];
+ if (module) {
+ const container = document.getElementById(module.containerId);
+ if (container) container.innerHTML = '';
+ delete this.loadedModules[moduleId];
+ console.log(`[adapter] 卸载模块: ${moduleId}`);
+ }
+ }
+};
+
+console.log('[adapter] 模块适配器已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/all-in-one.html b/agents/zhuyuan-dev-agent/modules/m-channel/all-in-one.html
new file mode 100644
index 0000000..4c1eb87
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/all-in-one.html
@@ -0,0 +1,396 @@
+
+
+
+
+
+ 光湖频道 · 一体化版
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/app.js b/agents/zhuyuan-dev-agent/modules/m-channel/app.js
new file mode 100644
index 0000000..152d4aa
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/app.js
@@ -0,0 +1,50 @@
+// 应用入口
+console.log('[app] 启动中...');
+
+// 等待 DOM 加载完成
+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);
+ }
+ });
+ });
+
+ console.log('[app] 初始化完成');
+});
+
+// 拦截所有事件总线消息,用于调试面板
+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] 事件总线拦截器已安装');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/app.js b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/app.js
new file mode 100644
index 0000000..25e29f0
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/app.js
@@ -0,0 +1,42 @@
+// app.js - 光湖频道动态渲染引擎入口
+console.log('[app] 启动中...');
+
+// 等待 DOM 完全加载
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('[app] DOM 已加载,初始化组件...');
+
+ // 获取内容容器
+ const contentEl = document.getElementById('channel-content');
+ if (!contentEl) {
+ console.error('[app] 找不到 #channel-content 元素');
+ return;
+ }
+
+ // 恢复状态(如果 ChannelState 存在)
+ if (window.ChannelState) {
+ const savedState = ChannelState.restoreState();
+ console.log('[app] 恢复的状态:', savedState);
+ } else {
+ console.warn('[app] ChannelState 未加载');
+ }
+
+ // 初始化路由器
+ if (window.ChannelRouter) {
+ ChannelRouter.init(contentEl);
+ } else {
+ console.error('[app] ChannelRouter 未加载');
+ return;
+ }
+
+ // 绑定导航按钮点击事件
+ document.querySelectorAll('.channel-btn').forEach(btn => {
+ btn.addEventListener('click', function(e) {
+ const channel = this.dataset.channel;
+ if (channel) {
+ ChannelRouter.navigateTo(channel);
+ }
+ });
+ });
+
+ console.log('[app] 初始化完成');
+});
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-router-backup.js b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-router-backup.js
new file mode 100644
index 0000000..614fc3e
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-router-backup.js
@@ -0,0 +1,98 @@
+// ================== 路由配置 ==================
+const routes = {
+ 'home': 'views/home.html',
+ 'channel': 'views/channel.html',
+ 'about': 'views/about.html'
+};
+
+// 获取当前 hash 中的路径(去掉 #/)
+function getHashPath() {
+ const hash = window.location.hash.slice(1) || '/';
+ const path = hash.startsWith('/') ? hash.slice(1) : hash;
+ return path || 'home';
+}
+
+// ================== 加载视图 ==================
+async function loadView(path) {
+ const routerView = document.getElementById('router-view');
+ if (!routerView) return;
+
+ // 显示加载动画
+ routerView.innerHTML = '';
+
+ try {
+ const viewFile = routes[path];
+ if (!viewFile) {
+ await load404(routerView);
+ return;
+ }
+
+ const response = await fetch(viewFile);
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+ const html = await response.text();
+ routerView.innerHTML = html;
+ } catch (error) {
+ console.error('加载视图失败:', error);
+ routerView.innerHTML = `
+
+ ❌ 加载失败:${error.message}
+ 请检查文件是否存在,或刷新重试
+
+ `;
+ }
+
+ // 更新导航高亮和状态栏
+ updateActiveNav(path);
+ updateStatusBar(path);
+}
+
+// 加载 404 页面
+async function load404(container) {
+ try {
+ const resp = await fetch('views/404.html');
+ if (resp.ok) {
+ container.innerHTML = await resp.text();
+ } else {
+ container.innerHTML = '⚠️ 404 - 页面未找到
';
+ }
+ } catch {
+ container.innerHTML = '⚠️ 404 - 页面未找到
';
+ }
+}
+
+// 更新导航高亮
+function updateActiveNav(path) {
+ document.querySelectorAll('.nav-link').forEach(link => {
+ link.classList.remove('active');
+ const linkPath = link.getAttribute('href').slice(2);
+ if (linkPath === path) {
+ link.classList.add('active');
+ }
+ });
+}
+
+// 更新状态栏
+function updateStatusBar(path) {
+ const statusEl = document.getElementById('current-route');
+ if (statusEl) {
+ statusEl.textContent = `当前路由:/${path}`;
+ }
+}
+
+// 监听 hash 变化
+window.addEventListener('hashchange', () => {
+ const path = getHashPath();
+ loadView(path);
+});
+
+// 首次加载
+window.addEventListener('DOMContentLoaded', () => {
+ if (!window.location.hash) {
+ window.location.hash = '#/home';
+ } else {
+ const path = getHashPath();
+ loadView(path);
+ }
+});
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-router.js b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-router.js
new file mode 100644
index 0000000..b1930c6
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-router.js
@@ -0,0 +1,267 @@
+/**
+ * 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);
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-state.js b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-state.js
new file mode 100644
index 0000000..35ea173
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-state.js
@@ -0,0 +1,149 @@
+/**
+ * channel-state.js
+ * 频道状态管理器 - 记忆妈妈的浏览足迹
+ * 使用localStorage持久化,刷新页面自动恢复
+ * 功能:保存/恢复当前路由、已访问模块列表、历史栈
+ */
+
+const ChannelState = (function() {
+ const STORAGE_KEY = 'm-channel-state';
+
+ // 默认状态
+ const defaultState = {
+ currentRoute: '/home',
+ visitedModules: [], // 已访问过的模块ID列表
+ historyStack: ['/home'], // 历史记录栈
+ historyIndex: 0 // 当前在历史栈中的位置
+ };
+
+ let state = { ...defaultState };
+
+ // 加载本地存储的状态
+ function load() {
+ try {
+ const saved = localStorage.getItem(STORAGE_KEY);
+ if (saved) {
+ state = JSON.parse(saved);
+ // 确保必要字段存在
+ if (!state.visitedModules) state.visitedModules = [];
+ if (!state.historyStack || !Array.isArray(state.historyStack)) {
+ state.historyStack = [state.currentRoute || '/home'];
+ }
+ if (typeof state.historyIndex !== 'number') {
+ state.historyIndex = 0;
+ }
+ console.log('[state] restore', state);
+ } else {
+ reset();
+ }
+ } catch (e) {
+ console.warn('[state] load failed, use default', e);
+ reset();
+ }
+ return state;
+ }
+
+ // 保存当前状态到localStorage
+ function save() {
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+ console.log('[state] save', state);
+ } catch (e) {
+ console.warn('[state] save failed', e);
+ }
+ }
+
+ // 重置为默认状态
+ function reset() {
+ state = { ...defaultState };
+ save();
+ }
+
+ // 更新当前路由
+ function setCurrentRoute(route) {
+ if (state.currentRoute !== route) {
+ state.currentRoute = route;
+ // 添加到已访问模块(如果是模块路由)
+ if (route.startsWith('/module/')) {
+ const moduleId = route.replace('/module/', '');
+ if (!state.visitedModules.includes(moduleId)) {
+ state.visitedModules.push(moduleId);
+ }
+ }
+ save();
+ }
+ }
+
+ // 添加到历史栈(用于前进后退)
+ function pushHistory(route) {
+ // 如果当前不在栈顶,先截断后面的记录
+ if (state.historyIndex < state.historyStack.length - 1) {
+ state.historyStack = state.historyStack.slice(0, state.historyIndex + 1);
+ }
+ state.historyStack.push(route);
+ state.historyIndex = state.historyStack.length - 1;
+ setCurrentRoute(route); // 会触发保存
+ }
+
+ // 后退
+ function goBack() {
+ if (state.historyIndex > 0) {
+ state.historyIndex--;
+ state.currentRoute = state.historyStack[state.historyIndex];
+ save();
+ return state.currentRoute;
+ }
+ return null;
+ }
+
+ // 前进
+ function goForward() {
+ if (state.historyIndex < state.historyStack.length - 1) {
+ state.historyIndex++;
+ state.currentRoute = state.historyStack[state.historyIndex];
+ save();
+ return state.currentRoute;
+ }
+ return null;
+ }
+
+ // 获取当前状态
+ function getState() {
+ return { ...state };
+ }
+
+ // 标记模块为已访问(外部调用)
+ function markModuleVisited(moduleId) {
+ if (!state.visitedModules.includes(moduleId)) {
+ state.visitedModules.push(moduleId);
+ save();
+ }
+ }
+
+ // 清除状态(用于测试)
+ function clear() {
+ localStorage.removeItem(STORAGE_KEY);
+ reset();
+ }
+
+ // 初始化:加载状态
+ load();
+
+ return {
+ load,
+ save,
+ reset,
+ setCurrentRoute,
+ pushHistory,
+ goBack,
+ goForward,
+ getState,
+ markModuleVisited,
+ clear
+ };
+})();
+
+// 导出(如果是模块环境)
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = ChannelState;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-style.css b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-style.css
new file mode 100644
index 0000000..f2a8cd2
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-style.css
@@ -0,0 +1,132 @@
+/**
+ * channel-style.css
+ * 频道基础样式 + 过渡动画变量
+ */
+
+/* 引入过渡动画 */
+@import url('channel-transition.css');
+
+:root {
+ --primary-color: #4a90e2;
+ --secondary-color: #f5f5f5;
+ --text-color: #333;
+ --border-radius: 8px;
+ --transition-duration: 0.3s;
+ --transition-timing: ease;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ margin: 0;
+ padding: 20px;
+ background: #f0f2f5;
+ color: var(--text-color);
+}
+
+/* 频道头部 */
+.channel-header {
+ background: white;
+ padding: 15px 20px;
+ border-radius: var(--border-radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ margin-bottom: 20px;
+}
+
+.channel-header h1 {
+ margin: 0;
+ font-size: 1.8rem;
+ color: var(--primary-color);
+}
+
+/* 导航菜单 */
+.channel-nav {
+ background: white;
+ padding: 10px 20px;
+ border-radius: var(--border-radius);
+ margin-bottom: 20px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.channel-nav ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ gap: 20px;
+}
+
+.channel-nav a {
+ text-decoration: none;
+ color: var(--text-color);
+ padding: 8px 16px;
+ border-radius: 20px;
+ transition: background 0.2s;
+}
+
+.channel-nav a:hover {
+ background: var(--secondary-color);
+}
+
+.channel-nav a.active {
+ background: var(--primary-color);
+ color: white;
+}
+
+/* 内容区域 */
+.channel-content {
+ background: white;
+ padding: 20px;
+ border-radius: var(--border-radius);
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ min-height: 300px;
+}
+
+/* 已访问模块标记 */
+.module-link.visited {
+ position: relative;
+}
+
+.module-link.visited::after {
+ content: "✓";
+ position: absolute;
+ top: -5px;
+ right: -5px;
+ background: var(--primary-color);
+ color: white;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* 动画模式切换按钮 */
+.animation-toggle {
+ margin-left: auto;
+ display: flex;
+ gap: 10px;
+}
+
+.animation-toggle button {
+ padding: 5px 15px;
+ border: 1px solid #ddd;
+ background: white;
+ border-radius: 20px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.animation-toggle button.active {
+ background: var(--primary-color);
+ color: white;
+ border-color: var(--primary-color);
+}
+
+/* 调试面板样式(后续使用) */
+.debug-panel {
+ margin-top: 30px;
+ border-top: 2px dashed #ccc;
+ padding-top: 20px;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-transition.css b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-transition.css
new file mode 100644
index 0000000..b13bd02
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/channel-transition.css
@@ -0,0 +1,83 @@
+/**
+ * channel-transition.css
+ * 路由过渡动画 - 让切换像呼吸一样自然
+ * 提供两种模式:淡入淡出(fade) / 滑入滑出(slide)
+ */
+
+/* 基础容器样式 */
+.route-container {
+ position: relative;
+ width: 100%;
+ min-height: 200px;
+}
+
+/* 所有路由页面默认绝对定位,便于重叠动画 */
+.route-page {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ transition: opacity 0.3s ease, transform 0.3s ease;
+}
+
+/* 淡入淡出模式 */
+.fade-mode .route-page {
+ opacity: 0;
+ transform: scale(0.98);
+ pointer-events: none;
+}
+
+.fade-mode .route-page.active {
+ opacity: 1;
+ transform: scale(1);
+ pointer-events: auto;
+ position: relative; /* 激活页变为相对定位,占据文档流高度 */
+}
+
+/* 滑入滑出模式 */
+.slide-mode .route-page {
+ opacity: 1;
+ transform: translateX(0);
+ pointer-events: none;
+}
+
+.slide-mode .route-page.active {
+ position: relative;
+ pointer-events: auto;
+}
+
+/* 进入动画:从右侧滑入 */
+.slide-mode .route-page.slide-enter {
+ transform: translateX(100%);
+}
+
+.slide-mode .route-page.active.slide-enter {
+ transform: translateX(0);
+}
+
+/* 离开动画:向左侧滑出(用于后退时的反向) */
+.slide-mode .route-page.slide-exit {
+ transform: translateX(-100%);
+}
+
+/* 前进/后退方向控制 */
+.slide-mode.forward .route-page.slide-enter {
+ transform: translateX(100%);
+}
+
+.slide-mode.forward .route-page.active.slide-enter {
+ transform: translateX(0);
+}
+
+.slide-mode.backward .route-page.slide-enter {
+ transform: translateX(-100%);
+}
+
+.slide-mode.backward .route-page.active.slide-enter {
+ transform: translateX(0);
+}
+
+/* 防止快速点击时动画重叠 */
+.route-page {
+ will-change: transform, opacity;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/event-bus.js b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/event-bus.js
new file mode 100644
index 0000000..ab14007
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/event-bus.js
@@ -0,0 +1,111 @@
+/**
+ * event-bus.js
+ * 事件总线 - 模块间的群聊频道
+ * 发布/订阅模式,支持命名空间和调试日志
+ */
+
+const EventBus = (function() {
+ // 存储订阅者:{ eventName: [handler1, handler2, ...] }
+ const listeners = {};
+ // 调试模式开关
+ let debugMode = true;
+
+ // 订阅事件
+ function on(eventName, handler) {
+ if (typeof handler !== 'function') {
+ console.error('[bus] 订阅必须提供函数');
+ return;
+ }
+
+ if (!listeners[eventName]) {
+ listeners[eventName] = [];
+ }
+ listeners[eventName].push(handler);
+
+ if (debugMode) {
+ console.log(`[bus] 订阅事件: ${eventName},当前订阅数: ${listeners[eventName].length}`);
+ }
+
+ // 返回取消订阅函数
+ return function off() {
+ off(eventName, handler);
+ };
+ }
+
+ // 取消订阅
+ function off(eventName, handler) {
+ if (!listeners[eventName]) return;
+
+ if (handler) {
+ // 移除特定handler
+ const index = listeners[eventName].indexOf(handler);
+ if (index !== -1) {
+ listeners[eventName].splice(index, 1);
+ if (debugMode) {
+ console.log(`[bus] 取消订阅: ${eventName},剩余: ${listeners[eventName].length}`);
+ }
+ }
+ } else {
+ // 移除该事件所有订阅
+ delete listeners[eventName];
+ if (debugMode) {
+ console.log(`[bus] 移除所有订阅: ${eventName}`);
+ }
+ }
+ }
+
+ // 触发事件
+ function emit(eventName, data) {
+ if (!listeners[eventName]) {
+ if (debugMode) {
+ console.log(`[bus] 触发事件 ${eventName} 但无订阅者`);
+ }
+ return;
+ }
+
+ if (debugMode) {
+ console.log(`[bus] 触发事件: ${eventName},数据:`, data);
+ }
+
+ // 复制一份以防在遍历过程中修改
+ const handlers = listeners[eventName].slice();
+ handlers.forEach(handler => {
+ try {
+ handler(data, eventName);
+ } catch (e) {
+ console.error(`[bus] 事件 ${eventName} 处理出错:`, e);
+ }
+ });
+ }
+
+ // 清空所有订阅
+ function clear() {
+ for (let key in listeners) {
+ delete listeners[key];
+ }
+ if (debugMode) {
+ console.log('[bus] 清空所有订阅');
+ }
+ }
+
+ // 开启/关闭调试
+ function setDebug(enable) {
+ debugMode = enable;
+ }
+
+ return {
+ on,
+ off,
+ emit,
+ clear,
+ setDebug
+ };
+})();
+
+// 挂载到全局
+window.EventBus = EventBus;
+
+// 如果是模块环境
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = EventBus;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/index.html b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/index.html
new file mode 100644
index 0000000..e86e990
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ 光湖频道 · 动态渲染引擎
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-lifecycle.js b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-lifecycle.js
new file mode 100644
index 0000000..37daed6
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-lifecycle.js
@@ -0,0 +1,90 @@
+/**
+ * module-lifecycle.js
+ * 模块生命周期管理 - 让模块知道自己何时加载/卸载/收到消息
+ * 配合事件总线使用
+ */
+
+const ModuleLifecycle = (function() {
+ // 存储每个模块的钩子函数
+ const hooks = {};
+
+ // 注册模块的生命周期钩子
+ function register(moduleId, lifecycles) {
+ if (!moduleId) return;
+
+ hooks[moduleId] = {
+ onLoad: lifecycles.onLoad || null,
+ onUnload: lifecycles.onUnload || null,
+ onMessage: lifecycles.onMessage || null
+ };
+
+ console.log(`[lifecycle] 注册模块: ${moduleId}`, lifecycles);
+
+ // 如果已经加载(比如页面初始化时),自动触发onLoad?
+ // 这里留给loader去调用
+ }
+
+ // 触发模块加载
+ function triggerLoad(moduleId, params) {
+ const moduleHooks = hooks[moduleId];
+ if (moduleHooks && moduleHooks.onLoad) {
+ try {
+ moduleHooks.onLoad(params);
+ console.log(`[lifecycle] onLoad 模块: ${moduleId}`);
+ } catch (e) {
+ console.error(`[lifecycle] onLoad 模块 ${moduleId} 出错:`, e);
+ }
+ }
+ }
+
+ // 触发模块卸载
+ function triggerUnload(moduleId) {
+ const moduleHooks = hooks[moduleId];
+ if (moduleHooks && moduleHooks.onUnload) {
+ try {
+ moduleHooks.onUnload();
+ console.log(`[lifecycle] onUnload 模块: ${moduleId}`);
+ } catch (e) {
+ console.error(`[lifecycle] onUnload 模块 ${moduleId} 出错:`, e);
+ }
+ }
+
+ // 卸载时自动取消该模块的所有事件订阅
+ // 这里简单使用事件总线的off,但需要知道该模块订阅了哪些事件
+ // 我们约定模块在订阅时使用带命名空间的事件名,比如 moduleA:click
+ // 或者在onUnload里手动取消。为了简化,我们不清除订阅,但可以在onUnload里做。
+ // 更完善的做法是记录每个模块的订阅列表,但这里先不实现。
+ }
+
+ // 触发模块收到消息(由事件总线转发时调用)
+ function triggerMessage(moduleId, message, data) {
+ const moduleHooks = hooks[moduleId];
+ if (moduleHooks && moduleHooks.onMessage) {
+ try {
+ moduleHooks.onMessage(message, data);
+ console.log(`[lifecycle] onMessage 模块: ${moduleId} 消息: ${message}`);
+ } catch (e) {
+ console.error(`[lifecycle] onMessage 模块 ${moduleId} 出错:`, e);
+ }
+ }
+ }
+
+ // 获取模块的钩子(用于调试)
+ function getHooks(moduleId) {
+ return hooks[moduleId] || null;
+ }
+
+ return {
+ register,
+ triggerLoad,
+ triggerUnload,
+ triggerMessage,
+ getHooks
+ };
+})();
+
+window.ModuleLifecycle = ModuleLifecycle;
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = ModuleLifecycle;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-loader.js b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-loader.js
new file mode 100644
index 0000000..2747442
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-loader.js
@@ -0,0 +1,116 @@
+/**
+ * module-loader.js
+ * 模块加载器 - 负责动态加载模块HTML/JS,并管理生命周期
+ * 集成事件总线和生命周期钩子
+ */
+
+const ModuleLoader = (function() {
+ // 已加载的模块缓存
+ const loadedModules = {};
+
+ // 加载模块
+ async function loadModule(moduleId, container, params = {}) {
+ if (loadedModules[moduleId]) {
+ console.log(`[loader] 模块 ${moduleId} 已加载,直接显示`);
+ showModule(moduleId, container, params);
+ return;
+ }
+
+ try {
+ console.log(`[loader] 正在加载模块: ${moduleId}`);
+
+ // 获取模块URL(这里使用mock-modules下的html文件)
+ const url = `mock-modules/mock-${moduleId}.html`;
+
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+ const html = await response.text();
+
+ // 解析HTML,提取body内容作为模块内容
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, 'text/html');
+ let moduleContent = doc.body.innerHTML;
+
+ // 提取script标签并执行
+ const scripts = doc.querySelectorAll('script');
+ scripts.forEach(script => {
+ const newScript = document.createElement('script');
+ if (script.src) {
+ newScript.src = script.src;
+ } else {
+ newScript.textContent = script.textContent;
+ }
+ document.body.appendChild(newScript);
+ // 注意:动态添加的脚本会立即执行
+ });
+
+ // 存储模块内容
+ loadedModules[moduleId] = {
+ content: moduleContent,
+ scripts: scripts.length
+ };
+
+ // 显示模块
+ showModule(moduleId, container, params);
+
+ // 触发生命周期 onLoad
+ ModuleLifecycle.triggerLoad(moduleId, params);
+
+ // 标记已访问(状态管理)
+ if (window.ChannelState) {
+ ChannelState.markModuleVisited(moduleId);
+ }
+
+ console.log(`[loader] 模块 ${moduleId} 加载完成`);
+ } catch (error) {
+ console.error(`[loader] 加载模块 ${moduleId} 失败:`, error);
+ container.innerHTML = `加载失败:${error.message}
`;
+ }
+ }
+
+ // 显示已加载的模块
+ function showModule(moduleId, container, params) {
+ const mod = loadedModules[moduleId];
+ if (!mod) return;
+
+ container.innerHTML = mod.content;
+
+ // 如果有参数,可以通过自定义事件传递
+ if (params) {
+ const event = new CustomEvent('module:params', { detail: params });
+ container.dispatchEvent(event);
+ }
+
+ // 通知模块已显示(通过生命周期?)
+ // 可以用triggerMessage
+ ModuleLifecycle.triggerMessage(moduleId, 'show', params);
+ }
+
+ // 卸载模块
+ function unloadModule(moduleId) {
+ if (loadedModules[moduleId]) {
+ // 触发生命周期 onUnload
+ ModuleLifecycle.triggerUnload(moduleId);
+
+ // 清理缓存(可选)
+ delete loadedModules[moduleId];
+ console.log(`[loader] 模块 ${moduleId} 已卸载`);
+ }
+ }
+
+ // 预加载模块
+ function preloadModule(moduleId) {
+ // 简单fetch但不显示
+ fetch(`mock-modules/mock-${moduleId}.html`).catch(() => {});
+ }
+
+ return {
+ loadModule,
+ unloadModule,
+ preloadModule
+ };
+})();
+
+window.ModuleLoader = ModuleLoader;
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-registry.js b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-registry.js
new file mode 100644
index 0000000..a0a87ff
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/backup-混乱版/module-registry.js
@@ -0,0 +1,7 @@
+const moduleRegistry = {
+ 'mock-a': 'mock-modules/mock-a.html',
+ 'mock-b': 'mock-modules/mock-b.html',
+ 'mock-c': 'mock-modules/mock-c.html',
+ 'mock-d': 'mock-modules/mock-d.html'
+};
+window.moduleRegistry = moduleRegistry;
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-analytics.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-analytics.js
new file mode 100644
index 0000000..df5ed7e
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-analytics.js
@@ -0,0 +1,178 @@
+/**
+ * channel-analytics.js
+ * 频道数据采集核心·环节8 + 环节9设置联动
+ * 记录模块访问次数、停留时间、页面加载速度
+ */
+
+const ChannelAnalytics = (function() {
+ const STORAGE_KEY = 'channel-analytics-data';
+
+ // 数据结构初始化
+ function getDefaultData() {
+ return {
+ modules: {}, // {moduleId: {visits: 0, totalDuration: 0, loadTimes: [], dailyVisits: {} }}
+ globalDaily: {}, // {'YYYY-MM-DD': totalVisits}
+ lastUpdated: null
+ };
+ }
+
+ // localStorage 读写
+ function loadData() {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (raw) return JSON.parse(raw);
+ } catch(e) {
+ console.log('⚠️ 读取分析数据失败,重新初始化');
+ }
+ return getDefaultData();
+ }
+
+ function saveData(data) {
+ data.lastUpdated = new Date().toISOString();
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+ }
+
+ // 日期工具
+ function today() {
+ return new Date().toISOString().split('T')[0];
+ }
+
+ // 确保模块数据结构存在
+ function ensureModule(data, moduleId) {
+ if (!data.modules[moduleId]) {
+ data.modules[moduleId] = {
+ visits: 0,
+ totalDuration: 0,
+ loadTimes: [],
+ dailyVisits: {}
+ };
+ }
+ return data.modules[moduleId];
+ }
+
+ // 当前会话状态
+ let currentModule = null;
+ let enterTime = null;
+ let loadStartTime = null;
+
+ // 公开方法
+ return {
+ // 记录模块访问(环节9:检查数据采集开关)
+ recordVisit: function(moduleId) {
+ // 数据采集开关检查(环节9新增)
+ if (typeof ChannelSettings !== 'undefined' && !ChannelSettings.get('analyticsEnabled')) {
+ console.log('📊 数据采集已关闭(设置中心)');
+ return;
+ }
+ if (!moduleId) return;
+ const data = loadData();
+ const mod = ensureModule(data, moduleId);
+
+ // 访问计数 +1
+ mod.visits++;
+
+ // 每日访问计数
+ const d = today();
+ mod.dailyVisits[d] = (mod.dailyVisits[d] || 0) + 1;
+
+ // 全局每日访问
+ data.globalDaily[d] = (data.globalDaily[d] || 0) + 1;
+
+ saveData(data);
+ console.log('📊 已记录:模块 ' + moduleId + ' 被访问,累计 ' + mod.visits + ' 次');
+
+ // 记录进入时间
+ this.startSession(moduleId);
+ },
+
+ // 开始计时
+ startSession: function(moduleId) {
+ // 先结束上一个模块的计时
+ if (currentModule && enterTime) {
+ this.endSession();
+ }
+ currentModule = moduleId;
+ enterTime = performance.now();
+ loadStartTime = performance.now();
+ },
+
+ // 结束计时(切换模块或离开时调用)
+ endSession: function() {
+ if (!currentModule || !enterTime) return;
+ // 数据采集开关检查(环节9新增)
+ if (typeof ChannelSettings !== 'undefined' && !ChannelSettings.get('analyticsEnabled')) {
+ // 如果关闭采集,清空当前会话但不记录
+ currentModule = null;
+ enterTime = null;
+ return;
+ }
+ const duration = Math.round((performance.now() - enterTime) / 1000); // 秒
+ const data = loadData();
+ const mod = ensureModule(data, currentModule);
+ mod.totalDuration += duration;
+ saveData(data);
+ console.log('⏱️ 停留时间:模块 ' + currentModule + ' 停留约 ' + duration + ' 秒');
+ currentModule = null;
+ enterTime = null;
+ },
+
+ // 记录加载性能
+ recordLoadTime: function(moduleId, loadTimeMs) {
+ if (!moduleId) return;
+ // 数据采集开关检查(环节9新增)
+ if (typeof ChannelSettings !== 'undefined' && !ChannelSettings.get('analyticsEnabled')) {
+ return;
+ }
+ const data = loadData();
+ const mod = ensureModule(data, moduleId);
+ mod.loadTimes.push(loadTimeMs);
+ // 只保留最近50次
+ if (mod.loadTimes.length > 50) {
+ mod.loadTimes = mod.loadTimes.slice(-50);
+ }
+ saveData(data);
+ console.log('⚡ 加载耗时:模块 ' + moduleId + ' 加载 ' + Math.round(loadTimeMs) + ' 毫秒');
+ },
+
+ // 标记加载开始
+ markLoadStart: function() {
+ loadStartTime = performance.now();
+ },
+
+ // 标记加载完成并记录
+ markLoadEnd: function(moduleId) {
+ if (loadStartTime && moduleId) {
+ const loadTime = performance.now() - loadStartTime;
+ this.recordLoadTime(moduleId, loadTime);
+ loadStartTime = null;
+ }
+ },
+
+ // 获取所有数据(面板用)
+ getAllData: function() {
+ return loadData();
+ },
+
+ // 获取最近7天趋势
+ getWeeklyTrend: function() {
+ const data = loadData();
+ const trend = [];
+ for (let i = 6; i >= 0; i--) {
+ const d = new Date();
+ d.setDate(d.getDate() - i);
+ const dateStr = d.toISOString().split('T')[0];
+ trend.push({
+ date: dateStr,
+ visits: data.globalDaily[dateStr] || 0
+ });
+ }
+ return trend;
+ },
+
+ // 清除所有数据(调试用)
+ clearAll: function() {
+ localStorage.removeItem(STORAGE_KEY);
+ console.log('🗑️ 所有分析数据已清除');
+ }
+ };
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-complete.html b/agents/zhuyuan-dev-agent/modules/m-channel/channel-complete.html
new file mode 100644
index 0000000..9162957
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-complete.html
@@ -0,0 +1,267 @@
+
+
+
+
+
+ 光湖频道 · 完全版
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
主题色
+
+
+
+
+
+
+
+
+
+
统计数据
+
+
+
+
其他
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-dashboard.css b/agents/zhuyuan-dev-agent/modules/m-channel/channel-dashboard.css
new file mode 100644
index 0000000..1269c86
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-dashboard.css
@@ -0,0 +1,199 @@
+/**
+ * channel-dashboard.css
+ * 频道数据面板样式·深色主题
+ */
+
+.dashboard-container {
+ padding: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+ color: #e0e0e0;
+}
+
+.dashboard-header {
+ text-align: center;
+ margin-bottom: 30px;
+}
+
+.dashboard-header h2 {
+ font-size: 24px;
+ color: #4fc3f7;
+ margin-bottom: 8px;
+}
+
+.dashboard-header p {
+ font-size: 14px;
+ color: #888;
+}
+
+/* 图表网格 */
+.charts-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+ margin-bottom: 30px;
+}
+
+.chart-card {
+ background: #1a1a2e;
+ border-radius: 12px;
+ padding: 20px;
+ border: 1px solid #2a2a4a;
+}
+
+.chart-card h3 {
+ font-size: 16px;
+ color: #4fc3f7;
+ margin-bottom: 15px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #2a2a4a;
+}
+
+.chart-card canvas {
+ width: 100% !important;
+ height: 200px !important;
+ display: block;
+}
+
+/* 柱状图 */
+.bar-chart {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-around;
+ height: 200px;
+ padding: 10px 0;
+ border-bottom: 2px solid #333;
+}
+
+.bar-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+ max-width: 80px;
+}
+
+.bar-fill {
+ width: 40px;
+ border-radius: 4px 4px 0 0;
+ transition: height 0.5s ease;
+ min-height: 4px;
+}
+
+.bar-label {
+ margin-top: 8px;
+ font-size: 11px;
+ color: #aaa;
+ text-align: center;
+ word-break: break-all;
+}
+
+.bar-value {
+ font-size: 12px;
+ color: #fff;
+ margin-bottom: 4px;
+}
+
+/* 饼图 */
+.pie-container {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+}
+
+.pie-canvas-wrap {
+ flex-shrink: 0;
+}
+
+.pie-legend {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.pie-legend li {
+ display: flex;
+ align-items: center;
+ margin-bottom: 6px;
+ font-size: 13px;
+}
+
+.pie-legend .dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ margin-right: 8px;
+ flex-shrink: 0;
+}
+
+/* 折线图 */
+.line-chart-wrap {
+ position: relative;
+ height: 200px;
+}
+
+/* 性能表格 */
+.perf-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 13px;
+}
+
+.perf-table th {
+ text-align: left;
+ padding: 8px;
+ background: #2a2a4a;
+ color: #4fc3f7;
+ font-weight: normal;
+}
+
+.perf-table td {
+ padding: 8px;
+ border-bottom: 1px solid #2a2a4a;
+}
+
+.perf-table tr:last-child td {
+ border-bottom: none;
+}
+
+.perf-table .slow {
+ color: #ff5252;
+ font-weight: bold;
+}
+
+/* 按钮 */
+.dashboard-actions {
+ margin-top: 20px;
+ text-align: right;
+}
+
+.btn-clear {
+ background: #ff5252;
+ color: #fff;
+ border: none;
+ padding: 10px 24px;
+ border-radius: 8px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: background 0.3s;
+}
+
+.btn-clear:hover {
+ background: #ff1744;
+}
+
+/* 响应式 */
+@media (max-width: 768px) {
+ .charts-grid {
+ grid-template-columns: 1fr;
+ }
+ .pie-container {
+ flex-direction: column;
+ }
+}
+
+/* 饼图 canvas 保持正方形 */
+.pie-canvas-wrap canvas {
+ width: 160px !important;
+ height: 160px !important;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-dashboard.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-dashboard.js
new file mode 100644
index 0000000..13f3555
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-dashboard.js
@@ -0,0 +1,201 @@
+
+/**
+ * channel-dashboard.js
+ * 频道数据面板逻辑·图表渲染
+ */
+
+const ChannelDashboard = (function() {
+ // 配色方案
+ const COLORS = ['#4fc3f7', '#ffb74d', '#ff8a80', '#aed581', '#ba68c8', '#4dd0e1', '#ffd54f'];
+
+ // 柱状图渲染
+ function renderBarChart() {
+ const container = document.getElementById('visitBarChart');
+ if (!container) return;
+ const data = ChannelAnalytics.getAllData();
+ const modules = data.modules;
+ const keys = Object.keys(modules);
+ if (keys.length === 0) {
+ container.innerHTML = '暂无数据,多点几个模块再来看
';
+ return;
+ }
+ const maxVisits = Math.max.apply(null, keys.map(function(k) { return modules[k].visits; })) || 1;
+ let html = '';
+ keys.forEach(function(id, i) {
+ const mod = modules[id];
+ const height = Math.max(4, (mod.visits / maxVisits) * 180);
+ const color = COLORS[i % COLORS.length];
+ html += '' +
+ '
' + mod.visits + '' +
+ '
' +
+ '
' + id.replace('m-', '') + '' +
+ '
';
+ });
+ container.innerHTML = html;
+ }
+
+ // 饼图渲染
+ function renderPieChart() {
+ const canvas = document.getElementById('pieCanvas');
+ const legendEl = document.getElementById('pieLegend');
+ if (!canvas || !legendEl) return;
+ const ctx = canvas.getContext('2d');
+ const data = ChannelAnalytics.getAllData();
+ const modules = data.modules;
+ const keys = Object.keys(modules);
+ const total = keys.reduce(function(sum, k) {
+ return sum + (modules[k].totalDuration || 0);
+ }, 0);
+
+ // 清空画布
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ if (total === 0 || keys.length === 0) {
+ ctx.fillStyle = '#333';
+ ctx.beginPath();
+ ctx.arc(80, 80, 70, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.fillStyle = '#666';
+ ctx.font = '12px sans-serif';
+ ctx.textAlign = 'center';
+ ctx.fillText('暂无数据', 80, 84);
+ legendEl.innerHTML = '';
+ return;
+ }
+
+ let startAngle = -Math.PI / 2;
+ let legendHtml = '';
+ keys.forEach(function(id, i) {
+ const mod = modules[id];
+ const pct = mod.totalDuration / total;
+ const sweep = pct * Math.PI * 2;
+ const color = COLORS[i % COLORS.length];
+
+ ctx.beginPath();
+ ctx.moveTo(80, 80);
+ ctx.arc(80, 80, 70, startAngle, startAngle + sweep);
+ ctx.closePath();
+ ctx.fillStyle = color;
+ ctx.fill();
+
+ startAngle += sweep;
+
+ const minutes = Math.round(mod.totalDuration / 60);
+ const pctStr = Math.round(pct * 100);
+ legendHtml += '' +
+ id.replace('m-', '') + ' ' + pctStr + '% (' + minutes + '分钟)';
+ });
+ legendEl.innerHTML = legendHtml;
+ }
+
+ // 折线图渲染
+ function renderLineChart() {
+ const canvas = document.getElementById('lineCanvas');
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ const trend = ChannelAnalytics.getWeeklyTrend();
+ const w = canvas.width;
+ const h = canvas.height;
+
+ ctx.clearRect(0, 0, w, h);
+
+ const maxVal = Math.max.apply(null, trend.map(function(t) { return t.visits; })) || 1;
+ const padLeft = 40;
+ const padBottom = 30;
+ const padTop = 10;
+ const chartW = w - padLeft - 20;
+ const chartH = h - padBottom - padTop;
+ const stepX = chartW / (trend.length - 1 || 1);
+
+ // 网格线
+ ctx.strokeStyle = '#2a2a4a';
+ ctx.lineWidth = 1;
+ for (let g = 0; g <= 4; g++) {
+ let gy = padTop + (chartH / 4) * g;
+ ctx.beginPath();
+ ctx.moveTo(padLeft, gy);
+ ctx.lineTo(w - 20, gy);
+ ctx.stroke();
+ }
+
+ // 折线
+ ctx.strokeStyle = '#4fc3f7';
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ trend.forEach(function(t, i) {
+ let x = padLeft + stepX * i;
+ let y = padTop + chartH - (t.visits / maxVal) * chartH;
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ });
+ ctx.stroke();
+
+ // 数据点
+ trend.forEach(function(t, i) {
+ let x = padLeft + stepX * i;
+ let y = padTop + chartH - (t.visits / maxVal) * chartH;
+ ctx.beginPath();
+ ctx.arc(x, y, 4, 0, Math.PI * 2);
+ ctx.fillStyle = '#4fc3f7';
+ ctx.fill();
+ });
+
+ // 标签
+ ctx.fillStyle = '#aaa';
+ ctx.font = '11px sans-serif';
+ ctx.textAlign = 'center';
+ trend.forEach(function(t, i) {
+ let x = padLeft + stepX * i;
+ let y = padTop + chartH + 15;
+ ctx.fillText(t.date.slice(5), x, y);
+ });
+ }
+
+ // 性能表格渲染
+ function renderPerfTable() {
+ const tbody = document.getElementById('perfTableBody');
+ if (!tbody) return;
+ const data = ChannelAnalytics.getAllData();
+ const modules = data.modules;
+ const keys = Object.keys(modules);
+ if (keys.length === 0) {
+ tbody.innerHTML = '| 暂无数据 |
';
+ return;
+ }
+
+ let html = '';
+ keys.forEach(function(id) {
+ const mod = modules[id];
+ const avgLoad = mod.loadTimes.length ?
+ Math.round(mod.loadTimes.reduce((a, b) => a + b, 0) / mod.loadTimes.length) : 0;
+ const minutes = Math.round(mod.totalDuration / 60);
+ const statusClass = avgLoad > 300 ? 'slow' : '';
+ const statusText = avgLoad > 300 ? '⚠️ 慢' : '✅ 正常';
+
+ html += '' +
+ '| ' + id.replace('m-', '') + ' | ' +
+ '' + mod.visits + ' | ' +
+ '' + minutes + '分钟 | ' +
+ '' + avgLoad + 'ms | ' +
+ '' + statusText + ' | ' +
+ '
';
+ });
+ tbody.innerHTML = html;
+ }
+
+ // 公开方法
+ return {
+ render: function() {
+ renderBarChart();
+ renderPieChart();
+ renderLineChart();
+ renderPerfTable();
+ },
+ clearData: function() {
+ if (confirm('确定清除所有统计数据吗?')) {
+ ChannelAnalytics.clearAll();
+ this.render();
+ console.log('🗑️ 数据已清除,图表已重置');
+ }
+ }
+ };
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-enhancements.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-enhancements.js
new file mode 100644
index 0000000..3689661
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-enhancements.js
@@ -0,0 +1,338 @@
+// channel-enhancements.js - 频道搜索、面包屑、快捷键增强功能(终极修复版)
+(function() {
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+
+ function init() {
+ addStyles();
+ createSearchBox();
+ createBreadcrumb();
+ createShortcutHint();
+ initShortcuts();
+ bindCardClicks(); // ★ 新增:为卡片绑定点击切换模块
+ window.addEventListener('hashchange', updateBreadcrumb);
+ updateBreadcrumb();
+ }
+
+ function addStyles() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .channel-search-container {
+ padding: 16px 20px;
+ background: #f8f9fa;
+ border-bottom: 1px solid #e9ecef;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+ .channel-search-input {
+ flex: 1;
+ padding: 10px 16px;
+ border: 1px solid #ced4da;
+ border-radius: 24px;
+ font-size: 14px;
+ outline: none;
+ transition: border-color 0.2s;
+ }
+ .channel-search-input:focus {
+ border-color: #4d6bfe;
+ box-shadow: 0 0 0 3px rgba(77,107,254,0.1);
+ }
+ .channel-search-clear {
+ background: none;
+ border: none;
+ font-size: 18px;
+ cursor: pointer;
+ color: #6c757d;
+ padding: 0 8px;
+ display: none;
+ }
+ .channel-search-clear:hover {
+ color: #212529;
+ }
+ .channel-search-input:not(:placeholder-shown) + .channel-search-clear {
+ display: inline-block;
+ }
+ .highlight {
+ background-color: #ffeb3b;
+ padding: 2px 0;
+ border-radius: 2px;
+ }
+ .no-results {
+ text-align: center;
+ padding: 40px;
+ color: #6c757d;
+ font-style: italic;
+ }
+ .channel-breadcrumb {
+ padding: 12px 20px;
+ background: white;
+ border-bottom: 1px solid #e9ecef;
+ font-size: 14px;
+ }
+ .channel-breadcrumb a {
+ color: #4d6bfe;
+ text-decoration: none;
+ }
+ .channel-breadcrumb a:hover {
+ text-decoration: underline;
+ }
+ .channel-breadcrumb span {
+ color: #6c757d;
+ }
+ .channel-breadcrumb .current {
+ color: #212529;
+ font-weight: 500;
+ }
+ .shortcut-hint {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ background: rgba(0,0,0,0.7);
+ color: white;
+ padding: 8px 16px;
+ border-radius: 30px;
+ font-size: 12px;
+ backdrop-filter: blur(4px);
+ z-index: 1000;
+ }
+ .shortcut-hint kbd {
+ background: rgba(255,255,255,0.2);
+ padding: 2px 6px;
+ border-radius: 4px;
+ margin: 0 2px;
+ }
+ .module-card.selected {
+ outline: 2px solid #4d6bfe;
+ outline-offset: 2px;
+ transform: scale(1.02);
+ transition: all 0.2s;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ function createSearchBox() {
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list') || document.querySelector('.channel-content');
+ if (!container) return;
+
+ const searchContainer = document.createElement('div');
+ searchContainer.className = 'channel-search-container';
+ searchContainer.innerHTML = `
+
+
+ `;
+ container.parentNode.insertBefore(searchContainer, container);
+
+ const searchInput = searchContainer.querySelector('.channel-search-input');
+ const clearBtn = searchContainer.querySelector('.channel-search-clear');
+
+ searchInput.addEventListener('input', function() {
+ filterModules(this.value);
+ });
+
+ clearBtn.addEventListener('click', function() {
+ searchInput.value = '';
+ filterModules('');
+ searchInput.focus();
+ });
+
+ window.__channelSearchInput = searchInput;
+ }
+
+ function filterModules(keyword) {
+ const cards = document.querySelectorAll('.module-card');
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
+ let hasResults = false;
+
+ // 移除所有高亮
+ removeAllHighlights();
+
+ cards.forEach(card => {
+ const text = card.innerText || card.textContent;
+ if (keyword === '') {
+ card.style.display = '';
+ hasResults = true;
+ } else {
+ const lowerText = text.toLowerCase();
+ const lowerKeyword = keyword.toLowerCase();
+ if (lowerText.includes(lowerKeyword)) {
+ card.style.display = '';
+ highlightText(card, keyword);
+ hasResults = true;
+ } else {
+ card.style.display = 'none';
+ }
+ }
+ });
+
+ let noResultsEl = document.querySelector('.no-results');
+ if (!hasResults && keyword !== '') {
+ if (!noResultsEl) {
+ noResultsEl = document.createElement('div');
+ noResultsEl.className = 'no-results';
+ noResultsEl.textContent = '没有找到匹配的模块';
+ container.parentNode.insertBefore(noResultsEl, container.nextSibling);
+ }
+ } else {
+ if (noResultsEl) noResultsEl.remove();
+ }
+ }
+
+ function removeAllHighlights() {
+ document.querySelectorAll('.highlight').forEach(span => {
+ const parent = span.parentNode;
+ parent.replaceChild(document.createTextNode(span.textContent), span);
+ parent.normalize();
+ });
+ }
+
+ function highlightText(card, keyword) {
+ const regex = new RegExp(`(${keyword})`, 'gi');
+ const walk = document.createTreeWalker(card, NodeFilter.SHOW_TEXT, {
+ acceptNode: function(node) {
+ if (node.parentNode.classList && node.parentNode.classList.contains('highlight')) {
+ return NodeFilter.FILTER_REJECT;
+ }
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ }, false);
+
+ const textNodes = [];
+ while (walk.nextNode()) textNodes.push(walk.currentNode);
+
+ textNodes.forEach(node => {
+ const text = node.nodeValue;
+ if (regex.test(text)) {
+ const span = document.createElement('span');
+ span.className = 'highlight';
+ span.innerHTML = text.replace(regex, '$1');
+ node.parentNode.replaceChild(span, node);
+ }
+ });
+ }
+
+ function createBreadcrumb() {
+ const searchContainer = document.querySelector('.channel-search-container');
+ const breadcrumb = document.createElement('div');
+ breadcrumb.className = 'channel-breadcrumb';
+ breadcrumb.id = 'channelBreadcrumb';
+ if (searchContainer) {
+ searchContainer.parentNode.insertBefore(breadcrumb, searchContainer.nextSibling);
+ } else {
+ const container = document.querySelector('.module-grid') || document.querySelector('#module-list');
+ if (container) {
+ container.parentNode.insertBefore(breadcrumb, container);
+ }
+ }
+ }
+
+ function updateBreadcrumb() {
+ const breadcrumb = document.getElementById('channelBreadcrumb');
+ if (!breadcrumb) return;
+ const hash = window.location.hash.slice(1) || '';
+ let moduleName = '频道';
+ if (hash.startsWith('module-')) {
+ const moduleId = hash.replace('module-', '');
+ const moduleNames = {
+ 'M06': '工单管理',
+ 'M08': '数据看板',
+ 'M11': '组件库',
+ 'debug': '调试面板'
+ };
+ moduleName = moduleNames[moduleId] || moduleId;
+ }
+ breadcrumb.innerHTML = `
+ 首页 >
+ 频道 >
+ ${moduleName}
+ `;
+ }
+
+ function createShortcutHint() {
+ const hint = document.createElement('div');
+ hint.className = 'shortcut-hint';
+ hint.innerHTML = `
+ ⌘K 搜索 ·
+ ↑↓ 选择 ·
+ Enter 打开 ·
+ Esc 关闭
+ `;
+ document.body.appendChild(hint);
+ }
+
+ function initShortcuts() {
+ document.addEventListener('keydown', function(e) {
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
+ e.preventDefault();
+ const searchInput = window.__channelSearchInput;
+ if (searchInput) searchInput.focus();
+ }
+ if (e.key === 'Escape') {
+ const searchInput = window.__channelSearchInput;
+ if (searchInput && document.activeElement === searchInput) {
+ searchInput.value = '';
+ filterModules('');
+ searchInput.blur();
+ } else if (searchInput && searchInput.value) {
+ searchInput.value = '';
+ filterModules('');
+ }
+ }
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
+ const cards = Array.from(document.querySelectorAll('.module-card:not([style*="display: none"])'));
+ if (cards.length === 0) return;
+ e.preventDefault();
+ let selectedIndex = cards.findIndex(card => card.classList.contains('selected'));
+ if (selectedIndex === -1) {
+ selectedIndex = e.key === 'ArrowDown' ? 0 : cards.length - 1;
+ } else {
+ cards[selectedIndex].classList.remove('selected');
+ if (e.key === 'ArrowDown') {
+ selectedIndex = (selectedIndex + 1) % cards.length;
+ } else {
+ selectedIndex = (selectedIndex - 1 + cards.length) % cards.length;
+ }
+ }
+ cards[selectedIndex].classList.add('selected');
+ cards[selectedIndex].scrollIntoView({ block: 'nearest' });
+ }
+ if (e.key === 'Enter') {
+ const selected = document.querySelector('.module-card.selected');
+ if (selected) {
+ selected.click(); // 触发我们绑定的点击事件
+ }
+ }
+ });
+ }
+
+ // ★★★ 核心修复:为所有卡片绑定点击切换模块 ★★★
+ function bindCardClicks() {
+ const grid = document.querySelector('.module-grid') || document.querySelector('#module-list') || document.body;
+ grid.addEventListener('click', function(e) {
+ const card = e.target.closest('.module-card');
+ if (!card) return;
+
+ // 阻止可能冲突的其他事件(可选)
+ e.preventDefault();
+
+ // 根据卡片文字判断是哪个模块
+ const text = card.innerText || card.textContent;
+ let moduleId = null;
+ if (text.includes('工单管理')) moduleId = 'M06';
+ else if (text.includes('数据统计')) moduleId = 'M08';
+ else if (text.includes('组件库')) moduleId = 'M11';
+ else if (text.includes('调试面板')) moduleId = 'debug';
+ // 如果识别不到,尝试从其他属性获取(比如 data-module-id)
+ else if (card.dataset.moduleId) moduleId = card.dataset.moduleId;
+
+ if (moduleId) {
+ // 改变 hash,触发路由更新
+ window.location.hash = `module-${moduleId}`;
+ }
+ });
+ }
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-favorites.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-favorites.js
new file mode 100644
index 0000000..4208f65
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-favorites.js
@@ -0,0 +1,271 @@
+// 频道收藏与拖拽排序管理
+window.ChannelFavorites = {
+ // 当前正在拖拽的元素
+ draggingElement: null,
+
+ // 初始化
+ init: function() {
+ console.log('[favorites] 初始化');
+ this.bindEvents();
+ this.renderFavorites();
+ },
+
+ // 绑定事件
+ bindEvents: function() {
+ // 使用事件委托监听星星点击
+ document.addEventListener('click', (e) => {
+ const star = e.target.closest('.favorite-star');
+ if (star) {
+ e.preventDefault();
+ const card = star.closest('.module-card');
+ if (card) {
+ const moduleId = card.dataset.module;
+ if (moduleId) {
+ this.toggleFavorite(moduleId, star);
+ }
+ }
+ }
+ });
+
+ // 监听偏好变化,重新渲染收藏状态
+ if (window.EventBus) {
+ EventBus.on('preferences:changed', (data) => {
+ if (data.key === 'favorites' || data.full) {
+ this.updateAllStars();
+ }
+ });
+ }
+ },
+
+ // 切换收藏状态
+ toggleFavorite: function(moduleId, starElement) {
+ if (!window.ChannelPreferences) return;
+
+ const isNowFavorite = ChannelPreferences.toggleFavorite(moduleId);
+
+ // 更新星星样式
+ if (starElement) {
+ if (isNowFavorite) {
+ starElement.classList.add('active');
+ starElement.textContent = '★';
+ } else {
+ starElement.classList.remove('active');
+ starElement.textContent = '☆';
+ }
+ }
+
+ // 触发布局更新(收藏置顶)
+ this.updateFavoritesOrder();
+
+ // 发送事件
+ if (window.EventBus) {
+ EventBus.emit('favorite:toggled', {
+ module: moduleId,
+ favorite: isNowFavorite
+ });
+ }
+ },
+
+ // 更新所有星星的显示状态
+ updateAllStars: function() {
+ const favorites = window.ChannelPreferences ? ChannelPreferences.getFavorites() : [];
+ document.querySelectorAll('.favorite-star').forEach(star => {
+ const card = star.closest('.module-card');
+ if (card) {
+ const moduleId = card.dataset.module;
+ if (moduleId) {
+ if (favorites.includes(moduleId)) {
+ star.classList.add('active');
+ star.textContent = '★';
+ } else {
+ star.classList.remove('active');
+ star.textContent = '☆';
+ }
+ }
+ }
+ });
+ },
+
+ // 根据收藏状态重新排序(收藏置顶)
+ updateFavoritesOrder: function() {
+ const container = document.querySelector('.channel-content');
+ if (!container) return;
+
+ const cards = Array.from(container.querySelectorAll('.module-card'));
+ const favorites = window.ChannelPreferences ? ChannelPreferences.getFavorites() : [];
+
+ // 按收藏状态和原有顺序排序
+ cards.sort((a, b) => {
+ const aId = a.dataset.module;
+ const bId = b.dataset.module;
+ const aFav = favorites.includes(aId);
+ const bFav = favorites.includes(bId);
+
+ if (aFav && !bFav) return -1;
+ if (!aFav && bFav) return 1;
+
+ // 如果都是收藏或都不是收藏,保持原有顺序
+ const aOrder = cards.indexOf(a);
+ const bOrder = cards.indexOf(b);
+ return aOrder - bOrder;
+ });
+
+ // 重新插入到容器中
+ cards.forEach(card => container.appendChild(card));
+
+ // 保存排序到偏好设置
+ const moduleOrder = cards.map(card => card.dataset.module);
+ if (window.ChannelPreferences) {
+ ChannelPreferences.setModuleOrder(moduleOrder);
+ }
+ },
+
+ // 渲染收藏状态(初始化时调用)
+ renderFavorites: function() {
+ this.updateAllStars();
+ },
+
+ // ===== 拖拽排序相关 =====
+
+ // 初始化拖拽
+ initDragAndDrop: function() {
+ console.log('[favorites] 初始化拖拽排序');
+
+ const container = document.querySelector('.channel-content');
+ if (!container) return;
+
+ // 为每个卡片添加拖拽手柄
+ container.querySelectorAll('.module-card').forEach(card => {
+ // 检查是否已有拖拽手柄
+ if (!card.querySelector('.drag-handle')) {
+ const header = card.querySelector('.module-card-header');
+ if (header) {
+ const handle = document.createElement('span');
+ handle.className = 'drag-handle';
+ handle.innerHTML = '⋮⋮';
+ handle.setAttribute('draggable', 'false');
+ header.insertBefore(handle, header.firstChild);
+ }
+ }
+
+ // 设置 draggable
+ card.setAttribute('draggable', 'true');
+
+ // 移除旧监听器,添加新监听器
+ card.removeEventListener('dragstart', this.handleDragStart);
+ card.removeEventListener('dragend', this.handleDragEnd);
+ card.removeEventListener('dragover', this.handleDragOver);
+ card.removeEventListener('dragenter', this.handleDragEnter);
+ card.removeEventListener('dragleave', this.handleDragLeave);
+ card.removeEventListener('drop', this.handleDrop);
+
+ card.addEventListener('dragstart', this.handleDragStart.bind(this));
+ card.addEventListener('dragend', this.handleDragEnd.bind(this));
+ card.addEventListener('dragover', this.handleDragOver);
+ card.addEventListener('dragenter', this.handleDragEnter);
+ card.addEventListener('dragleave', this.handleDragLeave);
+ card.addEventListener('drop', this.handleDrop.bind(this));
+ });
+ },
+
+ // 拖拽开始
+ handleDragStart: function(e) {
+ this.draggingElement = e.target.closest('.module-card');
+ if (!this.draggingElement) return;
+
+ e.dataTransfer.setData('text/plain', this.draggingElement.dataset.module);
+ e.dataTransfer.effectAllowed = 'move';
+
+ // 添加拖拽中的样式
+ setTimeout(() => {
+ this.draggingElement.classList.add('dragging');
+ }, 0);
+ },
+
+ // 拖拽结束
+ handleDragEnd: function(e) {
+ const card = e.target.closest('.module-card');
+ if (card) {
+ card.classList.remove('dragging');
+ }
+
+ // 移除所有高亮
+ document.querySelectorAll('.module-card.drop-target').forEach(el => {
+ el.classList.remove('drop-target');
+ });
+
+ this.draggingElement = null;
+ },
+
+ // 拖拽经过(必须阻止默认事件才能成为放置目标)
+ handleDragOver: function(e) {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = 'move';
+ },
+
+ // 拖拽进入
+ handleDragEnter: function(e) {
+ const card = e.target.closest('.module-card');
+ if (card && card !== this.draggingElement) {
+ card.classList.add('drop-target');
+ }
+ },
+
+ // 拖拽离开
+ handleDragLeave: function(e) {
+ const card = e.target.closest('.module-card');
+ if (card) {
+ card.classList.remove('drop-target');
+ }
+ },
+
+ // 放置
+ handleDrop: function(e) {
+ e.preventDefault();
+
+ const targetCard = e.target.closest('.module-card');
+ if (!targetCard || !this.draggingElement || targetCard === this.draggingElement) {
+ return;
+ }
+
+ // 移除高亮
+ targetCard.classList.remove('drop-target');
+
+ // 获取所有卡片
+ const container = document.querySelector('.channel-content');
+ const cards = Array.from(container.querySelectorAll('.module-card'));
+
+ const fromIndex = cards.indexOf(this.draggingElement);
+ const toIndex = cards.indexOf(targetCard);
+
+ if (fromIndex === -1 || toIndex === -1) return;
+
+ // 重新排序
+ if (window.ChannelPreferences) {
+ ChannelPreferences.reorderModules(fromIndex, toIndex);
+ }
+
+ // 移动 DOM 元素
+ if (fromIndex < toIndex) {
+ targetCard.insertAdjacentElement('afterend', this.draggingElement);
+ } else {
+ targetCard.insertAdjacentElement('beforebegin', this.draggingElement);
+ }
+
+ // 触发事件
+ if (window.EventBus) {
+ EventBus.emit('favorites:reordered', {
+ from: fromIndex,
+ to: toIndex,
+ module: this.draggingElement.dataset.module
+ });
+ }
+ },
+
+ // 刷新拖拽功能(在布局变化后调用)
+ refreshDragAndDrop: function() {
+ this.initDragAndDrop();
+ }
+};
+
+console.log('[favorites] 已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-layout.css b/agents/zhuyuan-dev-agent/modules/m-channel/channel-layout.css
new file mode 100644
index 0000000..31c39c3
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-layout.css
@@ -0,0 +1,145 @@
+/* 频道布局样式 - 三种布局模式 */
+
+/* 基础卡片样式(所有布局共用) */
+.module-card {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ transition: all 0.3s ease;
+ overflow: hidden;
+ position: relative;
+}
+
+.module-card:hover {
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ transform: translateY(-2px);
+}
+
+/* 卡片头部 */
+.module-card-header {
+ padding: 16px;
+ border-bottom: 1px solid #eee;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.module-card-title {
+ font-size: 18px;
+ font-weight: 600;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* 收藏星星 */
+.favorite-star {
+ font-size: 20px;
+ color: #ccc;
+ cursor: pointer;
+ transition: color 0.2s;
+ user-select: none;
+}
+
+.favorite-star.active {
+ color: #fbbf24;
+}
+
+.favorite-star:hover {
+ transform: scale(1.1);
+}
+
+/* 卡片内容区 */
+.module-card-content {
+ padding: 16px;
+ min-height: 100px;
+}
+
+/* 拖拽把手 */
+.drag-handle {
+ font-size: 20px;
+ color: #999;
+ cursor: grab;
+ user-select: none;
+ margin-right: 8px;
+}
+
+.drag-handle:active {
+ cursor: grabbing;
+}
+
+/* ===== 布局1:网格模式(默认) ===== */
+.layout-grid .channel-content {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: 24px;
+ padding: 24px;
+}
+
+.layout-grid .module-card {
+ height: fit-content;
+}
+
+/* ===== 布局2:列表模式(一行一个) ===== */
+.layout-list .channel-content {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ padding: 24px;
+}
+
+.layout-list .module-card {
+ width: 100%;
+}
+
+.layout-list .module-card-header {
+ padding: 12px 16px;
+}
+
+.layout-list .module-card-content {
+ padding: 12px 16px;
+}
+
+/* ===== 布局3:紧凑模式(小卡片密排) ===== */
+.layout-compact .channel-content {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: 12px;
+ padding: 16px;
+}
+
+.layout-compact .module-card {
+ font-size: 14px;
+}
+
+.layout-compact .module-card-header {
+ padding: 10px 12px;
+}
+
+.layout-compact .module-card-title {
+ font-size: 16px;
+}
+
+.layout-compact .module-card-content {
+ padding: 10px 12px;
+ min-height: 80px;
+}
+
+/* 布局切换动画 */
+.channel-content {
+ transition: all 0.3s ease-in-out;
+}
+
+/* 拖拽中的样式 */
+.module-card.dragging {
+ opacity: 0.6;
+ transform: scale(0.98);
+ box-shadow: 0 8px 16px rgba(0,0,0,0.2);
+}
+
+/* 拖拽放置区高亮 */
+.module-card.drop-target {
+ border: 2px dashed #3b82f6;
+ background: #f0f9ff;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-notifications.css b/agents/zhuyuan-dev-agent/modules/m-channel/channel-notifications.css
new file mode 100644
index 0000000..5905e8d
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-notifications.css
@@ -0,0 +1,200 @@
+/* 频道通知系统样式 - 环节7 */
+:root {
+ --update-badge-bg: #2196f3;
+ --unread-dot-bg: #f44336;
+ --notification-panel-bg: #ffffff;
+ --notification-panel-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ --notification-hover-bg: #f5f5f5;
+}
+
+/* 更新标签(蓝色) */
+.update-badge {
+ display: inline-block;
+ background-color: var(--update-badge-bg);
+ color: white;
+ font-size: 12px;
+ font-weight: 500;
+ padding: 2px 8px;
+ border-radius: 12px;
+ margin-top: 8px;
+ align-self: flex-start;
+}
+
+/* 未读小红点(带数字) */
+.unread-dot {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ min-width: 18px;
+ height: 18px;
+ background-color: var(--unread-dot-bg);
+ color: white;
+ font-size: 12px;
+ font-weight: bold;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 4px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ z-index: 10;
+}
+
+/* 铃铛图标容器 */
+.bell-container {
+ position: relative;
+ display: inline-block;
+ margin-right: 16px;
+ cursor: pointer;
+}
+
+.bell-icon {
+ font-size: 24px;
+ color: #555;
+ transition: color 0.2s;
+}
+
+.bell-icon:hover {
+ color: #000;
+}
+
+/* 铃铛上的数字气泡 */
+.bell-badge {
+ position: absolute;
+ top: -4px;
+ right: -6px;
+ min-width: 18px;
+ height: 18px;
+ background-color: var(--unread-dot-bg);
+ color: white;
+ font-size: 12px;
+ font-weight: bold;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 4px;
+ z-index: 20;
+}
+
+/* 侧滑通知面板 */
+.notification-panel {
+ position: fixed;
+ top: 0;
+ right: -400px;
+ width: 360px;
+ height: 100vh;
+ background: var(--notification-panel-bg);
+ box-shadow: var(--notification-panel-shadow);
+ transition: right 0.3s ease;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ border-left: 1px solid #e0e0e0;
+}
+
+.notification-panel.open {
+ right: 0;
+}
+
+.panel-header {
+ padding: 20px;
+ border-bottom: 1px solid #e0e0e0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-weight: 600;
+}
+
+.panel-header button {
+ background: none;
+ border: none;
+ color: #2196f3;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.panel-header button:hover {
+ text-decoration: underline;
+}
+
+.notification-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+
+.notification-item {
+ padding: 16px 20px;
+ border-bottom: 1px solid #f0f0f0;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.notification-item:hover {
+ background: var(--notification-hover-bg);
+}
+
+.notification-item.read {
+ opacity: 0.6;
+}
+
+.notification-time {
+ font-size: 12px;
+ color: #999;
+ margin-bottom: 4px;
+}
+
+.notification-module {
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.notification-summary {
+ font-size: 14px;
+ color: #333;
+}
+
+.empty-state {
+ padding: 40px 20px;
+ text-align: center;
+ color: #999;
+ font-size: 14px;
+}
+
+/* 遮罩层 */
+.panel-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0,0,0,0.3);
+ z-index: 999;
+ display: none;
+}
+
+.panel-overlay.show {
+ display: block;
+}
+
+/* 让模块卡片成为相对定位容器(用于小红点定位) */
+.module-card {
+ position: relative !important;
+}
+
+/* 强制修复高亮点击问题 */
+.search-highlight,
+.highlight,
+[class*="highlight"] {
+ pointer-events: none !important;
+}
+
+/* 确保卡片本身可点击 */
+.module-card,
+[class*="module-card"] {
+ cursor: pointer;
+ pointer-events: auto !important;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-notifications.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-notifications.js
new file mode 100644
index 0000000..a14c781
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-notifications.js
@@ -0,0 +1,365 @@
+/**
+ * 频道通知系统 - 环节7 (晨星陪伴版)
+ * 功能:模块更新提醒、未读小红点、通知面板、高亮点击修复
+ * 修改:通过卡片文字内容匹配模块ID(解决moduleId undefined问题)
+ */
+
+(function() {
+ // ========== 初始化模拟数据 ==========
+ const STORAGE_KEY = 'channel_notifications';
+
+ // 默认模拟数据(基于已完成模块 M06, M08, M11, 以及当前频道模块)
+ const DEFAULT_UPDATES = {
+ 'M06': {
+ hasUpdate: true,
+ count: 2,
+ updates: [
+ { time: '10:30', summary: '修复了拖拽排序的bug' },
+ { time: '昨天', summary: '新增统计面板' }
+ ]
+ },
+ 'M08': {
+ hasUpdate: true,
+ count: 1,
+ updates: [
+ { time: '昨天', summary: '优化了模块加载性能' }
+ ]
+ },
+ 'M11': {
+ hasUpdate: true,
+ count: 3,
+ updates: [
+ { time: '15:20', summary: '新增键盘快捷键' },
+ { time: '昨天', summary: '修复焦点管理' },
+ { time: '3月10日', summary: '模块生命周期完善' }
+ ]
+ },
+ 'channel': {
+ hasUpdate: true,
+ count: 1,
+ updates: [
+ { time: '现在', summary: '通知系统上线啦!' }
+ ]
+ }
+ };
+
+ // 加载或初始化数据
+ let notificationData = JSON.parse(localStorage.getItem(STORAGE_KEY));
+ if (!notificationData) {
+ notificationData = DEFAULT_UPDATES;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(notificationData));
+ }
+
+ // ========== 工具函数:从卡片文字猜测模块ID ==========
+ function guessModuleIdFromCard(card) {
+ // 尝试找卡片里的标题文字
+ const titleElem = card.querySelector('h3, .module-title, .card-title, .module-name');
+ if (titleElem) {
+ const text = titleElem.textContent.trim();
+ // 简单映射:如果文字包含“工单” -> M06
+ if (text.includes('工单')) return 'M06';
+ if (text.includes('数据统计')) return 'M08';
+ if (text.includes('组件库')) return 'M11';
+ if (text.includes('调试面板')) return 'M11'; // 暂时用 M11
+ }
+
+ // 后备:用卡片内所有文字尝试匹配
+ const fullText = card.textContent;
+ if (fullText.includes('工单')) return 'M06';
+ if (fullText.includes('数据统计')) return 'M08';
+ if (fullText.includes('组件库')) return 'M11';
+
+ return null; // 实在猜不到就返回 null
+ }
+
+ function getModuleCards() {
+ return document.querySelectorAll('.module-card');
+ }
+
+ // ========== 更新标签渲染(带日志) ==========
+ function renderUpdateBadges() {
+ console.log('🟦 renderUpdateBadges 开始执行');
+ const cards = document.querySelectorAll('.module-card');
+ console.log('找到卡片数量:', cards.length);
+
+ cards.forEach(card => {
+ // 先尝试原有方法,失败则用猜测
+ let moduleId = card.dataset.moduleId || card.id || card.querySelector('.module-name')?.textContent.trim();
+ if (!moduleId) {
+ moduleId = guessModuleIdFromCard(card);
+ }
+ console.log('卡片最终 moduleId:', moduleId);
+
+ // 移除旧的标签
+ const oldBadge = card.querySelector('.update-badge');
+ if (oldBadge) oldBadge.remove();
+
+ const modData = moduleId ? notificationData[moduleId] : null;
+ console.log('模块数据:', moduleId, modData);
+
+ if (modData && modData.hasUpdate) {
+ console.log('✅ 应该添加标签的模块:', moduleId);
+ const badge = document.createElement('span');
+ badge.className = 'update-badge';
+ badge.textContent = '有更新';
+ // 内联样式保证可见
+ badge.style.backgroundColor = '#2196f3';
+ badge.style.color = 'white';
+ badge.style.padding = '2px 8px';
+ badge.style.borderRadius = '12px';
+ badge.style.fontSize = '12px';
+ badge.style.display = 'inline-block';
+ badge.style.marginTop = '8px';
+ // 直接追加到卡片末尾
+ card.appendChild(badge);
+ console.log('✅ 标签已追加到卡片', moduleId);
+ } else {
+ console.log('❌ 不需要添加标签的模块:', moduleId);
+ }
+ });
+ console.log('🟦 renderUpdateBadges 执行完毕');
+ }
+
+ // ========== 小红点渲染 ==========
+ function renderUnreadDots() {
+ const cards = getModuleCards();
+ cards.forEach(card => {
+ let moduleId = card.dataset.moduleId || card.id || card.querySelector('.module-name')?.textContent.trim();
+ if (!moduleId) {
+ moduleId = guessModuleIdFromCard(card);
+ }
+ if (!moduleId) return;
+
+ const oldDot = card.querySelector('.unread-dot');
+ if (oldDot) oldDot.remove();
+
+ const modData = notificationData[moduleId];
+ if (modData && modData.hasUpdate && modData.count > 0) {
+ const dot = document.createElement('span');
+ dot.className = 'unread-dot';
+ dot.textContent = modData.count > 9 ? '9+' : modData.count;
+ card.appendChild(dot);
+ }
+ });
+ }
+
+ // ========== 标记模块已读 ==========
+ function markModuleAsRead(moduleId) {
+ if (notificationData[moduleId]) {
+ notificationData[moduleId].hasUpdate = false;
+ notificationData[moduleId].count = 0;
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(notificationData));
+ renderUpdateBadges();
+ renderUnreadDots();
+ updateBellBadge();
+ renderNotificationPanel();
+ }
+ }
+
+ // ========== 铃铛相关 ==========
+ let bellContainer, panel, overlay;
+ let isPanelOpen = false;
+
+ function createBell() {
+ const header = document.querySelector('.channel-header') || document.querySelector('header') || document.body;
+ bellContainer = document.createElement('div');
+ bellContainer.className = 'bell-container';
+ bellContainer.innerHTML = `
+ 🔔
+ 0
+ `;
+ const title = header.querySelector('h1, h2');
+ if (title) {
+ title.insertAdjacentElement('afterend', bellContainer);
+ } else {
+ header.appendChild(bellContainer);
+ }
+
+ panel = document.createElement('div');
+ panel.className = 'notification-panel';
+ panel.innerHTML = `
+
+
+ `;
+ overlay = document.createElement('div');
+ overlay.className = 'panel-overlay';
+ document.body.appendChild(panel);
+ document.body.appendChild(overlay);
+
+ bellContainer.addEventListener('click', togglePanel);
+ overlay.addEventListener('click', closePanel);
+ panel.querySelector('.mark-all-read').addEventListener('click', markAllRead);
+ }
+
+ function updateBellBadge() {
+ const totalUnread = Object.values(notificationData).reduce((acc, mod) => acc + (mod.count || 0), 0);
+ const badge = bellContainer?.querySelector('.bell-badge');
+ if (badge) {
+ if (totalUnread > 0) {
+ badge.style.display = 'flex';
+ badge.textContent = totalUnread > 9 ? '9+' : totalUnread;
+ } else {
+ badge.style.display = 'none';
+ }
+ }
+ }
+
+ function togglePanel(e) {
+ e.stopPropagation();
+ isPanelOpen ? closePanel() : openPanel();
+ }
+
+ function openPanel() {
+ isPanelOpen = true;
+ panel.classList.add('open');
+ overlay.classList.add('show');
+ renderNotificationPanel();
+ }
+
+ function closePanel() {
+ isPanelOpen = false;
+ panel.classList.remove('open');
+ overlay.classList.remove('show');
+ }
+
+ function renderNotificationPanel() {
+ const list = panel.querySelector('.notification-list');
+ if (!list) return;
+
+ let items = [];
+ for (const [moduleId, modData] of Object.entries(notificationData)) {
+ if (modData.updates && modData.updates.length > 0) {
+ modData.updates.forEach((update) => {
+ items.push({
+ moduleId,
+ time: update.time,
+ summary: update.summary,
+ read: !modData.hasUpdate
+ });
+ });
+ }
+ }
+
+ if (items.length === 0) {
+ list.innerHTML = '暂无新通知';
+ return;
+ }
+
+ items.sort((a, b) => (a.time > b.time ? -1 : 1));
+
+ list.innerHTML = items.map(item => `
+
+ ${item.time}
+ ${item.moduleId}
+ ${item.summary}
+
+ `).join('');
+
+ list.querySelectorAll('.notification-item').forEach(item => {
+ item.addEventListener('click', (e) => {
+ const moduleId = item.dataset.module;
+ const card = findCardByModuleId(moduleId);
+ if (card) {
+ card.click();
+ }
+ markModuleAsRead(moduleId);
+ closePanel();
+ });
+ });
+ }
+
+ function findCardByModuleId(moduleId) {
+ const cards = getModuleCards();
+ for (let card of cards) {
+ let id = card.dataset.moduleId || card.id || card.querySelector('.module-name')?.textContent.trim();
+ if (!id) id = guessModuleIdFromCard(card);
+ if (id === moduleId) return card;
+ }
+ return null;
+ }
+
+ function markAllRead() {
+ for (let moduleId in notificationData) {
+ notificationData[moduleId].hasUpdate = false;
+ notificationData[moduleId].count = 0;
+ }
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(notificationData));
+ renderUpdateBadges();
+ renderUnreadDots();
+ updateBellBadge();
+ renderNotificationPanel();
+ }
+
+ function fixHighlightClick() {
+ const container = document.querySelector('.modules-grid') || document.body;
+ container.addEventListener('click', (e) => {
+ const card = e.target.closest('.module-card');
+ if (card) {
+ // 不做额外处理,只是确保事件冒泡
+ }
+ }, true);
+
+ const style = document.createElement('style');
+ style.textContent = `
+ .search-highlight {
+ pointer-events: none !important;
+ }
+ .module-card {
+ cursor: pointer;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ function init() {
+ if (!document.querySelector('link[href*="channel-notifications.css"]')) {
+ const link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = 'channel-notifications.css';
+ document.head.appendChild(link);
+ }
+
+ renderUpdateBadges();
+ renderUnreadDots();
+
+ createBell();
+ updateBellBadge();
+
+ fixHighlightClick();
+
+ document.addEventListener('click', (e) => {
+ const card = e.target.closest('.module-card');
+ if (card) {
+ let moduleId = card.dataset.moduleId || card.id || card.querySelector('.module-name')?.textContent.trim();
+ if (!moduleId) moduleId = guessModuleIdFromCard(card);
+ if (moduleId) {
+ setTimeout(() => markModuleAsRead(moduleId), 100);
+ }
+ }
+ });
+
+ window.addEventListener('modulesRendered', () => {
+ renderUpdateBadges();
+ renderUnreadDots();
+ });
+
+ let lastCardCount = 0;
+ setInterval(() => {
+ const cards = getModuleCards();
+ if (cards.length !== lastCardCount) {
+ lastCardCount = cards.length;
+ renderUpdateBadges();
+ renderUnreadDots();
+ }
+ }, 2000);
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-preferences.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-preferences.js
new file mode 100644
index 0000000..a4ee89c
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-preferences.js
@@ -0,0 +1,194 @@
+// 频道偏好管理 - 记住用户的装修设置
+window.ChannelPreferences = {
+ STORAGE_KEY: 'hololake_channel_preferences',
+
+ // 默认配置
+ defaults: {
+ layout: 'grid', // grid, list, compact
+ theme: 'default', // default, ocean, forest, sunset, lavender
+ favorites: [], // 收藏的模块ID列表
+ moduleOrder: [], // 模块排序顺序(默认按ID)
+ stats: {} // 使用统计
+ },
+
+ // 当前配置
+ config: null,
+
+ // 初始化(加载配置)
+ init: function() {
+ console.log('[preferences] 初始化');
+ this.load();
+ return this.config;
+ },
+
+ // 加载配置
+ load: function() {
+ const saved = localStorage.getItem(this.STORAGE_KEY);
+ if (saved) {
+ try {
+ this.config = JSON.parse(saved);
+ console.log('[preferences] 加载配置:', this.config);
+ } catch (e) {
+ console.error('[preferences] 解析失败,使用默认配置', e);
+ this.config = { ...this.defaults };
+ }
+ } else {
+ console.log('[preferences] 无保存配置,使用默认');
+ this.config = { ...this.defaults };
+ }
+ return this.config;
+ },
+
+ // 保存配置
+ save: function() {
+ if (!this.config) return;
+ localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.config));
+ console.log('[preferences] 保存配置:', this.config);
+ },
+
+ // 获取指定键的值
+ get: function(key) {
+ if (!this.config) this.load();
+ return this.config[key];
+ },
+
+ // 设置指定键的值(自动保存)
+ set: function(key, value) {
+ if (!this.config) this.load();
+ this.config[key] = value;
+ this.save();
+
+ // 触发事件总线通知
+ if (window.EventBus) {
+ EventBus.emit('preferences:changed', { key, value });
+ }
+ },
+
+ // 更新整个配置(合并)
+ update: function(newConfig) {
+ if (!this.config) this.load();
+ this.config = { ...this.config, ...newConfig };
+ this.save();
+ if (window.EventBus) {
+ EventBus.emit('preferences:changed', { full: this.config });
+ }
+ },
+
+ // 恢复默认设置
+ reset: function() {
+ this.config = { ...this.defaults };
+ this.save();
+ if (window.EventBus) {
+ EventBus.emit('preferences:reset', this.config);
+ }
+ console.log('[preferences] 恢复默认');
+ },
+
+ // 记录模块使用(用于统计)
+ recordUsage: function(moduleId) {
+ if (!this.config) this.load();
+ if (!this.config.stats) this.config.stats = {};
+ if (!this.config.stats[moduleId]) {
+ this.config.stats[moduleId] = {
+ count: 0,
+ lastUsed: null,
+ totalTime: 0
+ };
+ }
+ this.config.stats[moduleId].count++;
+ this.config.stats[moduleId].lastUsed = Date.now();
+ this.save();
+
+ // 开始计时(将在模块卸载时记录时长)
+ this.startTiming(moduleId);
+ },
+
+ // 计时相关
+ timing: {},
+ startTiming: function(moduleId) {
+ this.timing[moduleId] = Date.now();
+ },
+ stopTiming: function(moduleId) {
+ if (this.timing[moduleId]) {
+ const duration = (Date.now() - this.timing[moduleId]) / 1000; // 秒
+ if (this.config.stats[moduleId]) {
+ this.config.stats[moduleId].totalTime += duration;
+ this.save();
+ }
+ delete this.timing[moduleId];
+ }
+ },
+
+ // 获取统计报告
+ getStats: function() {
+ if (!this.config) this.load();
+ return this.config.stats || {};
+ },
+
+ // 获取收藏列表
+ getFavorites: function() {
+ return this.get('favorites') || [];
+ },
+
+ // 切换收藏状态
+ toggleFavorite: function(moduleId) {
+ let favorites = this.get('favorites') || [];
+ if (favorites.includes(moduleId)) {
+ favorites = favorites.filter(id => id !== moduleId);
+ } else {
+ favorites.push(moduleId);
+ }
+ this.set('favorites', favorites);
+ return favorites.includes(moduleId);
+ },
+
+ // 获取布局
+ getLayout: function() {
+ return this.get('layout') || 'grid';
+ },
+
+ // 设置布局
+ setLayout: function(layout) {
+ this.set('layout', layout);
+ },
+
+ // 获取主题
+ getTheme: function() {
+ return this.get('theme') || 'default';
+ },
+
+ // 设置主题
+ setTheme: function(theme) {
+ this.set('theme', theme);
+ },
+
+ // 获取模块排序
+ getModuleOrder: function() {
+ return this.get('moduleOrder') || [];
+ },
+
+ // 设置模块排序
+ setModuleOrder: function(order) {
+ this.set('moduleOrder', order);
+ },
+
+ // 重新排序(拖拽后调用)
+ reorderModules: function(fromIndex, toIndex) {
+ const order = this.getModuleOrder();
+ if (order.length === 0) {
+ // 如果还没有自定义顺序,则基于当前模块列表生成
+ const modules = window.ModuleRegistry ? ModuleRegistry.list() : [];
+ // 排除非显示模块(如调试面板)
+ const displayModules = modules.filter(m => ['m06','m08','m11','home','debug'].includes(m));
+ this.setModuleOrder(displayModules);
+ }
+ // 重新加载最新顺序
+ const currentOrder = this.getModuleOrder();
+ if (fromIndex < 0 || fromIndex >= currentOrder.length || toIndex < 0 || toIndex >= currentOrder.length) return;
+ const [moved] = currentOrder.splice(fromIndex, 1);
+ currentOrder.splice(toIndex, 0, moved);
+ this.setModuleOrder(currentOrder);
+ }
+};
+
+console.log('[preferences] 已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-router.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-router.js
new file mode 100644
index 0000000..0896208
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-router.js
@@ -0,0 +1,243 @@
+// 频道路由(支持真实模块适配器 + 卡片列表首页 + 数据采集钩子)
+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');
+ }
+ });
+ });
+ }
+
+ // 页面关闭时结束计时
+ window.addEventListener('beforeunload', function() {
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.endSession();
+ }
+ });
+ },
+
+ 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;
+
+ // 数据采集钩子:开始加载
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadStart();
+ if (channel !== 'home' && channel !== 'debug' && channel !== 'dashboard' && channel !== 'settings') {
+ ChannelAnalytics.recordVisit(channel);
+ }
+ }
+
+ // 如果是真实模块(m06/m08/m11)
+ if (channel === 'm06' || channel === 'm08' || channel === 'm11') {
+ if (window.ModuleAdapter) {
+ ModuleAdapter.loadModule(channel, 'channel-content');
+ // 模块加载完成后记录加载时间
+ setTimeout(() => {
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadEnd(channel);
+ }
+ }, 100);
+ } else {
+ this.contentEl.innerHTML = '适配器未加载
';
+ }
+ ModuleLifecycle.onLoad(channel);
+ return;
+ }
+
+ // 数据面板路由(环节8新增)
+ if (channel === 'dashboard') {
+ fetch('views/channel-dashboard.html')
+ .then(response => response.text())
+ .then(html => {
+ this.contentEl.innerHTML = html;
+ // 渲染图表
+ if (typeof ChannelDashboard !== 'undefined') {
+ ChannelDashboard.render();
+ }
+ ModuleLifecycle.onLoad('dashboard');
+ // 记录加载完成
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadEnd('dashboard');
+ }
+ })
+ .catch(err => {
+ this.contentEl.innerHTML = '数据面板加载失败
';
+ console.error(err);
+ });
+ return;
+ }
+
+ // 设置中心路由(环节9新增)
+ if (channel === 'settings') {
+ fetch('views/channel-settings.html')
+ .then(r => r.text())
+ .then(html => {
+ this.contentEl.innerHTML = html;
+ if (typeof SettingsUI !== 'undefined') {
+ SettingsUI.init();
+ }
+ // 记录加载完成
+ if (typeof ChannelAnalytics !== 'undefined') {
+ ChannelAnalytics.markLoadEnd('settings');
+ }
+ })
+ .catch(err => {
+ this.contentEl.innerHTML = '设置面板加载失败
';
+ console.error(err);
+ });
+ return;
+ }
+
+ // 内置频道
+ if (channel === 'home') {
+ this.renderModuleCards();
+ } else if (channel === 'debug') {
+ this.loadDebugPanel();
+ } else {
+ this.contentEl.innerHTML = '未知频道
';
+ }
+
+ // 对于内置频道也记录加载完成
+ if (typeof ChannelAnalytics !== 'undefined' && channel !== 'dashboard' && channel !== 'settings') {
+ ChannelAnalytics.markLoadEnd(channel);
+ }
+ },
+
+ // 渲染所有模块卡片(用于首页)
+ renderModuleCards: function() {
+ const modules = [
+ { id: 'm06', name: '工单管理', icon: '📋', desc: '管理任务和工单' },
+ { id: 'm08', name: '数据统计', icon: '📊', desc: '查看数据统计和报表' },
+ { id: 'm11', name: '组件库', icon: '🧩', desc: '系统组件展示' },
+ { id: 'debug', name: '调试面板', icon: '🐞', desc: '查看事件和模块状态' },
+ { id: 'dashboard', name: '数据面板', icon: '📈', desc: '查看使用统计和性能' }
+ ];
+
+ let html = '';
+ modules.forEach(mod => {
+ const isFavorite = window.ChannelPreferences ?
+ ChannelPreferences.getFavorites().includes(mod.id) : false;
+ html += `
+
+ `;
+ });
+ html += '
';
+
+ this.contentEl.innerHTML = html;
+ ModuleLifecycle.onLoad('home');
+
+ // 触发收藏/拖拽重新初始化
+ if (window.ChannelFavorites) {
+ setTimeout(() => {
+ ChannelFavorites.init();
+ ChannelFavorites.initDragAndDrop();
+ }, 100);
+ }
+ },
+
+ loadDebugPanel: function() {
+ fetch('views/channel-debug.html')
+ .then(response => response.text())
+ .then(html => {
+ this.contentEl.innerHTML = html;
+ ModuleLifecycle.onLoad('debug');
+ this.initDebugPanel();
+ })
+ .catch(err => {
+ this.contentEl.innerHTML = '调试面板加载失败
';
+ });
+ },
+
+ 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('状态已清除,刷新页面生效');
+ });
+ }
+ },
+
+ getVisitedChannels: function() {
+ const visited = [];
+ document.querySelectorAll('.channel-btn.visited').forEach(btn => {
+ visited.push(btn.dataset.channel);
+ });
+ return visited;
+ }
+};
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-router.js.bakcat b/agents/zhuyuan-dev-agent/modules/m-channel/channel-router.js.bakcat
new file mode 100644
index 0000000..e69de29
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings-ui.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings-ui.js
new file mode 100644
index 0000000..3d07af7
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings-ui.js
@@ -0,0 +1,123 @@
+/**
+ * channel-settings-ui.js
+ * 设置面板UI控制器·环节9
+ */
+var SettingsUI = (function() {
+ // 渲染主题卡片
+ function renderThemeCards() {
+ var container = document.getElementById('themeCards');
+ if (!container) return;
+ var current = ChannelSettings.get('theme');
+ var themes = ChannelSettings.THEMES;
+ var html = '';
+ Object.keys(themes).forEach(function(id) {
+ var t = themes[id];
+ var active = (id === current) ? 'active' : '';
+ html += '';
+ });
+ container.innerHTML = html;
+ }
+
+ // 同步UI状态
+ function syncUI() {
+ var s = ChannelSettings.get();
+
+ // 下拉框
+ var fontSize = document.getElementById('settingFontSize');
+ if (fontSize) fontSize.value = s.fontSize;
+
+ var interval = document.getElementById('settingRefreshInterval');
+ if (interval) interval.value = s.refreshInterval;
+
+ // 开关
+ var toggleMap = {
+ settingAnimation: 'animationEnabled',
+ settingSidebar: 'sidebarCollapsed',
+ settingNotifyModule: 'notifyModuleUpdate',
+ settingNotifySystem: 'notifySystem',
+ settingNotifySound: 'notifySound',
+ settingAnalytics: 'analyticsEnabled',
+ settingAutoRefresh: 'autoRefresh'
+ };
+ Object.keys(toggleMap).forEach(function(elId) {
+ var el = document.getElementById(elId);
+ if (el) el.checked = !!s[toggleMap[elId]];
+ });
+
+ renderThemeCards();
+ }
+
+ return {
+ // 初始化面板
+ init: function() {
+ syncUI();
+ console.log('⚙️ 设置面板已加载');
+ },
+ // 选择主题
+ selectTheme: function(themeId) {
+ ChannelSettings.set('theme', themeId);
+ renderThemeCards();
+ },
+ // 开关变更
+ onToggle: function(key, value) {
+ ChannelSettings.set(key, value);
+ console.log('⚙️ ' + key + ' = ' + value);
+ },
+ // 下拉变更
+ onChange: function(key, value) {
+ ChannelSettings.set(key, value);
+ console.log('⚙️ ' + key + ' = ' + value);
+ },
+ // 导出
+ exportSettings: function() {
+ var json = ChannelSettings.exportJSON();
+ if (navigator.clipboard) {
+ navigator.clipboard.writeText(json).then(function() {
+ alert('✅ 设置已复制到剪贴板!\n\n可以保存为文件或发给其他设备。');
+ });
+ } else {
+ // 降级:显示在弹窗
+ prompt('复制下面的内容:', json);
+ }
+ console.log('⚙️ 设置已导出');
+ },
+ // 显示导入弹窗
+ showImport: function() {
+ var modal = document.getElementById('importModal');
+ if (modal) modal.classList.add('show');
+ },
+ // 隐藏导入弹窗
+ hideImport: function() {
+ var modal = document.getElementById('importModal');
+ if (modal) modal.classList.remove('show');
+ },
+ // 执行导入
+ doImport: function() {
+ var textarea = document.getElementById('importTextarea');
+ if (!textarea || !textarea.value.trim()) {
+ alert('请粘贴JSON内容');
+ return;
+ }
+ var ok = ChannelSettings.importJSON(textarea.value.trim());
+ if (ok) {
+ alert('✅ 设置导入成功!');
+ this.hideImport();
+ syncUI();
+ } else {
+ alert('❌ JSON格式错误,请检查内容');
+ }
+ },
+ // 恢复默认
+ resetAll: function() {
+ if (confirm('确定恢复所有设置为默认值吗?')) {
+ ChannelSettings.reset();
+ syncUI();
+ alert('✅ 已恢复默认设置');
+ }
+ }
+ };
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings.css b/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings.css
new file mode 100644
index 0000000..577df35
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings.css
@@ -0,0 +1,258 @@
+/**
+ * channel-settings.css
+ * 频道设置面板样式 · 环节9
+ */
+.settings-container {
+ padding: 20px;
+ max-width: 800px;
+ margin: 0 auto;
+ color: var(--text-color, #e0e0e0);
+}
+
+.settings-header {
+ text-align: center;
+ margin-bottom: 30px;
+}
+
+.settings-header h2 {
+ font-size: 24px;
+ color: var(--accent-color, #4fc3f7);
+ margin-bottom: 8px;
+}
+
+/* 设置分组 */
+.settings-group {
+ background: var(--card-bg, #1a1a2e);
+ border-radius: 12px;
+ padding: 20px;
+ margin-bottom: 20px;
+ border: 1px solid var(--border-color, #2a2a4a);
+}
+
+.settings-group h3 {
+ font-size: 16px;
+ color: var(--accent-color, #4fc3f7);
+ margin-bottom: 15px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid var(--border-color, #2a2a4a);
+}
+
+/* 设置行 */
+.setting-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 0;
+ border-bottom: 1px solid rgba(255,255,255,0.05);
+}
+
+.setting-row:last-child {
+ border-bottom: none;
+}
+
+.setting-label {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+
+.setting-label .title {
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.setting-label .desc {
+ font-size: 12px;
+ color: #888;
+ margin-top: 2px;
+}
+
+/* 开关组件 */
+.toggle-switch {
+ position: relative;
+ display: inline-block;
+ width: 44px;
+ height: 24px;
+ margin-left: 16px;
+}
+
+.toggle-switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.toggle-slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #555;
+ transition: .3s;
+ border-radius: 24px;
+}
+
+.toggle-slider:before {
+ position: absolute;
+ content: "";
+ height: 18px;
+ width: 18px;
+ left: 3px;
+ bottom: 3px;
+ background: white;
+ transition: .3s;
+ border-radius: 50%;
+}
+
+input:checked + .toggle-slider {
+ background: var(--accent-color, #4fc3f7);
+}
+
+input:checked + .toggle-slider::before {
+ transform: translateX(20px);
+}
+
+/* 下拉选择 */
+.setting-select {
+ background: var(--card-bg, #1a1a2e);
+ color: var(--text-color, #e0e0e0);
+ border: 1px solid var(--border-color, #2a2a4a);
+ border-radius: 8px;
+ padding: 6px 12px;
+ font-size: 14px;
+ cursor: pointer;
+ margin-left: 16px;
+}
+
+/* 主题预览卡片 */
+.theme-cards {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ margin-top: 10px;
+}
+
+.theme-card {
+ width: 120px;
+ padding: 12px;
+ border-radius: 10px;
+ cursor: pointer;
+ text-align: center;
+ font-size: 13px;
+ border: 2px solid transparent;
+ transition: border-color 0.3s, transform 0.2s;
+}
+
+.theme-card:hover {
+ transform: translateY(-2px);
+}
+
+.theme-card.active {
+ border-color: var(--accent-color, #4fc3f7);
+}
+
+.theme-preview {
+ height: 40px;
+ border-radius: 6px;
+ margin-bottom: 8px;
+}
+
+/* 操作按钮组 */
+.settings-actions {
+ display: flex;
+ gap: 12px;
+ justify-content: center;
+ margin-top: 24px;
+ flex-wrap: wrap;
+}
+
+.btn-settings {
+ padding: 10px 24px;
+ border-radius: 8px;
+ font-size: 14px;
+ cursor: pointer;
+ border: none;
+ transition: background 0.3s, transform 0.2s;
+}
+
+.btn-settings:hover {
+ transform: translateY(-1px);
+}
+
+.btn-export {
+ background: #66bb6a;
+ color: white;
+}
+
+.btn-import {
+ background: #42a5f5;
+ color: white;
+}
+
+.btn-reset {
+ background: #ff7043;
+ color: white;
+}
+
+/* 导入弹窗 */
+.import-modal {
+ display: none;
+ position: fixed;
+ top: 0; left: 0; width: 100%; height: 100%;
+ background: rgba(0,0,0,0.6);
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.import-modal.show {
+ display: flex;
+}
+
+.import-dialog {
+ background: var(--card-bg, #1a1a2e);
+ border-radius: 16px;
+ padding: 24px;
+ max-width: 500px;
+ width: 90%;
+ border: 1px solid var(--border-color);
+}
+
+.import-textarea {
+ width: 100%;
+ height: 150px;
+ background: #111;
+ color: #e0e0e0;
+ border: 1px solid #333;
+ border-radius: 8px;
+ padding: 10px;
+ font-family: monospace;
+ font-size: 13px;
+ resize: vertical;
+ margin: 12px 0;
+}
+
+/* 响应式 */
+@media (max-width: 600px) {
+ .theme-cards {
+ justify-content: center;
+ }
+ .settings-actions {
+ flex-direction: column;
+ align-items: stretch;
+ }
+}
+
+/* 无动画模式 */
+.no-animation * {
+ transition: none !important;
+ animation: none !important;
+}
+
+/* 字体大小全局应用 */
+body, .channel-btn, .module-card, .channel-content, .nav-item,
+p, span, div, h1, h2, h3, h4, a, button, .setting-row, .setting-label {
+ font-size: var(--base-font-size, 15px);
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings.js
new file mode 100644
index 0000000..a7421d4
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-settings.js
@@ -0,0 +1,180 @@
+/**
+ * channel-settings.js
+ * 频道设置数据管理核心·环节9
+ * 统一管理所有用户偏好:主题、布局、通知、显示
+ */
+const ChannelSettings = (function(){
+ const STORAGE_KEY = 'channel-user-settings';
+
+ // 默认设置
+ const DEFAULTS = {
+ theme: 'dark', // dark / light / ocean
+ accentColor: '#4fc3f7', // 主题强调色
+ layout: 'grid', // grid / list
+ fontSize: 'medium', // small / medium / large
+ showWelcome: true, // 显示欢迎横幅
+ notifyModuleUpdate: true, // 模块更新通知
+ notifySystem: true, // 系统通知
+ notifySound: false, // 通知声音
+ autoRefresh: true, // 自动刷新数据
+ refreshInterval: 30, // 刷新间隔(秒)
+ sidebarCollapsed: false,// 侧边栏收起
+ animationEnabled: true, // 过渡动画
+ analyticsEnabled: true // 数据采集
+ };
+
+ // 主题预设
+ const THEMES = {
+ dark: {
+ name: '深色模式',
+ bg: '#0d1117',
+ cardBg: '#1a1a2e',
+ text: '#e0e0e0',
+ accent: '#4fc3f7',
+ border: '#2a2a4a'
+ },
+ light: {
+ name: '浅色模式',
+ bg: '#f5f5f5',
+ cardBg: '#ffffff',
+ text: '#333333',
+ accent: '#4fc3f7',
+ border: '#dddddd'
+ },
+ ocean: {
+ name: '海洋模式',
+ bg: '#1a2f3f',
+ cardBg: '#1e3a4a',
+ text: '#e0f0fa',
+ accent: '#7bc8e0',
+ border: '#2d5a6e'
+ }
+ };
+
+ // 加载设置
+ function load() {
+ try {
+ var stored = localStorage.getItem(STORAGE_KEY);
+ if (stored) {
+ var parsed = JSON.parse(stored);
+ // 合并默认值,保证新字段存在
+ var merged = {};
+ Object.keys(DEFAULTS).forEach(function(k) {
+ merged[k] = parsed.hasOwnProperty(k) ? parsed[k] : DEFAULTS[k];
+ });
+ return merged;
+ }
+ } catch(e) {
+ console.warn('读取设置失败,使用默认值', e);
+ }
+ return JSON.parse(JSON.stringify(DEFAULTS));
+ }
+
+ function save(settings) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
+ }
+
+ // ── 应用主题到页面 ──
+ function applyTheme(themeName) {
+ var theme = THEMES[themeName] || THEMES.dark;
+ var root = document.documentElement;
+ root.style.setProperty('--bg-color', theme.bg);
+ root.style.setProperty('--card-bg', theme.cardBg);
+ root.style.setProperty('--text-color', theme.text);
+ root.style.setProperty('--accent-color', theme.accent);
+ root.style.setProperty('--border-color', theme.border);
+ document.body.style.background = theme.bg;
+ document.body.style.color = theme.text;
+ console.log('⚙️ 主题已切换:' + theme.name);
+ }
+
+ // ── 应用字体大小 ──
+ function applyFontSize(size) {
+ var map = { small: '13px', medium: '15px', large: '17px' };
+ var fontSize = map[size] || '15px';
+ document.documentElement.style.setProperty('--base-font-size', fontSize);
+ document.body.style.fontSize = fontSize;
+ console.log('⚙️ 字体大小已应用:' + size + ' (' + fontSize + ')');
+ }
+
+ // ── 公开方法 ──
+ return {
+ DEFAULTS: DEFAULTS,
+ THEMES: THEMES,
+ get: function(key) {
+ var s = load();
+ return key ? s[key] : s;
+ },
+ set: function(key, value) {
+ var s = load();
+ s[key] = value;
+ save(s);
+ // 即时应用
+ if (key === 'theme') applyTheme(value);
+ if (key === 'fontSize') applyFontSize(value);
+ if (key === 'animationEnabled') {
+ document.body.classList.toggle('no-animation', !value);
+ }
+ },
+ setMultiple: function(obj) {
+ var s = load();
+ Object.keys(obj).forEach(function(k) { s[k] = obj[k]; });
+ save(s);
+ if (obj.theme) applyTheme(obj.theme);
+ if (obj.fontSize) applyFontSize(obj.fontSize);
+ },
+ reset: function() {
+ save(JSON.parse(JSON.stringify(DEFAULTS)));
+ applyTheme(DEFAULTS.theme);
+ applyFontSize(DEFAULTS.fontSize);
+ console.log('⚙️ 所有设置已恢复默认');
+ },
+ // 导出为JSON字符串
+ exportJSON: function() {
+ var s = load();
+ return JSON.stringify(s, null, 2);
+ },
+ // 从JSON字符串导入
+ importJSON: function(jsonStr) {
+ try {
+ var imported = JSON.parse(jsonStr);
+ var merged = {};
+ Object.keys(DEFAULTS).forEach(function(k) {
+ merged[k] = imported.hasOwnProperty(k) ? imported[k] : DEFAULTS[k];
+ });
+ save(merged);
+ applyTheme(merged.theme);
+ applyFontSize(merged.fontSize);
+ console.log('✅ 设置导入成功');
+ return true;
+ } catch(e) {
+ console.error('❌ 导入失败: JSON格式错误');
+ return false;
+ }
+ },
+ // 初始化(页面加载时调用)
+ init: function() {
+ var s = load();
+ applyTheme(s.theme);
+ applyFontSize(s.fontSize);
+ if (!s.animationEnabled) {
+ document.body.classList.add('no-animation');
+ }
+ console.log('⚙️ 频道设置已初始化');
+ },
+ getThemeList: function() {
+ return Object.keys(THEMES).map(function(k) {
+ return { id: k, name: THEMES[k].name };
+ });
+ }
+ };
+})();
+
+// 页面加载时自动初始化
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function() {
+ ChannelSettings.init();
+ });
+} else {
+ ChannelSettings.init();
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-state.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-state.js
new file mode 100644
index 0000000..38edffd
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-state.js
@@ -0,0 +1,44 @@
+// 状态管理(localStorage)
+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] 已清除');
+ }
+};
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-stats.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-stats.js
new file mode 100644
index 0000000..d4263fe
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-stats.js
@@ -0,0 +1,298 @@
+// 频道使用统计管理
+window.ChannelStats = {
+ // 初始化
+ init: function() {
+ console.log('[stats] 初始化');
+ this.bindEvents();
+ },
+
+ // 绑定事件
+ bindEvents: function() {
+ if (!window.EventBus) return;
+
+ // 监听模块加载,开始计时
+ EventBus.on('module:loaded', (data) => {
+ if (data && data.module) {
+ this.recordView(data.module);
+ }
+ });
+
+ // 监听模块卸载,结束计时
+ EventBus.on('module:unloaded', (data) => {
+ if (data && data.module) {
+ if (window.ChannelPreferences) {
+ ChannelPreferences.stopTiming(data.module);
+ }
+ }
+ });
+
+ // 监听页面关闭,停止所有计时
+ window.addEventListener('beforeunload', () => {
+ if (window.ChannelPreferences && window.ChannelPreferences.timing) {
+ Object.keys(ChannelPreferences.timing).forEach(moduleId => {
+ ChannelPreferences.stopTiming(moduleId);
+ });
+ }
+ });
+ },
+
+ // 记录模块访问
+ recordView: function(moduleId) {
+ if (!window.ChannelPreferences) return;
+
+ // 记录使用次数和开始计时
+ ChannelPreferences.recordUsage(moduleId);
+
+ // 发送事件
+ if (window.EventBus) {
+ EventBus.emit('stats:recorded', {
+ module: moduleId,
+ time: Date.now()
+ });
+ }
+ },
+
+ // 获取统计报告
+ getStatsReport: function() {
+ if (!window.ChannelPreferences) return {};
+
+ const stats = ChannelPreferences.getStats();
+ const modules = window.ModuleRegistry ? ModuleRegistry.list() : [];
+
+ // 格式化统计数据
+ const report = {
+ totalViews: 0,
+ mostUsed: null,
+ lastUsed: null,
+ moduleDetails: {}
+ };
+
+ let maxCount = 0;
+ let lastTime = 0;
+
+ modules.forEach(moduleId => {
+ const moduleStats = stats[moduleId] || { count: 0, lastUsed: null, totalTime: 0 };
+ report.moduleDetails[moduleId] = {
+ count: moduleStats.count,
+ lastUsed: moduleStats.lastUsed ? new Date(moduleStats.lastUsed).toLocaleString() : '从未使用',
+ totalTime: moduleStats.totalTime ? this.formatTime(moduleStats.totalTime) : '0秒'
+ };
+
+ report.totalViews += moduleStats.count;
+
+ if (moduleStats.count > maxCount) {
+ maxCount = moduleStats.count;
+ report.mostUsed = moduleId;
+ }
+
+ if (moduleStats.lastUsed && moduleStats.lastUsed > lastTime) {
+ lastTime = moduleStats.lastUsed;
+ report.lastUsed = moduleId;
+ }
+ });
+
+ return report;
+ },
+
+ // 格式化时间(秒 -> 可读格式)
+ formatTime: function(seconds) {
+ if (seconds < 60) return `${Math.round(seconds)}秒`;
+ if (seconds < 3600) {
+ const minutes = Math.floor(seconds / 60);
+ const secs = Math.round(seconds % 60);
+ return `${minutes}分${secs}秒`;
+ }
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+ return `${hours}小时${minutes}分`;
+ },
+
+ // 渲染统计面板
+ renderStatsPanel: function(container) {
+ if (!container) return;
+
+ const report = this.getStatsReport();
+ const modules = window.ModuleRegistry ? ModuleRegistry.list() : [];
+
+ let html = `
+
+
📊 使用统计
+
+
+ 总访问次数:
+ ${report.totalViews}
+
+ ${report.mostUsed ? `
+
+ 最常用模块:
+ ${this.getModuleName(report.mostUsed)} (${report.moduleDetails[report.mostUsed]?.count || 0}次)
+
+ ` : ''}
+ ${report.lastUsed ? `
+
+ 最近使用:
+ ${this.getModuleName(report.lastUsed)}
+
+ ` : ''}
+
+
+
模块详情
+
+
+
+ | 模块 |
+ 访问次数 |
+ 累计使用时长 |
+ 最后使用 |
+
+
+
+ `;
+
+ modules.forEach(moduleId => {
+ const detail = report.moduleDetails[moduleId] || { count: 0, totalTime: '0秒', lastUsed: '从未使用' };
+ html += `
+
+ | ${this.getModuleName(moduleId)} |
+ ${detail.count} |
+ ${detail.totalTime} |
+ ${detail.lastUsed} |
+
+ `;
+ });
+
+ html += `
+
+
+
+
+
+
+ `;
+
+ container.innerHTML = html;
+
+ // 绑定重置按钮
+ const resetBtn = document.getElementById('reset-stats-btn');
+ if (resetBtn) {
+ resetBtn.addEventListener('click', () => {
+ if (confirm('确定要重置所有统计数据吗?')) {
+ this.resetStats();
+ }
+ });
+ }
+ },
+
+ // 获取模块显示名称
+ getModuleName: function(moduleId) {
+ const names = {
+ 'm06': '工单管理',
+ 'm08': '数据统计',
+ 'm11': '组件库',
+ 'home': '首页',
+ 'debug': '调试面板'
+ };
+ return names[moduleId] || moduleId;
+ },
+
+ // 重置统计
+ resetStats: function() {
+ if (!window.ChannelPreferences) return;
+
+ const config = ChannelPreferences.config;
+ if (config) {
+ config.stats = {};
+ ChannelPreferences.save();
+
+ if (window.EventBus) {
+ EventBus.emit('stats:reset');
+ }
+
+ alert('统计数据已重置');
+
+ // 重新渲染统计面板
+ const container = document.querySelector('.stats-panel-container');
+ if (container) {
+ this.renderStatsPanel(container);
+ }
+ }
+ },
+
+ // 注入样式
+ injectStyles: function() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .stats-panel {
+ padding: 20px;
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ }
+ .stats-summary {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 16px;
+ margin: 20px 0;
+ padding: 16px;
+ background: #f8fafc;
+ border-radius: 8px;
+ }
+ .stat-item {
+ font-size: 14px;
+ }
+ .stat-label {
+ color: #64748b;
+ }
+ .stat-value {
+ font-weight: 600;
+ color: #0f172a;
+ margin-left: 8px;
+ }
+ .stats-table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 20px 0;
+ }
+ .stats-table th,
+ .stats-table td {
+ padding: 12px;
+ text-align: left;
+ border-bottom: 1px solid #e2e8f0;
+ }
+ .stats-table th {
+ background: #f1f5f9;
+ font-weight: 600;
+ color: #334155;
+ }
+ .stats-table tr:hover {
+ background: #f8fafc;
+ }
+ .stats-actions {
+ text-align: right;
+ margin-top: 20px;
+ }
+ .stats-reset-btn {
+ padding: 8px 16px;
+ background: #ef4444;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ }
+ .stats-reset-btn:hover {
+ background: #dc2626;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+};
+
+// 自动注入样式
+setTimeout(() => {
+ if (window.ChannelStats) {
+ ChannelStats.injectStyles();
+ }
+}, 100);
+
+console.log('[stats] 已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-style.css b/agents/zhuyuan-dev-agent/modules/m-channel/channel-style.css
new file mode 100644
index 0000000..b03191e
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-style.css
@@ -0,0 +1,62 @@
+/* 频道基础样式 */
+.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);
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-theme.js b/agents/zhuyuan-dev-agent/modules/m-channel/channel-theme.js
new file mode 100644
index 0000000..24ee895
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-theme.js
@@ -0,0 +1,157 @@
+// 频道主题管理 - 换主题色
+window.ChannelTheme = {
+ // 预设主题
+ themes: {
+ default: {
+ name: '默认蓝',
+ colors: {
+ primary: '#3b82f6',
+ primaryDark: '#2563eb',
+ secondary: '#10b981',
+ background: '#ffffff',
+ surface: '#f9fafb',
+ text: '#1f2937',
+ textLight: '#6b7280',
+ border: '#e5e7eb'
+ }
+ },
+ ocean: {
+ name: '海洋',
+ colors: {
+ primary: '#0891b2',
+ primaryDark: '#0e7490',
+ secondary: '#2dd4bf',
+ background: '#ecfeff',
+ surface: '#ffffff',
+ text: '#164e63',
+ textLight: '#155e75',
+ border: '#a5f3fc'
+ }
+ },
+ forest: {
+ name: '森林',
+ colors: {
+ primary: '#059669',
+ primaryDark: '#047857',
+ secondary: '#fbbf24',
+ background: '#f0fdf4',
+ surface: '#ffffff',
+ text: '#064e3b',
+ textLight: '#065f46',
+ border: '#a7f3d0'
+ }
+ },
+ sunset: {
+ name: '日落',
+ colors: {
+ primary: '#d97706',
+ primaryDark: '#b45309',
+ secondary: '#f43f5e',
+ background: '#fff7ed',
+ surface: '#ffffff',
+ text: '#7c2d12',
+ textLight: '#9a3412',
+ border: '#fed7aa'
+ }
+ },
+ lavender: {
+ name: '薰衣草',
+ colors: {
+ primary: '#8b5cf6',
+ primaryDark: '#7c3aed',
+ secondary: '#ec4899',
+ background: '#f5f3ff',
+ surface: '#ffffff',
+ text: '#4c1d95',
+ textLight: '#5b21b6',
+ border: '#ddd6fe'
+ }
+ }
+ },
+
+ // 当前主题ID
+ currentTheme: 'default',
+
+ // 初始化主题
+ init: function() {
+ console.log('[theme] 初始化');
+
+ // 从偏好设置加载主题
+ if (window.ChannelPreferences) {
+ const savedTheme = ChannelPreferences.getTheme();
+ if (savedTheme && this.themes[savedTheme]) {
+ this.currentTheme = savedTheme;
+ }
+ }
+
+ // 应用主题
+ this.applyTheme(this.currentTheme);
+
+ // 监听主题变化事件
+ if (window.EventBus) {
+ EventBus.on('preferences:changed', (data) => {
+ if (data.key === 'theme') {
+ this.applyTheme(data.value);
+ }
+ });
+ }
+ },
+
+ // 应用主题
+ applyTheme: function(themeId) {
+ if (!this.themes[themeId]) {
+ console.error(`[theme] 未知主题: ${themeId}`);
+ return;
+ }
+
+ this.currentTheme = themeId;
+ const colors = this.themes[themeId].colors;
+
+ // 设置 CSS 自定义属性
+ const root = document.documentElement;
+ for (const [key, value] of Object.entries(colors)) {
+ root.style.setProperty(`--theme-${key}`, value);
+ }
+
+ console.log(`[theme] 应用主题: ${themeId}`);
+
+ // 触发事件
+ if (window.EventBus) {
+ EventBus.emit('theme:changed', { theme: themeId, colors });
+ }
+ },
+
+ // 切换主题
+ setTheme: function(themeId) {
+ if (!this.themes[themeId]) return;
+
+ this.applyTheme(themeId);
+
+ // 保存到偏好设置
+ if (window.ChannelPreferences) {
+ ChannelPreferences.setTheme(themeId);
+ }
+ },
+
+ // 获取所有主题列表
+ getThemes: function() {
+ return Object.entries(this.themes).map(([id, theme]) => ({
+ id,
+ name: theme.name
+ }));
+ },
+
+ // 获取当前主题名称
+ getCurrentThemeName: function() {
+ return this.themes[this.currentTheme]?.name || '默认蓝';
+ }
+};
+
+// 自动初始化
+setTimeout(() => {
+ if (window.ChannelTheme) {
+ ChannelTheme.init();
+ }
+}, 200);
+
+console.log('[theme] 已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-transition.css b/agents/zhuyuan-dev-agent/modules/m-channel/channel-transition.css
new file mode 100644
index 0000000..25628e0
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-transition.css
@@ -0,0 +1,53 @@
+/* 过渡动画 */
+.channel-content {
+ transition: opacity 0.3s ease-in-out;
+}
+
+.channel-content.fade-out {
+ opacity: 0;
+}
+
+.channel-content.fade-in {
+ opacity: 1;
+}
+
+/* 滑动动画 */
+.slide-left-enter {
+ transform: translateX(100%);
+}
+
+.slide-left-enter-active {
+ transform: translateX(0);
+ transition: transform 0.3s ease-in-out;
+}
+
+.slide-right-enter {
+ transform: translateX(-100%);
+}
+
+.slide-right-enter-active {
+ transform: translateX(0);
+ transition: transform 0.3s ease-in-out;
+}
+
+.slide-exit {
+ position: absolute;
+ width: 100%;
+}
+
+.slide-exit-active {
+ transition: transform 0.3s ease-in-out;
+}
+
+.slide-exit-left {
+ transform: translateX(-100%);
+}
+
+.slide-exit-right {
+ transform: translateX(100%);
+}
+
+/* 临时测试动画 */
+.fade-out, .fade-in {
+ transition: opacity 1s ease !important;
+}
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/channel-ultimate.html b/agents/zhuyuan-dev-agent/modules/m-channel/channel-ultimate.html
new file mode 100644
index 0000000..346968f
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/channel-ultimate.html
@@ -0,0 +1,1401 @@
+
+
+
+
+
+ 光湖频道 · 终极版
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
主题色
+
+
+
+
+
+
+
+
+
+
统计数据
+
+
+
+
其他
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/error-boundary.js b/agents/zhuyuan-dev-agent/modules/m-channel/error-boundary.js
new file mode 100644
index 0000000..9ea7037
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/error-boundary.js
@@ -0,0 +1,134 @@
+// 错误边界 - 保险丝
+window.ErrorBoundary = {
+ // 包裹一个可能出错的渲染函数
+ wrap: function(renderFn, fallbackComponent, moduleId) {
+ return function(container) {
+ try {
+ return renderFn(container);
+ } catch (error) {
+ console.error(`[error-boundary] 模块 ${moduleId} 渲染失败:`, error);
+ return this.showFallback(container, moduleId, error);
+ }
+ };
+ },
+
+ // 显示降级界面
+ showFallback: function(container, moduleId, error) {
+ if (!container) return;
+
+ container.innerHTML = `
+
+
⚠️
+
+
模块 ${moduleId} 加载失败
+
${error.message || '未知错误'}
+
+
+
+
+
+
+ `;
+
+ // 记录错误到事件总线
+ if (window.EventBus) {
+ EventBus.emit('module:error', {
+ module: moduleId,
+ error: error.message,
+ time: Date.now()
+ });
+ }
+ },
+
+ // 重新加载模块(通过适配器)
+ reloadModule: function(moduleId) {
+ console.log(`[error-boundary] 尝试重载模块: ${moduleId}`);
+
+ // 触发全局重载事件
+ if (window.EventBus) {
+ EventBus.emit('module:reload', { module: moduleId });
+ }
+
+ // 如果存在适配器,调用适配器的重载方法
+ if (window.ModuleAdapter && window.ModuleAdapter.reloadModule) {
+ const container = document.querySelector(`[data-module="${moduleId}"]`) ||
+ document.getElementById(`module-${moduleId}`);
+ if (container) {
+ ModuleAdapter.reloadModule(moduleId, container.id);
+ }
+ } else {
+ // 否则直接刷新页面(简易方案)
+ location.reload();
+ }
+ },
+
+ // 隐藏错误(仅当用户点击“忽略”时)
+ hideError: function(btn) {
+ const errorDiv = btn.closest('.error-boundary');
+ if (errorDiv) {
+ errorDiv.style.display = 'none';
+ }
+ },
+
+ // 添加一些基础样式(会在 channel-style.css 中补充)
+ injectStyles: function() {
+ const style = document.createElement('style');
+ style.textContent = `
+ .error-boundary {
+ padding: 30px;
+ text-align: center;
+ background: #fff3f3;
+ border: 1px solid #ffcdd2;
+ border-radius: 8px;
+ margin: 20px 0;
+ }
+ .error-icon {
+ font-size: 48px;
+ margin-bottom: 15px;
+ }
+ .error-message h4 {
+ color: #d32f2f;
+ margin: 0 0 10px;
+ }
+ .error-message p {
+ color: #666;
+ margin: 0 0 20px;
+ }
+ .error-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ }
+ .error-retry {
+ padding: 8px 16px;
+ background: #d32f2f;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ .error-dismiss {
+ padding: 8px 16px;
+ background: #9e9e9e;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ `;
+ document.head.appendChild(style);
+ }
+};
+
+// 自动注入样式
+setTimeout(() => {
+ if (window.ErrorBoundary) {
+ ErrorBoundary.injectStyles();
+ }
+}, 100);
+
+console.log('[error-boundary] 已加载');
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/event-bus.js b/agents/zhuyuan-dev-agent/modules/m-channel/event-bus.js
new file mode 100644
index 0000000..a4b09f2
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/event-bus.js
@@ -0,0 +1,37 @@
+// 事件总线(群聊频道)
+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}`);
+ }
+ });
+ }
+};
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/index.html b/agents/zhuyuan-dev-agent/modules/m-channel/index.html
new file mode 100644
index 0000000..dd5afd8
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/index.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+ 光湖频道 · 动态渲染引擎
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/mock-modules/mock-a.html b/agents/zhuyuan-dev-agent/modules/m-channel/mock-modules/mock-a.html
new file mode 100644
index 0000000..2c43abc
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/mock-modules/mock-a.html
@@ -0,0 +1,16 @@
+
+
+
+ 模拟模块A
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/mock-modules/mock-b.html b/agents/zhuyuan-dev-agent/modules/m-channel/mock-modules/mock-b.html
new file mode 100644
index 0000000..5236978
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/mock-modules/mock-b.html
@@ -0,0 +1,17 @@
+
+
+
+ 模拟模块B
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/module-lifecycle.js b/agents/zhuyuan-dev-agent/modules/m-channel/module-lifecycle.js
new file mode 100644
index 0000000..d99ea03
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/module-lifecycle.js
@@ -0,0 +1,23 @@
+// 模块生命周期管理
+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);
+ }
+};
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/module-loader.js b/agents/zhuyuan-dev-agent/modules/m-channel/module-loader.js
new file mode 100644
index 0000000..e812f52
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/module-loader.js
@@ -0,0 +1,36 @@
+// 模块加载器
+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;
+ }
+
+ // 如果模块有 init 方法
+ if (module.init && typeof module.init === 'function') {
+ module.init(container);
+ } else {
+ container.innerHTML = `模块 ${moduleName} 内容
`;
+ }
+
+ 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);
+ }
+};
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/module-registry.js b/agents/zhuyuan-dev-agent/modules/m-channel/module-registry.js
new file mode 100644
index 0000000..c3fb0ae
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/module-registry.js
@@ -0,0 +1,17 @@
+// 模块注册表
+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);
+ }
+};
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-dashboard.html b/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-dashboard.html
new file mode 100644
index 0000000..e67784d
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-dashboard.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
⚡ 模块加载性能
+
+
+
+ | 模块 |
+ 访问次数 |
+ 总停留(分钟) |
+ 平均加载(ms) |
+ 状态 |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-debug.html b/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-debug.html
new file mode 100644
index 0000000..91f1f88
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-debug.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
🐞 调试面板
+
+
+ 当前活跃模块:
+ 加载中...
+
+
+
事件总线聊天记录
+
+
+
+
+
+
+
+
+
+
+
+
+
消息会广播给所有订阅的模块
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-settings.html b/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-settings.html
new file mode 100644
index 0000000..a7f66b0
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/m-channel/views/channel-settings.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+
🎨 外观设置
+
+
+ 主题模式
+ 选择你喜欢的界面风格
+
+
+
+
+
+
+ 字体大小
+ 调整界面文字大小
+
+
+
+
+
+
+ 过渡动画
+ 开启页面切换动画效果
+
+
+
+
+
+
+ 侧边栏默认收起
+ 页面加载时侧边栏是否收起
+
+
+
+
+
+
+
+
🔔 通知设置
+
+
+ 模块更新通知
+ 有模块内容更新时提醒
+
+
+
+
+
+ 系统通知
+ 系统公告和维护提醒
+
+
+
+
+
+ 通知声音
+ 收到通知时播放提示音
+
+
+
+
+
+
+
+
📊 数据设置
+
+
+ 数据采集
+ 允许采集模块访问和性能数据(用于数据面板)
+
+
+
+
+
+ 自动刷新
+ 定时自动刷新频道数据
+
+
+
+
+
+ 刷新间隔
+ 自动刷新的时间间隔
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📥 导入设置
+
粘贴之前导出的JSON内容:
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/model-caller.js b/agents/zhuyuan-dev-agent/modules/model-caller.js
new file mode 100644
index 0000000..3302bcd
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/model-caller.js
@@ -0,0 +1,70 @@
+// ═══════════════════════════════════════
+// 铸渊开发Agent · 大模型调用模块
+// 支持 OpenAI 兼容 API → DeepSeek/千问/等
+// ═══════════════════════════════════════
+
+const https = require("https");
+
+function getConfig() {
+ const key = process.env.MODEL_API_KEY;
+ const base = process.env.MODEL_API_BASE || "https://api.deepseek.com/v1";
+ const model = process.env.MODEL_NAME || "deepseek-chat";
+
+ if (!key) {
+ console.warn("[ModelCaller] MODEL_API_KEY 未配置");
+ return null;
+ }
+ return { key, base, model };
+}
+
+/**
+ * 调用大模型
+ * @param {string} prompt - 输入提示
+ * @param {object} opts - { max_tokens, temperature }
+ * @returns {Promise} 模型回复文本
+ */
+async function call(prompt, opts = {}) {
+ const config = getConfig();
+ if (!config) throw new Error("MODEL_API_KEY 未配置");
+
+ const body = JSON.stringify({
+ model: config.model,
+ messages: [
+ { role: "system", content: "你是一个技术开发Agent。回答要精确、结构化、可执行。" },
+ { role: "user", content: prompt }
+ ],
+ max_tokens: opts.max_tokens || 4096,
+ temperature: opts.temperature || 0.3
+ });
+
+ return new Promise((resolve, reject) => {
+ const url = new URL(config.base + "/chat/completions");
+ const req = https.request({
+ hostname: url.hostname,
+ path: url.pathname,
+ method: "POST",
+ headers: {
+ "Authorization": "Bearer " + config.key,
+ "Content-Type": "application/json"
+ }
+ }, (res) => {
+ let data = "";
+ res.on("data", (c) => data += c);
+ res.on("end", () => {
+ try {
+ const json = JSON.parse(data);
+ if (json.error) return reject(new Error(json.error.message || "API错误"));
+ resolve(json.choices?.[0]?.message?.content || "");
+ } catch (e) {
+ reject(new Error("解析失败: " + data.slice(0, 200)));
+ }
+ });
+ });
+
+ req.on("error", reject);
+ req.write(body);
+ req.end();
+ });
+}
+
+module.exports = { call };
diff --git a/agents/zhuyuan-dev-agent/modules/notion-bridge.js b/agents/zhuyuan-dev-agent/modules/notion-bridge.js
new file mode 100644
index 0000000..7dbf128
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/notion-bridge.js
@@ -0,0 +1,118 @@
+// ═══════════════════════════════════════
+// 铸渊开发Agent · Notion 桥接模块
+// 读取规划数据库 · 写入执行进度
+// ═══════════════════════════════════════
+
+const https = require("https");
+
+const NOTION_API = "https://api.notion.com/v1";
+const NOTION_VERSION = "2022-06-28";
+
+function getHeaders() {
+ const token = process.env.NOTION_TOKEN;
+ if (!token) {
+ console.warn("[NotionBridge] NOTION_TOKEN 未配置");
+ return null;
+ }
+ return {
+ "Authorization": "Bearer " + token,
+ "Content-Type": "application/json",
+ "Notion-Version": NOTION_VERSION
+ };
+}
+
+function notionRequest(endpoint, method, body) {
+ return new Promise((resolve, reject) => {
+ const headers = getHeaders();
+ if (!headers) return reject(new Error("NOTION_TOKEN 未配置"));
+
+ const url = new URL(NOTION_API + endpoint);
+ const options = {
+ hostname: url.hostname,
+ path: url.pathname + url.search,
+ method: method,
+ headers: headers
+ };
+
+ const req = https.request(options, (res) => {
+ let data = "";
+ res.on("data", (chunk) => data += chunk);
+ res.on("end", () => {
+ try {
+ const json = JSON.parse(data);
+ if (res.statusCode >= 400) {
+ reject(new Error("Notion API " + res.statusCode + ": " + (json.message || data)));
+ } else {
+ resolve(json);
+ }
+ } catch (e) {
+ reject(new Error("解析失败: " + data.slice(0, 200)));
+ }
+ });
+ });
+
+ req.on("error", reject);
+ if (body) req.write(JSON.stringify(body));
+ req.end();
+ });
+}
+
+/**
+ * 从 Notion 数据库读取规划
+ * @param {string} databaseId - Notion 数据库 ID
+ * @returns {Promise} 规划条目列表
+ */
+async function fetchPlans(databaseId) {
+ const result = await notionRequest("/databases/" + databaseId + "/query", "POST", {
+ sorts: [{ property: "Created", direction: "descending" }],
+ page_size: 10
+ });
+
+ return (result.results || []).map((page) => {
+ const props = page.properties || {};
+ return {
+ notion_page_id: page.id,
+ title: props.Name?.title?.[0]?.plain_text || "无标题",
+ status: props.Status?.status?.name || "unknown",
+ plan_id: props.PlanID?.rich_text?.[0]?.plain_text || "",
+ created: page.created_time
+ };
+ });
+}
+
+/**
+ * 更新 Notion 中某条规划的状态
+ * @param {string} pageId - Notion 页面 ID
+ * @param {string} status - 新状态
+ * @param {object} details - 额外详情
+ */
+async function updatePlanStatus(pageId, status, details = {}) {
+ const properties = {
+ Status: { status: { name: status } }
+ };
+
+ if (details.log_url) {
+ properties.LogURL = { url: details.log_url };
+ }
+
+ return notionRequest("/pages/" + pageId, "PATCH", { properties });
+}
+
+/**
+ * 向 Notion 数据库追加执行日志
+ * @param {string} databaseId - Notion 数据库 ID
+ * @param {object} entry - 日志条目 { plan_id, task_id, action, result, time }
+ */
+async function appendLog(databaseId, entry) {
+ return notionRequest("/pages", "POST", {
+ parent: { database_id: databaseId },
+ properties: {
+ Name: { title: [{ text: { content: entry.plan_id + " · " + entry.task_id } }] },
+ Status: { status: { name: entry.result === "ok" ? "Done" : "Failed" } },
+ LogTime: { date: { start: entry.time || new Date().toISOString() } },
+ Detail: { rich_text: [{ text: { content: entry.detail || entry.action } }] }
+ }
+ });
+}
+
+module.exports = { fetchPlans, updatePlanStatus, appendLog };
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/background-gen.js b/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/background-gen.js
new file mode 100644
index 0000000..e03e9f9
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/background-gen.js
@@ -0,0 +1,238 @@
+/**
+ * M-PALACE · 背景生成引擎
+ * 接收锚点答案 + 人格侧面初始数据
+ * → 从宫廷数据库匹配
+ * → 生成逻辑自洽的专属背景
+ *
+ * 生成流程:
+ * 锚点1(世界观)→ 选择 templates/ 基础模板
+ * 锚点2(身份) → 确定主角定位 + 初始人格快照
+ * → 查询 palace-db 四大维度 → 调用模型 → 输出世界状态
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const DATA_DIR = path.join(__dirname, '..', '..', 'data');
+const TEMPLATES_DIR = path.join(DATA_DIR, 'templates');
+const PALACE_DB_PATH = path.join(DATA_DIR, 'palace-db.json');
+
+let _palaceDB = null;
+
+function loadPalaceDB() {
+ if (_palaceDB) return _palaceDB;
+ _palaceDB = JSON.parse(fs.readFileSync(PALACE_DB_PATH, 'utf-8'));
+ return _palaceDB;
+}
+
+function loadTemplate(worldview) {
+ const map = {
+ 'ancient-china': 'ancient-china.json',
+ '古代中国': 'ancient-china.json',
+ 'fantasy': 'fantasy-dynasty.json',
+ '架空王朝': 'fantasy-dynasty.json'
+ };
+ const file = map[worldview] || 'ancient-china.json';
+ return JSON.parse(fs.readFileSync(path.join(TEMPLATES_DIR, file), 'utf-8'));
+}
+
+/**
+ * 从模板中随机选取一项
+ */
+function pick(arr) {
+ return arr[Math.floor(Math.random() * arr.length)];
+}
+
+/**
+ * 基于人格侧面决定 NPC 映射
+ * 核心机密:每个 NPC 对应玩家 1~2 个人格侧面的外化
+ */
+function generateNPCMappings(scores) {
+ const npcs = [];
+ const dims = Object.entries(scores).sort((a, b) => b[1] - a[1]);
+
+ // 最强维度 → 镜像对手
+ const strongest = dims[0];
+ npcs.push({
+ archetype: 'mirror',
+ mapped_dims: [strongest[0]],
+ role_hint: 'rival',
+ description: '与你最相似的对手'
+ });
+
+ // 最弱维度 → 放大威胁
+ const weakest = dims[dims.length - 1];
+ npcs.push({
+ archetype: 'shadow',
+ mapped_dims: [weakest[0]],
+ role_hint: 'threat',
+ description: '让你恐惧的存在'
+ });
+
+ // 温情相关 → 情感锚点
+ if (scores.warmth >= 40) {
+ npcs.push({
+ archetype: 'anchor',
+ mapped_dims: ['warmth', 'loyalty'],
+ role_hint: 'ally',
+ description: '需要被保护的弱势角色'
+ });
+ }
+
+ // 多疑相关 → 暧昧盟友
+ if (scores.suspicion >= 45) {
+ npcs.push({
+ archetype: 'trickster',
+ mapped_dims: ['suspicion', 'cunning'],
+ role_hint: 'ambiguous',
+ description: '行为暧昧的盟友'
+ });
+ }
+
+ // 确保至少 3 个 NPC
+ if (npcs.length < 3) {
+ npcs.push({
+ archetype: 'catalyst',
+ mapped_dims: [dims[2][0]],
+ role_hint: 'neutral',
+ description: '推动局势变化的中立角色'
+ });
+ }
+
+ return npcs;
+}
+
+/**
+ * 基于人格分数查询 palace-db,确定初始格局
+ */
+function queryInitialSetup(scores) {
+ const db = loadPalaceDB();
+
+ // 权力维度:基于 ambition+cunning
+ const powerLevel = (scores.ambition + scores.cunning) / 2;
+ const factionCount = powerLevel > 60 ? 3 : 2;
+
+ // 后宫地位:基于 vanity+warmth
+ const statusLevel = (scores.vanity + scores.warmth) / 2;
+
+ // 情感关系:基于 warmth+loyalty
+ const emotionIntensity = (scores.warmth + scores.loyalty) / 2;
+
+ // 矛盾冲突:基于 aggression+suspicion
+ const conflictLevel = (scores.aggression + scores.suspicion) / 2;
+ const conflictType = conflictLevel > 60 ? '争宠' : '自保';
+
+ return {
+ factions: db.power.factions.slice(0, factionCount),
+ initialEvent: pick(db.power.events),
+ startingRank: db.status.ranks[Math.min(Math.floor(statusLevel / 15), db.status.ranks.length - 1)],
+ emotionType: emotionIntensity > 60 ? pick(db.emotion.types.slice(0, 3)) : pick(db.emotion.types.slice(3)),
+ emotionTrigger: pick(db.emotion.triggers),
+ conflictType: conflictType,
+ escalation: db.conflict.escalation[0]
+ };
+}
+
+/**
+ * 构建世界生成 prompt(供模型调用)
+ */
+function buildWorldPrompt(template, role, setup, npcMappings) {
+ const dynasty = template.setting.dynasty;
+ const era = pick(template.setting.era_prefix);
+ const roleConfig = template.roles[role] || template.roles['妃子'];
+ const title = pick(roleConfig.title_options);
+
+ const npcDescriptions = npcMappings.map((npc, i) => {
+ return `NPC${i + 1}(${npc.role_hint}型):${npc.description}`;
+ }).join('\n');
+
+ return {
+ system: [
+ '你是一个古风宫斗叙事生成器。',
+ '要求:文笔古风典雅,有呼吸感,不要白话流水账。',
+ '使用通感手法,注重氛围营造。',
+ '所有NPC的存在都有隐藏的人格映射逻辑,但不能向玩家揭示。'
+ ].join('\n'),
+ user: [
+ `请为以下设定生成宫廷世界开篇:`,
+ `王朝:${dynasty}`,
+ `年号:${era}`,
+ `玩家身份:${role}(${title})`,
+ `初始阵营格局:${setup.factions.join('、')}`,
+ `开局事件:${setup.initialEvent}`,
+ `情感基调:${setup.emotionType}`,
+ `冲突类型:${setup.conflictType}`,
+ ``,
+ `需要生成的NPC:`,
+ npcDescriptions,
+ ``,
+ `输出格式(JSON):`,
+ `{`,
+ ` "dynasty_name": "王朝名",`,
+ ` "era_name": "年号",`,
+ ` "background": "一段时代背景描述(古风文笔·100-200字)",`,
+ ` "player_intro": "主角身世与处境(古风文笔·100-200字)",`,
+ ` "npcs": [{ "name": "角色名", "title": "头衔", "personality": "性格", "relation_to_player": "与主角关系", "hidden_archetype": "mirror|shadow|anchor|trickster|catalyst" }],`,
+ ` "opening_event": "开局事件描述(古风文笔·150-300字)",`,
+ ` "opening_choices": ["选项A", "选项B", "选项C"]`,
+ `}`
+ ].join('\n'),
+ meta: { dynasty, era, title, role }
+ };
+}
+
+/**
+ * 主入口:生成完整背景(不依赖外部模型时,使用本地模板拼接)
+ */
+function generate(worldview, role, personaScores) {
+ const template = loadTemplate(worldview);
+ const setup = queryInitialSetup(personaScores);
+ const npcMappings = generateNPCMappings(personaScores);
+ const prompt = buildWorldPrompt(template, role, setup, npcMappings);
+
+ // 构造本地 fallback 世界(不依赖外部模型时使用)
+ const dynasty = prompt.meta.dynasty;
+ const era = prompt.meta.era;
+ const roleConfig = template.roles[role] || template.roles['妃子'];
+ const title = prompt.meta.title;
+
+ const localWorld = {
+ dynasty_name: dynasty,
+ era_name: era,
+ background: `${dynasty}${era}年间,朝堂暗流涌动。${setup.factions.join('与')}明争暗斗,帝位之下,人心叵测。宫墙之内,灯火通明处未必安宁,暗影幢幢处未必无情。`,
+ player_intro: `你是${dynasty}${title},${pick(roleConfig.family_options || ['名门'])}出身。入宫数载,${setup.conflictType === '争宠' ? '虽有圣眷,却树敌颇多' : '小心翼翼,只求自保平安'}。${setup.emotionType}的暗线,早已悄然铺开。`,
+ npcs: npcMappings.map(function (npc, i) {
+ const names = ['沈婉仪', '赵昭仪', '陈太傅', '李将军', '刘公公'];
+ return {
+ name: names[i] || '无名氏',
+ title: pick(template.setting.era_prefix) + '年间人物',
+ personality: npc.description,
+ relation_to_player: npc.role_hint,
+ hidden_archetype: npc.archetype
+ };
+ }),
+ opening_event: `夜半三更,${template.setting.inner_palace}传来急召。太后身边的掌事姑姑面色如常,语气却带着不容拒绝的冷意。你知道,这一趟,去与不去,都是棋局的一部分。`,
+ opening_choices: [
+ '立刻更衣前往,恭顺以对',
+ '称身体不适,推迟半个时辰',
+ '先派人去打听太后的意图'
+ ]
+ };
+
+ return {
+ world: localWorld,
+ npcMappings: npcMappings,
+ setup: setup,
+ prompt: prompt,
+ template: template
+ };
+}
+
+module.exports = {
+ generate,
+ loadTemplate,
+ loadPalaceDB,
+ generateNPCMappings,
+ queryInitialSetup,
+ buildWorldPrompt
+};
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/persona-analyzer.js b/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/persona-analyzer.js
new file mode 100644
index 0000000..593070d
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/persona-analyzer.js
@@ -0,0 +1,176 @@
+/**
+ * M-PALACE · 人格分析引擎
+ * 实时分析玩家每一次输入,识别并量化人格侧面
+ *
+ * 分析流程:
+ * ① 关键词匹配:扫描 persona-dict 的 keywords
+ * ② 行为信号匹配:判断选项类型对应的 behavior_signals
+ * ③ 上下文推断:结合对话历史推断语气/态度/策略倾向
+ * → 输出 8 维人格侧面分数(0~100)
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const DICT_PATH = path.join(__dirname, '..', '..', 'data', 'persona-dict.json');
+
+let _dictCache = null;
+
+function loadDict() {
+ if (_dictCache) return _dictCache;
+ _dictCache = JSON.parse(fs.readFileSync(DICT_PATH, 'utf-8'));
+ return _dictCache;
+}
+
+/**
+ * 生成初始人格分数(基于身份锚点)
+ */
+function getInitialScores(role) {
+ const base = {
+ ambition: 50, warmth: 50, aggression: 50, suspicion: 50,
+ vanity: 50, cunning: 50, loyalty: 50, fear: 50
+ };
+ const adjustments = {
+ '皇帝': { ambition: 20, vanity: 15 },
+ '妃子': { warmth: 15, fear: 10 },
+ '重臣': { loyalty: 20, cunning: 10 },
+ '奸臣': { cunning: 25, aggression: 15 }
+ };
+ const adj = adjustments[role] || {};
+ for (const [k, v] of Object.entries(adj)) {
+ base[k] = Math.min(100, base[k] + v);
+ }
+ return base;
+}
+
+/**
+ * 关键词匹配:在输入文本中扫描 persona-dict keywords
+ * @returns {{ [dimensionId]: number }} 命中次数 map
+ */
+function matchKeywords(text) {
+ const dict = loadDict();
+ const hits = {};
+ for (const dim of dict.dimensions) {
+ hits[dim.id] = 0;
+ for (const kw of dim.keywords) {
+ if (text.includes(kw)) hits[dim.id]++;
+ }
+ }
+ return hits;
+}
+
+/**
+ * 行为信号匹配:将选项 index 映射到行为信号
+ * 选项设计规则:A=最强侧面(舒适区),B=最弱侧面(成长区),C=中性
+ * @param {number|null} choiceIndex 0/1/2 or null(自由输入)
+ * @param {object} optionMeta 后端为本次选项附加的元数据 { a_dims, b_dims, c_dims }
+ */
+function matchBehavior(choiceIndex, optionMeta) {
+ if (choiceIndex === null || !optionMeta) return {};
+ const key = ['a_dims', 'b_dims', 'c_dims'][choiceIndex];
+ const dims = (optionMeta && optionMeta[key]) || [];
+ const hits = {};
+ for (const d of dims) {
+ hits[d] = (hits[d] || 0) + 1;
+ }
+ return hits;
+}
+
+/**
+ * 合并关键词 + 行为信号 → 更新人格分数
+ */
+function updateScores(currentScores, keywordHits, behaviorHits) {
+ const updated = { ...currentScores };
+ const KEYWORD_WEIGHT = 3;
+ const BEHAVIOR_WEIGHT = 5;
+
+ for (const dim of Object.keys(updated)) {
+ let delta = 0;
+ if (keywordHits[dim]) delta += keywordHits[dim] * KEYWORD_WEIGHT;
+ if (behaviorHits[dim]) delta += behaviorHits[dim] * BEHAVIOR_WEIGHT;
+ updated[dim] = Math.max(0, Math.min(100, updated[dim] + delta));
+ }
+ return updated;
+}
+
+/**
+ * 找到当前最突出特征
+ */
+function getDominantTrait(scores) {
+ let maxDim = null;
+ let maxVal = -1;
+ for (const [k, v] of Object.entries(scores)) {
+ if (v > maxVal) { maxVal = v; maxDim = k; }
+ }
+ return maxDim;
+}
+
+/**
+ * 检测分数突变(单次 +15 以上)
+ */
+function detectSurge(oldScores, newScores) {
+ const surges = [];
+ for (const dim of Object.keys(newScores)) {
+ const delta = newScores[dim] - (oldScores[dim] || 50);
+ if (delta >= 15) surges.push({ dim, delta });
+ }
+ return surges;
+}
+
+/**
+ * 检测交叉升高(两个维度同时上升 10+)
+ */
+function detectCrossRise(oldScores, newScores) {
+ const rising = [];
+ for (const dim of Object.keys(newScores)) {
+ const delta = newScores[dim] - (oldScores[dim] || 50);
+ if (delta >= 10) rising.push(dim);
+ }
+ return rising.length >= 2 ? rising : [];
+}
+
+/**
+ * 主分析入口
+ * @param {string} text 玩家输入文本
+ * @param {number|null} choiceIndex 选项序号(null = 自由输入)
+ * @param {object} optionMeta 选项元数据
+ * @param {object} currentScores 当前人格分数
+ * @returns {{ scores, dominant, surges, crossRise }}
+ */
+function analyze(text, choiceIndex, optionMeta, currentScores) {
+ const kwHits = text ? matchKeywords(text) : {};
+ const bhHits = matchBehavior(choiceIndex, optionMeta);
+ const oldScores = { ...currentScores };
+ const newScores = updateScores(currentScores, kwHits, bhHits);
+ const dominant = getDominantTrait(newScores);
+ const surges = detectSurge(oldScores, newScores);
+ const crossRise = detectCrossRise(oldScores, newScores);
+
+ return { scores: newScores, dominant, surges, crossRise };
+}
+
+/**
+ * 将 8 维人格分数转换为前端四维状态栏数值
+ * 权力值 = avg(ambition, cunning)
+ * 地位值 = avg(vanity, loyalty)
+ * 情感值 = avg(warmth, fear反转)
+ * 冲突值 = avg(aggression, suspicion)
+ */
+function toFourDimensions(scores) {
+ return {
+ power: Math.round((scores.ambition + scores.cunning) / 2),
+ status: Math.round((scores.vanity + scores.loyalty) / 2),
+ emotion: Math.round((scores.warmth + (100 - scores.fear)) / 2),
+ conflict: Math.round((scores.aggression + scores.suspicion) / 2)
+ };
+}
+
+module.exports = {
+ getInitialScores,
+ analyze,
+ getDominantTrait,
+ toFourDimensions,
+ matchKeywords,
+ detectSurge,
+ detectCrossRise
+};
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/plot-engine.js b/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/plot-engine.js
new file mode 100644
index 0000000..d8cf2e7
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/plot-engine.js
@@ -0,0 +1,311 @@
+/**
+ * M-PALACE · 角色情节引擎
+ * 根据持续更新的人格侧面数据,在专属背景下动态生成宫斗剧情
+ *
+ * 生成逻辑:
+ * ① 人格分析引擎更新侧面分数
+ * ② 检测分数变化幅度 → 决定事件类型
+ * ③ 查询 palace-db 匹配剧情模板
+ * ④ 综合 state+persona+history 生成叙事+选项
+ * ⑤ 更新 state + history → 返回前端
+ */
+
+const fs = require('fs');
+const path = require('path');
+const personaAnalyzer = require('./persona-analyzer');
+
+const DATA_DIR = path.join(__dirname, '..', '..', 'data');
+const PALACE_DB_PATH = path.join(DATA_DIR, 'palace-db.json');
+
+let _palaceDB = null;
+
+function loadPalaceDB() {
+ if (_palaceDB) return _palaceDB;
+ _palaceDB = JSON.parse(fs.readFileSync(PALACE_DB_PATH, 'utf-8'));
+ return _palaceDB;
+}
+
+/**
+ * 确定事件类型
+ * 突变(+15) → 关键事件
+ * 交叉升高 → 复合剧情线
+ * 平稳 → 主线叙事
+ */
+function determineEventType(surges, crossRise) {
+ if (surges.length > 0) return 'critical';
+ if (crossRise.length >= 2) return 'compound';
+ return 'mainline';
+}
+
+/**
+ * 从 palace-db 匹配适合当前状态的剧情模板
+ */
+function matchPlotTemplate(eventType, scores, chapter) {
+ const db = loadPalaceDB();
+ const dominant = personaAnalyzer.getDominantTrait(scores);
+
+ if (eventType === 'critical') {
+ // 关键事件:根据突变维度匹配冲突类型
+ const conflictMap = {
+ ambition: '夺权', aggression: '报仇', suspicion: '揭秘',
+ warmth: '救人', fear: '自保', cunning: '争宠',
+ loyalty: '救人', vanity: '争宠'
+ };
+ return {
+ conflict: conflictMap[dominant] || '自保',
+ escalation: db.conflict.escalation[Math.min(chapter, db.conflict.escalation.length - 1)],
+ emotionTrigger: pick(db.emotion.triggers),
+ statusEvent: pick(db.status.events)
+ };
+ }
+
+ if (eventType === 'compound') {
+ return {
+ conflict: pick(db.conflict.types),
+ escalation: db.conflict.escalation[Math.min(Math.floor(chapter / 2), db.conflict.escalation.length - 1)],
+ emotionTrigger: pick(db.emotion.triggers),
+ statusEvent: null
+ };
+ }
+
+ // mainline
+ return {
+ conflict: null,
+ escalation: db.conflict.escalation[0],
+ emotionTrigger: null,
+ statusEvent: null
+ };
+}
+
+/**
+ * 生成选项:A=顺应最强侧面, B=挑战最弱侧面, C=中性探索
+ */
+function generateChoicesMeta(scores) {
+ const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
+ const strongest = sorted[0][0];
+ const weakest = sorted[sorted.length - 1][0];
+ const mid = sorted[Math.floor(sorted.length / 2)][0];
+
+ return {
+ a_dims: [strongest],
+ b_dims: [weakest],
+ c_dims: [mid],
+ strategy: {
+ a: 'comfort', // 舒适区
+ b: 'growth', // 成长区
+ c: 'explore' // 探索区
+ }
+ };
+}
+
+/**
+ * 构建情节生成 prompt(供模型调用)
+ */
+function buildPlotPrompt(state, persona, history, plotTemplate, choicesMeta) {
+ const chapterNum = (state.chapter || 0) + 1;
+ const paragraphNum = (state.paragraph || 0) + 1;
+ const recentChoices = (history.choices || []).slice(-3);
+
+ const sorted = Object.entries(persona.current_scores).sort((a, b) => b[1] - a[1]);
+ const strongest = sorted[0];
+ const weakest = sorted[sorted.length - 1];
+
+ return {
+ system: [
+ '你是一个古风宫斗叙事引擎。',
+ '文笔要求:古风典雅·有呼吸感·注重氛围·善用通感。',
+ '每段叙事100-250字,节奏不急不徐。',
+ '选项设计:A=顺应玩家当前最强侧面,B=挑战最弱侧面,C=中性/意外方向。',
+ '永远不要暗示或揭示人格映射机制。'
+ ].join('\n'),
+ user: [
+ `当前状态:第${chapterNum}章·第${paragraphNum}段`,
+ `世界:${state.world ? state.world.dynasty_name + '·' + state.world.era_name : '未知王朝'}`,
+ `玩家身份:${state.player ? state.player.role : '未知'}`,
+ ``,
+ `NPC状态:${JSON.stringify((state.npcs || []).map(function (n) { return n.name + '(' + n.relation_to_player + ')'; }))}`,
+ ``,
+ `剧情类型:${plotTemplate.conflict || '日常推进'}`,
+ `升级阶段:${plotTemplate.escalation}`,
+ plotTemplate.emotionTrigger ? `情感触发:${plotTemplate.emotionTrigger}` : '',
+ plotTemplate.statusEvent ? `地位事件:${plotTemplate.statusEvent}` : '',
+ ``,
+ `玩家最近选择:${recentChoices.map(function (c) { return c.text; }).join(' → ') || '无'}`,
+ `玩家最强倾向:${strongest[0]}(${strongest[1]})`,
+ `玩家最弱倾向:${weakest[0]}(${weakest[1]})`,
+ ``,
+ `请生成下一段叙事和选项(JSON):`,
+ `{`,
+ ` "chapter_title": "第X章·标题",`,
+ ` "narrative": "叙事文本(古风文笔·100-250字)",`,
+ ` "choices": ["选项A(顺应${strongest[0]})", "选项B(挑战${weakest[0]})", "选项C(中性探索)"],`,
+ ` "dimension_changes": { "power": 0, "status": 0, "emotion": 0, "conflict": 0 }`,
+ `}`
+ ].filter(Boolean).join('\n'),
+ meta: { chapterNum, paragraphNum, choicesMeta }
+ };
+}
+
+/**
+ * 本地生成叙事 fallback(不依赖外部模型时使用)
+ */
+function generateLocalNarrative(state, persona, plotTemplate, choicesMeta) {
+ const db = loadPalaceDB();
+ const chapterNum = (state.chapter || 0) + 1;
+ const paragraphNum = (state.paragraph || 0) + 1;
+ const sorted = Object.entries(persona.current_scores).sort((a, b) => b[1] - a[1]);
+ const strongest = sorted[0][0];
+ const weakest = sorted[sorted.length - 1][0];
+
+ const locations = ['长乐宫', '御花园', '太和殿', '冷宫回廊', '密道暗阁'];
+ const atmosphere = ['月色冷白', '烛火摇曳', '细雨如丝', '暮色四合', '晨曦微露'];
+ const loc = pick(locations);
+ const atm = pick(atmosphere);
+
+ const narrativeTemplates = {
+ critical: `${atm},${loc}内气氛凝重如铁。一封密信悄然送到你案前,字迹潦草,却句句惊心。你知道,有些事一旦知晓,便再无退路。窗外的风卷起帘角,像是催促,又像是警告。`,
+ compound: `${atm},你独坐${loc},心思百转。今日朝堂上的一幕反复在脑海中翻涌——那个眼神,那句话,似乎都别有深意。而你身边的人,究竟哪个是真心,哪个是棋子?`,
+ mainline: `${atm},${loc}一切如常。宫人们来去匆匆,各怀心事。你整理好衣冠,准备迎接新的一天。然而平静之下,暗流从未停歇。`
+ };
+
+ const dimLabels = {
+ ambition: '野心', warmth: '温情', aggression: '攻击', suspicion: '多疑',
+ vanity: '虚荣', cunning: '心机', loyalty: '忠义', fear: '恐惧'
+ };
+
+ const choiceTemplates = {
+ ambition: ['主动出击,争取主导权', '直面挑战,绝不退缩'],
+ warmth: ['以柔化刚,寻求和解', '默默守护,不求回报'],
+ aggression: ['果断反击,以牙还牙', '设下陷阱,一击致命'],
+ suspicion: ['暗中调查,不动声色', '保持距离,静观其变'],
+ vanity: ['展示实力,彰显地位', '争取荣耀,不甘人后'],
+ cunning: ['迂回布局,暗中谋划', '利用关系,借力打力'],
+ loyalty: ['坚守立场,绝不妥协', '为盟友挺身而出'],
+ fear: ['谨慎行事,避免冲突', '退一步,保全实力']
+ };
+
+ const choiceA = pick(choiceTemplates[strongest] || ['继续观望']);
+ const choiceB = pick(choiceTemplates[weakest] || ['另辟蹊径']);
+ const choiceC = '派人暗中打探消息,再做定夺';
+
+ const dims = personaAnalyzer.toFourDimensions(persona.current_scores);
+ const dimChanges = { power: 0, status: 0, emotion: 0, conflict: 0 };
+ if (plotTemplate.conflict) {
+ dimChanges.conflict = Math.floor(Math.random() * 5) + 1;
+ }
+ if (plotTemplate.statusEvent) {
+ dimChanges.status = Math.floor(Math.random() * 5) - 2;
+ }
+
+ return {
+ chapter_title: `第${chapterNum}章·${plotTemplate.conflict || '暗流'}`,
+ narrative: narrativeTemplates[determineEventType(
+ personaAnalyzer.detectSurge({}, persona.current_scores),
+ personaAnalyzer.detectCrossRise({}, persona.current_scores)
+ )] || narrativeTemplates.mainline,
+ choices: [choiceA, choiceB, choiceC],
+ dimension_changes: dimChanges,
+ meta: {
+ chapter: chapterNum,
+ paragraph: paragraphNum,
+ choices_meta: choicesMeta
+ }
+ };
+}
+
+/**
+ * 主入口:推进情节
+ * @param {object} state 当前世界状态
+ * @param {object} persona 玩家人格数据
+ * @param {object} history 剧情历史
+ * @param {string} playerInput 玩家输入
+ * @param {number|null} choiceIndex 选项序号
+ * @param {object} prevOptionMeta 上一轮选项元数据
+ */
+function advance(state, persona, history, playerInput, choiceIndex, prevOptionMeta) {
+ // ① 人格分析更新
+ const analysis = personaAnalyzer.analyze(
+ playerInput, choiceIndex, prevOptionMeta, persona.current_scores
+ );
+
+ // 更新 persona
+ const oldScores = { ...persona.current_scores };
+ persona.current_scores = analysis.scores;
+ persona.dominant_trait = analysis.dominant;
+ if (!persona.history) persona.history = [];
+ persona.history.push({
+ timestamp: new Date().toISOString(),
+ scores: { ...analysis.scores },
+ input: playerInput,
+ choice_index: choiceIndex
+ });
+
+ // ② 确定事件类型
+ const eventType = determineEventType(analysis.surges, analysis.crossRise);
+
+ // ③ 匹配剧情模板
+ const plotTemplate = matchPlotTemplate(eventType, analysis.scores, state.chapter || 0);
+
+ // ④ 生成选项元数据
+ const choicesMeta = generateChoicesMeta(analysis.scores);
+
+ // ⑤ 生成叙事(本地 fallback)
+ const result = generateLocalNarrative(state, persona, plotTemplate, choicesMeta);
+
+ // ⑥ 更新 state
+ state.chapter = result.meta.chapter;
+ state.paragraph = result.meta.paragraph;
+ state.four_dimensions = personaAnalyzer.toFourDimensions(analysis.scores);
+
+ // ⑦ 更新 history
+ if (!history.choices) history.choices = [];
+ history.choices.push({
+ text: playerInput || `选项${(choiceIndex || 0) + 1}`,
+ choice_index: choiceIndex,
+ timestamp: new Date().toISOString()
+ });
+ if (!history.chapters) history.chapters = [];
+ if (history.chapters.length < result.meta.chapter) {
+ history.chapters.push({
+ number: result.meta.chapter,
+ title: result.chapter_title,
+ started_at: new Date().toISOString()
+ });
+ }
+ if (eventType === 'critical') {
+ if (!history.events) history.events = [];
+ history.events.push({
+ type: eventType,
+ chapter: result.meta.chapter,
+ trigger: analysis.surges.map(function (s) { return s.dim; }).join('+'),
+ timestamp: new Date().toISOString()
+ });
+ }
+
+ // 构建 prompt 供可选的模型调用
+ const prompt = buildPlotPrompt(state, persona, history, plotTemplate, choicesMeta);
+
+ return {
+ narrative: result,
+ state: state,
+ persona: persona,
+ history: history,
+ four_dimensions: state.four_dimensions,
+ choices_meta: choicesMeta,
+ event_type: eventType,
+ prompt: prompt
+ };
+}
+
+function pick(arr) {
+ return arr[Math.floor(Math.random() * arr.length)];
+}
+
+module.exports = {
+ advance,
+ determineEventType,
+ matchPlotTemplate,
+ generateChoicesMeta,
+ buildPlotPrompt,
+ generateLocalNarrative
+};
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/save-manager.js b/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/save-manager.js
new file mode 100644
index 0000000..0f5a1fb
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/engines/save-manager.js
@@ -0,0 +1,198 @@
+/**
+ * M-PALACE · 存档管理器
+ * 打包 / 加密 / 编号 / 解密 / 恢复
+ *
+ * 存档编号规则:PAL-{YYYYMMDD}-{随机4位}
+ * 存档结构:saves/{SAVE-ID}/state.json + persona.json + history.json
+ */
+
+const fs = require('fs');
+const path = require('path');
+const crypto = require('crypto');
+
+const SAVES_DIR = path.join(__dirname, '..', '..', 'data', 'saves');
+
+// AES-256-CBC 加密密钥
+const ENCRYPTION_KEY = process.env.PALACE_SAVE_KEY || (
+ process.env.NODE_ENV === 'production'
+ ? (function () { throw new Error('PALACE_SAVE_KEY environment variable is required in production'); })()
+ : 'palace-game-dev-only-key-32ch!!'
+);
+const IV_LENGTH = 16;
+
+/**
+ * 确保存档目录存在
+ */
+function ensureSavesDir() {
+ if (!fs.existsSync(SAVES_DIR)) {
+ fs.mkdirSync(SAVES_DIR, { recursive: true });
+ }
+}
+
+/**
+ * 生成存档编号:PAL-{YYYYMMDD}-{随机4位字母数字}
+ */
+function generateSaveId() {
+ const now = new Date();
+ const dateStr = now.getFullYear().toString() +
+ String(now.getMonth() + 1).padStart(2, '0') +
+ String(now.getDate()).padStart(2, '0');
+ const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
+ let rand = '';
+ for (let i = 0; i < 4; i++) {
+ rand += chars[Math.floor(Math.random() * chars.length)];
+ }
+ return 'PAL-' + dateStr + '-' + rand;
+}
+
+/**
+ * 派生 32 字节密钥
+ */
+function deriveKey(passphrase) {
+ return crypto.createHash('sha256').update(passphrase).digest();
+}
+
+/**
+ * AES 加密
+ */
+function encrypt(text) {
+ const key = deriveKey(ENCRYPTION_KEY);
+ const iv = crypto.randomBytes(IV_LENGTH);
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
+ let encrypted = cipher.update(text, 'utf-8', 'base64');
+ encrypted += cipher.final('base64');
+ return iv.toString('base64') + ':' + encrypted;
+}
+
+/**
+ * AES 解密
+ */
+function decrypt(encryptedText) {
+ const key = deriveKey(ENCRYPTION_KEY);
+ const parts = encryptedText.split(':');
+ const iv = Buffer.from(parts[0], 'base64');
+ const encrypted = parts.slice(1).join(':');
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
+ let decrypted = decipher.update(encrypted, 'base64', 'utf-8');
+ decrypted += decipher.final('utf-8');
+ return decrypted;
+}
+
+/**
+ * 保存存档:将 state+persona+history 打包加密写入磁盘
+ * @returns {string} 存档编号
+ */
+function save(state, persona, history, existingSaveId) {
+ ensureSavesDir();
+
+ const saveId = existingSaveId || generateSaveId();
+ const saveDir = path.join(SAVES_DIR, saveId);
+
+ if (!fs.existsSync(saveDir)) {
+ fs.mkdirSync(saveDir, { recursive: true });
+ }
+
+ // 加密各文件内容后写入
+ const stateData = encrypt(JSON.stringify(state));
+ const personaData = encrypt(JSON.stringify(persona));
+ const historyData = encrypt(JSON.stringify(history));
+
+ fs.writeFileSync(path.join(saveDir, 'state.json'), stateData, 'utf-8');
+ fs.writeFileSync(path.join(saveDir, 'persona.json'), personaData, 'utf-8');
+ fs.writeFileSync(path.join(saveDir, 'history.json'), historyData, 'utf-8');
+
+ // 写入元数据(不加密,方便索引)
+ const meta = {
+ save_id: saveId,
+ created_at: new Date().toISOString(),
+ chapter: state.chapter || 0,
+ paragraph: state.paragraph || 0,
+ dynasty: state.world ? state.world.dynasty_name : '未知',
+ role: state.player ? state.player.role : '未知'
+ };
+ fs.writeFileSync(path.join(saveDir, 'meta.json'), JSON.stringify(meta, null, 2), 'utf-8');
+
+ return saveId;
+}
+
+/**
+ * 读取存档:解密并恢复完整状态
+ * @param {string} saveId 存档编号
+ * @returns {{ state, persona, history, meta }}
+ */
+function load(saveId) {
+ const saveDir = path.join(SAVES_DIR, saveId);
+
+ if (!fs.existsSync(saveDir)) {
+ return null;
+ }
+
+ const stateEncrypted = fs.readFileSync(path.join(saveDir, 'state.json'), 'utf-8');
+ const personaEncrypted = fs.readFileSync(path.join(saveDir, 'persona.json'), 'utf-8');
+ const historyEncrypted = fs.readFileSync(path.join(saveDir, 'history.json'), 'utf-8');
+
+ const state = JSON.parse(decrypt(stateEncrypted));
+ const persona = JSON.parse(decrypt(personaEncrypted));
+ const history = JSON.parse(decrypt(historyEncrypted));
+
+ let meta = null;
+ const metaPath = path.join(saveDir, 'meta.json');
+ if (fs.existsSync(metaPath)) {
+ meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
+ }
+
+ return { state, persona, history, meta };
+}
+
+/**
+ * 列出所有存档
+ */
+function listSaves() {
+ ensureSavesDir();
+ const dirs = fs.readdirSync(SAVES_DIR).filter(function (d) {
+ return d.startsWith('PAL-') && fs.statSync(path.join(SAVES_DIR, d)).isDirectory();
+ });
+ return dirs.map(function (d) {
+ const metaPath = path.join(SAVES_DIR, d, 'meta.json');
+ if (fs.existsSync(metaPath)) {
+ return JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
+ }
+ return { save_id: d };
+ });
+}
+
+/**
+ * 删除存档
+ */
+function deleteSave(saveId) {
+ var saveDir = path.join(SAVES_DIR, saveId);
+ if (!fs.existsSync(saveDir)) return false;
+ try {
+ var files = fs.readdirSync(saveDir);
+ for (var i = 0; i < files.length; i++) {
+ fs.unlinkSync(path.join(saveDir, files[i]));
+ }
+ fs.rmdirSync(saveDir);
+ return true;
+ } catch (err) {
+ return false;
+ }
+}
+
+/**
+ * 检查存档是否存在
+ */
+function exists(saveId) {
+ return fs.existsSync(path.join(SAVES_DIR, saveId));
+}
+
+module.exports = {
+ save,
+ load,
+ listSaves,
+ deleteSave,
+ exists,
+ generateSaveId,
+ encrypt,
+ decrypt
+};
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/package-lock.json b/agents/zhuyuan-dev-agent/modules/palace-game/backend/package-lock.json
new file mode 100644
index 0000000..1a6e638
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/package-lock.json
@@ -0,0 +1,854 @@
+{
+ "name": "palace-game-backend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "palace-game-backend",
+ "version": "1.0.0",
+ "dependencies": {
+ "cors": "^2.8.5",
+ "express": "^4.21.2"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.14.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ }
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/package.json b/agents/zhuyuan-dev-agent/modules/palace-game/backend/package.json
new file mode 100644
index 0000000..5c4ec56
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "palace-game-backend",
+ "version": "1.0.0",
+ "description": "M-PALACE · 动态人格宫廷生成系统 · 后端服务",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js",
+ "dev": "node server.js"
+ },
+ "dependencies": {
+ "cors": "^2.8.5",
+ "express": "^4.21.2"
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/interact.js b/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/interact.js
new file mode 100644
index 0000000..1c13fa3
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/interact.js
@@ -0,0 +1,84 @@
+/**
+ * M-PALACE · 玩家互动 API
+ * POST /api/palace/interact
+ *
+ * 接收玩家输入 → 人格分析 → 情节推进 → 返回新叙事+选项
+ */
+
+const express = require('express');
+const router = express.Router();
+const plotEngine = require('../engines/plot-engine');
+const saveManager = require('../engines/save-manager');
+
+/**
+ * POST /api/palace/interact
+ * body: {
+ * save_id: "PAL-XXXXXXXX-XXXX",
+ * input: "玩家输入文本" | null,
+ * choice_index: 0|1|2|null,
+ * option_meta: { a_dims, b_dims, c_dims } | null
+ * }
+ */
+router.post('/', function (req, res) {
+ try {
+ var body = req.body || {};
+ var saveId = body.save_id;
+ var playerInput = body.input || '';
+ var choiceIndex = body.choice_index != null ? body.choice_index : null;
+ var optionMeta = body.option_meta || null;
+
+ if (!saveId) {
+ return res.status(400).json({
+ error: true,
+ code: 'MISSING_SAVE_ID',
+ message: '缺少存档编号'
+ });
+ }
+
+ // 读取存档
+ var saveData = saveManager.load(saveId);
+ if (!saveData) {
+ return res.status(404).json({
+ error: true,
+ code: 'SAVE_NOT_FOUND',
+ message: '存档不存在:' + saveId
+ });
+ }
+
+ var state = saveData.state;
+ var persona = saveData.persona;
+ var history = saveData.history;
+
+ // 情节推进
+ var result = plotEngine.advance(
+ state, persona, history,
+ playerInput, choiceIndex, optionMeta
+ );
+
+ // 自动存档
+ saveManager.save(result.state, result.persona, result.history, saveId);
+
+ res.json({
+ error: false,
+ save_id: saveId,
+ narrative: {
+ chapter_title: result.narrative.chapter_title,
+ narrative: result.narrative.narrative,
+ choices: result.narrative.choices
+ },
+ four_dimensions: result.four_dimensions,
+ choices_meta: result.choices_meta,
+ event_type: result.event_type,
+ chapter: result.state.chapter,
+ paragraph: result.state.paragraph
+ });
+ } catch (err) {
+ res.status(500).json({
+ error: true,
+ code: 'INTERACT_ERROR',
+ message: '剧情推进失败:' + err.message
+ });
+ }
+});
+
+module.exports = router;
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/save.js b/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/save.js
new file mode 100644
index 0000000..94ebdc6
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/save.js
@@ -0,0 +1,113 @@
+/**
+ * M-PALACE · 存档/读档 API
+ * POST /api/palace/save → 保存当前状态
+ * POST /api/palace/load → 读取存档
+ * GET /api/palace/saves → 列出所有存档
+ */
+
+const express = require('express');
+const router = express.Router();
+const saveManager = require('../engines/save-manager');
+
+/**
+ * POST /api/palace/save
+ * body: { save_id, state, persona, history }
+ */
+router.post('/', function (req, res) {
+ try {
+ var body = req.body || {};
+ var saveId = body.save_id;
+ var state = body.state;
+ var persona = body.persona;
+ var history = body.history;
+
+ if (!state || !persona || !history) {
+ return res.status(400).json({
+ error: true,
+ code: 'MISSING_DATA',
+ message: '缺少存档数据'
+ });
+ }
+
+ var resultId = saveManager.save(state, persona, history, saveId);
+
+ res.json({
+ error: false,
+ save_id: resultId,
+ message: '存档成功',
+ saved_at: new Date().toISOString()
+ });
+ } catch (err) {
+ res.status(500).json({
+ error: true,
+ code: 'SAVE_ERROR',
+ message: '存档失败:' + err.message
+ });
+ }
+});
+
+/**
+ * POST /api/palace/load
+ * body: { save_id: "PAL-XXXXXXXX-XXXX" }
+ */
+router.post('/load', function (req, res) {
+ try {
+ var body = req.body || {};
+ var saveId = body.save_id;
+
+ if (!saveId) {
+ return res.status(400).json({
+ error: true,
+ code: 'MISSING_SAVE_ID',
+ message: '请输入存档编号'
+ });
+ }
+
+ var saveData = saveManager.load(saveId);
+
+ if (!saveData) {
+ return res.status(404).json({
+ error: true,
+ code: 'SAVE_NOT_FOUND',
+ message: '存档不存在:' + saveId
+ });
+ }
+
+ res.json({
+ error: false,
+ save_id: saveId,
+ state: saveData.state,
+ persona: saveData.persona,
+ history: saveData.history,
+ meta: saveData.meta
+ });
+ } catch (err) {
+ res.status(500).json({
+ error: true,
+ code: 'LOAD_ERROR',
+ message: '读档失败:' + err.message
+ });
+ }
+});
+
+/**
+ * GET /api/palace/saves
+ */
+router.get('/list', function (_req, res) {
+ try {
+ var saves = saveManager.listSaves();
+ res.json({
+ error: false,
+ saves: saves,
+ count: saves.length
+ });
+ } catch (err) {
+ res.status(500).json({
+ error: true,
+ code: 'LIST_ERROR',
+ message: '获取存档列表失败:' + err.message
+ });
+ }
+});
+
+module.exports = router;
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/start.js b/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/start.js
new file mode 100644
index 0000000..c5d2aa0
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/routes/start.js
@@ -0,0 +1,94 @@
+/**
+ * M-PALACE · 快速启动 API
+ * POST /api/palace/start
+ *
+ * 接收世界观+身份锚点 → 生成初始人格快照 → 背景生成 → 返回世界状态
+ */
+
+const express = require('express');
+const router = express.Router();
+const personaAnalyzer = require('../engines/persona-analyzer');
+const backgroundGen = require('../engines/background-gen');
+const saveManager = require('../engines/save-manager');
+
+/**
+ * POST /api/palace/start
+ * body: { worldview: "古代中国"|"架空王朝", role: "妃子"|"皇帝"|"重臣"|"奸臣"|"随机" }
+ */
+router.post('/', function (req, res) {
+ try {
+ var body = req.body || {};
+ var worldview = body.worldview || '古代中国';
+ var role = body.role || '随机';
+
+ // 随机角色
+ if (role === '随机') {
+ var roles = ['妃子', '皇帝', '重臣', '奸臣'];
+ role = roles[Math.floor(Math.random() * roles.length)];
+ }
+
+ // ① 基于身份锚点生成初始人格分数
+ var initialScores = personaAnalyzer.getInitialScores(role);
+
+ // ② 背景生成引擎
+ var bgResult = backgroundGen.generate(worldview, role, initialScores);
+
+ // ③ 构建初始状态
+ var state = {
+ world: bgResult.world,
+ npcs: bgResult.world.npcs || [],
+ player: {
+ role: role,
+ title: bgResult.world.player_intro,
+ worldview: worldview
+ },
+ chapter: 1,
+ paragraph: 1,
+ four_dimensions: personaAnalyzer.toFourDimensions(initialScores)
+ };
+
+ var persona = {
+ current_scores: initialScores,
+ history: [{
+ timestamp: new Date().toISOString(),
+ scores: { ...initialScores },
+ input: '__init__',
+ choice_index: null
+ }],
+ dominant_trait: personaAnalyzer.getDominantTrait(initialScores)
+ };
+
+ var history = {
+ chapters: [{
+ number: 1,
+ title: '第一章·' + (bgResult.world.dynasty_name || '序幕'),
+ started_at: new Date().toISOString()
+ }],
+ events: [],
+ choices: []
+ };
+
+ // ④ 自动存档
+ var saveId = saveManager.save(state, persona, history);
+
+ res.json({
+ error: false,
+ save_id: saveId,
+ state: state,
+ narrative: {
+ chapter_title: history.chapters[0].title,
+ narrative: bgResult.world.opening_event,
+ choices: bgResult.world.opening_choices
+ },
+ four_dimensions: state.four_dimensions
+ });
+ } catch (err) {
+ res.status(500).json({
+ error: true,
+ code: 'START_ERROR',
+ message: '宫廷世界生成失败:' + err.message
+ });
+ }
+});
+
+module.exports = router;
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/backend/server.js b/agents/zhuyuan-dev-agent/modules/palace-game/backend/server.js
new file mode 100644
index 0000000..15c96bf
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/backend/server.js
@@ -0,0 +1,59 @@
+/**
+ * M-PALACE · Express 主服务
+ * 动态人格宫廷生成系统 · 后端核心
+ *
+ * API 路由:
+ * POST /api/palace/start → 快速启动(生成世界)
+ * POST /api/palace/interact → 玩家互动(剧情推进)
+ * POST /api/palace/save → 存档
+ * POST /api/palace/load → 读档
+ * GET /api/palace/saves → 存档列表
+ * GET /api/palace/health → 健康检查
+ */
+
+const express = require('express');
+const cors = require('cors');
+const path = require('path');
+
+const startRoutes = require('./routes/start');
+const interactRoutes = require('./routes/interact');
+const saveRoutes = require('./routes/save');
+
+const app = express();
+const PORT = process.env.PALACE_PORT || 3003;
+
+// 中间件
+app.use(cors());
+app.use(express.json({ limit: '2mb' }));
+
+// 静态文件:前端
+app.use('/palace-game', express.static(path.join(__dirname, '..', 'frontend')));
+
+// API 路由
+app.use('/api/palace/start', startRoutes);
+app.use('/api/palace/interact', interactRoutes);
+app.use('/api/palace/save', saveRoutes);
+
+// 健康检查
+app.get('/api/palace/health', function (_req, res) {
+ res.json({
+ status: 'ok',
+ service: 'palace-game',
+ version: '1.0.0',
+ timestamp: new Date().toISOString()
+ });
+});
+
+// 根路由 → 前端入口
+app.get('/', function (_req, res) {
+ res.redirect('/palace-game/index.html');
+});
+
+// 启动服务
+if (require.main === module) {
+ app.listen(PORT, function () {
+ console.log('🏯 M-PALACE · 宫廷纪服务启动 · 端口 ' + PORT);
+ });
+}
+
+module.exports = app;
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/data/palace-db.json b/agents/zhuyuan-dev-agent/modules/palace-game/data/palace-db.json
new file mode 100644
index 0000000..9f4e8c2
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/data/palace-db.json
@@ -0,0 +1,19 @@
+{
+ "power": {
+ "positions": ["皇帝", "太后", "皇后", "贵妃", "丞相", "大将军", "太监总管"],
+ "factions": ["太后党", "皇后党", "新贵派", "清流派", "孤臣"],
+ "events": ["朝堂弹劾", "密谋政变", "联姻结盟", "战事告急"]
+ },
+ "status": {
+ "ranks": ["答应", "常在", "贵人", "嫔", "妃", "贵妃", "皇贵妃", "皇后"],
+ "events": ["晋封", "降位", "禁足", "受宠", "失宠", "怀孕", "小产"]
+ },
+ "emotion": {
+ "types": ["暗恋", "深情", "嫉妒", "怨恨", "愧疚", "依赖", "背叛"],
+ "triggers": ["偶遇旧人", "收到密信", "目睹真相", "被迫抉择"]
+ },
+ "conflict": {
+ "types": ["争宠", "夺权", "报仇", "自保", "救人", "揭秘"],
+ "escalation": ["暗示", "试探", "交锋", "撕破脸", "鱼死网破"]
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/data/persona-dict.json b/agents/zhuyuan-dev-agent/modules/palace-game/data/persona-dict.json
new file mode 100644
index 0000000..3caf09d
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/data/persona-dict.json
@@ -0,0 +1,52 @@
+{
+ "dimensions": [
+ {
+ "id": "ambition",
+ "name": "野心",
+ "keywords": ["权力", "登上", "掌控", "不甘", "凭什么"],
+ "behavior_signals": ["选择攻击性选项", "主动挑战权威", "拒绝退让"]
+ },
+ {
+ "id": "warmth",
+ "name": "温情",
+ "keywords": ["保护", "心疼", "陪伴", "不忍", "算了"],
+ "behavior_signals": ["选择和解", "保护弱势角色", "牺牲利益"]
+ },
+ {
+ "id": "aggression",
+ "name": "攻击性",
+ "keywords": ["杀", "除掉", "报复", "不放过", "代价"],
+ "behavior_signals": ["选择对抗", "设计陷阱", "直接冲突"]
+ },
+ {
+ "id": "suspicion",
+ "name": "多疑",
+ "keywords": ["试探", "不对劲", "背后", "真的吗", "骗"],
+ "behavior_signals": ["频繁试探", "不信任NPC", "选择观望"]
+ },
+ {
+ "id": "vanity",
+ "name": "虚荣",
+ "keywords": ["面子", "配得上", "最好的", "羡慕", "风光"],
+ "behavior_signals": ["追求地位象征", "在意他人评价", "选择炫耀"]
+ },
+ {
+ "id": "cunning",
+ "name": "心机",
+ "keywords": ["计划", "利用", "棋子", "布局", "暗中"],
+ "behavior_signals": ["迂回策略", "利用他人", "长线布局"]
+ },
+ {
+ "id": "loyalty",
+ "name": "忠义",
+ "keywords": ["信任", "誓言", "守护", "不背叛", "承诺"],
+ "behavior_signals": ["坚守阵营", "拒绝背叛", "为盟友冒险"]
+ },
+ {
+ "id": "fear",
+ "name": "恐惧",
+ "keywords": ["害怕", "不敢", "万一", "小心", "退"],
+ "behavior_signals": ["回避冲突", "选择保守", "犹豫不决"]
+ }
+ ]
+}
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/data/saves/.gitkeep b/agents/zhuyuan-dev-agent/modules/palace-game/data/saves/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/data/templates/ancient-china.json b/agents/zhuyuan-dev-agent/modules/palace-game/data/templates/ancient-china.json
new file mode 100644
index 0000000..cdce45c
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/data/templates/ancient-china.json
@@ -0,0 +1,40 @@
+{
+ "id": "ancient-china",
+ "name": "古代中国",
+ "description": "大周王朝·正统朝堂·后宫嫔妃·权臣博弈",
+ "setting": {
+ "dynasty": "大周",
+ "era_prefix": ["永安", "承平", "天启", "景和", "元丰"],
+ "capital": "长安",
+ "palace_name": "未央宫",
+ "inner_palace": "长乐宫"
+ },
+ "roles": {
+ "妃子": {
+ "title_options": ["淑妃", "德妃", "贤妃", "丽嫔", "婉嫔"],
+ "family_options": ["书香门第", "武将世家", "寒门出身", "没落贵族"],
+ "starting_rank": "嫔",
+ "intro_template": "你是{family}出身的{title},入宫{years}年。{situation}"
+ },
+ "皇帝": {
+ "title_options": ["新帝", "少年天子", "中兴之主"],
+ "situation_options": ["初登大宝·朝局未稳", "亲政在即·太后干政", "盛世之君·暗流涌动"],
+ "intro_template": "你是{dynasty}的{title},{situation}。"
+ },
+ "重臣": {
+ "title_options": ["丞相", "御史大夫", "太傅", "兵部尚书"],
+ "faction_options": ["清流派", "保皇派", "中立派"],
+ "intro_template": "你是{dynasty}{title},{faction}中流砥柱。{situation}"
+ },
+ "奸臣": {
+ "title_options": ["内阁首辅", "司礼监掌印", "锦衣卫指挥使"],
+ "ambition_options": ["权倾朝野", "挟天子以令诸侯", "暗中培植势力"],
+ "intro_template": "你是{title},表面忠良,实则{ambition}。"
+ }
+ },
+ "atmosphere": {
+ "time_of_day": ["寅时·天未亮", "辰时·早朝", "午时·日正中", "酉时·暮色", "亥时·深夜"],
+ "weather": ["细雨绵绵", "月朗星稀", "寒风凛冽", "春光明媚", "大雪纷飞"],
+ "locations": ["御书房", "太和殿", "长乐宫", "御花园", "冷宫", "密道", "城墙"]
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/data/templates/fantasy-dynasty.json b/agents/zhuyuan-dev-agent/modules/palace-game/data/templates/fantasy-dynasty.json
new file mode 100644
index 0000000..818595b
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/data/templates/fantasy-dynasty.json
@@ -0,0 +1,40 @@
+{
+ "id": "fantasy-dynasty",
+ "name": "架空王朝",
+ "description": "九霄帝国·灵力与权谋交织·玄幻宫廷",
+ "setting": {
+ "dynasty": "九霄",
+ "era_prefix": ["灵元", "天衍", "混沌", "太初", "紫极"],
+ "capital": "天枢城",
+ "palace_name": "凌霄殿",
+ "inner_palace": "瑶光阁"
+ },
+ "roles": {
+ "妃子": {
+ "title_options": ["星妃", "月嫔", "霜华夫人", "云裳贵人"],
+ "family_options": ["上古灵族后裔", "剑修世家", "炼丹宗门", "凡人修仙"],
+ "starting_rank": "嫔",
+ "intro_template": "你是{family}出身的{title},入宫{years}年。{situation}"
+ },
+ "皇帝": {
+ "title_options": ["灵帝", "天选之主", "末代帝君"],
+ "situation_options": ["灵力觉醒·诸族觊觎", "仙魔大战后·百废待兴", "天命将尽·寻找继承"],
+ "intro_template": "你是{dynasty}的{title},{situation}。"
+ },
+ "重臣": {
+ "title_options": ["星辰阁大长老", "天机院主", "护国神将", "灵枢司正"],
+ "faction_options": ["仙道派", "人道派", "中立长老会"],
+ "intro_template": "你是{dynasty}{title},{faction}核心。{situation}"
+ },
+ "奸臣": {
+ "title_options": ["暗影阁主", "禁术研究者", "外域使者"],
+ "ambition_options": ["窃取帝国灵脉", "复活远古邪神", "暗中操控傀儡帝"],
+ "intro_template": "你是{title},表面忠良,实则{ambition}。"
+ }
+ },
+ "atmosphere": {
+ "time_of_day": ["灵潮初涌·黎明", "日中·灵力最盛", "黄昏·阴阳交替", "子夜·暗灵活跃"],
+ "weather": ["灵雨飘洒", "星河低垂", "雷灵暴动", "万里晴空·灵气充沛", "迷雾笼罩"],
+ "locations": ["灵枢大殿", "星象台", "瑶光阁", "灵兽园", "封印之地", "虚空裂缝", "万灵池"]
+ }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/frontend/game.html b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/game.html
new file mode 100644
index 0000000..7ad7d29
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/game.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+ 宫廷纪 · 叙事
+
+
+
+
+
+
+
+
+
+
+
+
第一章 · 序幕
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 📖 第1章 · 第1段
+ 存档号: —
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/frontend/game.js b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/game.js
new file mode 100644
index 0000000..3fb4ce0
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/game.js
@@ -0,0 +1,311 @@
+/**
+ * M-PALACE · 游戏交互逻辑
+ * 管理叙事渲染、选项交互、状态栏更新、存档操作
+ */
+
+(function () {
+ 'use strict';
+
+ var API_BASE = window.location.origin;
+ var saveId = sessionStorage.getItem('palace_save_id') || '';
+ var choicesMeta = null;
+ var isTyping = false;
+
+ // ---------- Initialization ----------
+ function init() {
+ var startData = sessionStorage.getItem('palace_state');
+ var loadedData = sessionStorage.getItem('palace_loaded');
+
+ if (startData) {
+ var data = JSON.parse(startData);
+ sessionStorage.removeItem('palace_state');
+ renderGameData(data);
+ } else if (loadedData) {
+ var loaded = JSON.parse(loadedData);
+ sessionStorage.removeItem('palace_loaded');
+ renderLoadedData(loaded);
+ } else if (saveId) {
+ loadSave(saveId);
+ } else {
+ window.location.href = 'index.html';
+ }
+
+ bindEvents();
+ }
+
+ // ---------- Rendering ----------
+ function renderGameData(data) {
+ saveId = data.save_id || saveId;
+ document.getElementById('save-id-display').textContent = '存档号: ' + saveId;
+
+ if (data.state && data.state.player) {
+ var p = data.state.player;
+ document.getElementById('top-title').textContent =
+ '🏯 宫廷纪 · ' + (p.worldview || '') + ' · ' + (p.role || '');
+ }
+
+ if (data.narrative) {
+ renderNarrative(data.narrative);
+ }
+
+ if (data.four_dimensions) {
+ updateStatusBar(data.four_dimensions);
+ }
+
+ updateProgressInfo(data.chapter || 1, data.paragraph || 1);
+ }
+
+ function renderLoadedData(data) {
+ document.getElementById('save-id-display').textContent = '存档号: ' + saveId;
+
+ if (data.state) {
+ var s = data.state;
+ if (s.player) {
+ document.getElementById('top-title').textContent =
+ '🏯 宫廷纪 · ' + (s.player.worldview || '') + ' · ' + (s.player.role || '');
+ }
+ if (s.four_dimensions) {
+ updateStatusBar(s.four_dimensions);
+ }
+ updateProgressInfo(s.chapter || 1, s.paragraph || 1);
+ }
+
+ // Render last narrative from history or show resume message
+ var narrativeEl = document.getElementById('narrative-text');
+ narrativeEl.textContent = '存档已恢复。继续你的宫廷故事……';
+ renderChoices(['继续前行', '回顾往事', '观察四周']);
+ }
+
+ function renderNarrative(narrative) {
+ if (narrative.chapter_title) {
+ document.getElementById('chapter-title').textContent = narrative.chapter_title;
+ }
+
+ var textEl = document.getElementById('narrative-text');
+ if (narrative.narrative) {
+ typewriter(textEl, narrative.narrative);
+ }
+
+ if (narrative.choices && narrative.choices.length > 0) {
+ // Delay choices until typewriter finishes
+ var delay = narrative.narrative ? narrative.narrative.length * 50 + 500 : 200;
+ setTimeout(function () {
+ renderChoices(narrative.choices);
+ }, delay);
+ }
+
+ choicesMeta = narrative.choices_meta || null;
+ }
+
+ function renderChoices(choices) {
+ var container = document.getElementById('choices-container');
+ container.innerHTML = '';
+
+ var nums = ['①', '②', '③'];
+ choices.forEach(function (text, i) {
+ var btn = document.createElement('button');
+ btn.className = 'choice-btn fade-in';
+ btn.innerHTML = '' + (nums[i] || '·') + '' + escapeHtml(text);
+ btn.addEventListener('click', function () {
+ submitChoice(i, text);
+ });
+ container.appendChild(btn);
+ });
+ }
+
+ // ---------- Typewriter Effect ----------
+ function typewriter(el, text) {
+ isTyping = true;
+ el.textContent = '';
+ var cursor = document.createElement('span');
+ cursor.className = 'typewriter-cursor';
+ el.appendChild(cursor);
+
+ var i = 0;
+ var interval = setInterval(function () {
+ if (i < text.length) {
+ el.insertBefore(document.createTextNode(text[i]), cursor);
+ i++;
+ } else {
+ clearInterval(interval);
+ if (cursor.parentNode) cursor.remove();
+ isTyping = false;
+ }
+ }, 45);
+ }
+
+ // ---------- Status Bar ----------
+ function updateStatusBar(dims) {
+ var keys = ['power', 'status', 'emotion', 'conflict'];
+ keys.forEach(function (k) {
+ var val = dims[k] != null ? dims[k] : 50;
+ var bar = document.getElementById('bar-' + k);
+ var valEl = document.getElementById('val-' + k);
+ if (bar) bar.style.width = val + '%';
+ if (valEl) valEl.textContent = val;
+ });
+ }
+
+ function updateProgressInfo(chapter, paragraph) {
+ var el = document.getElementById('progress-info');
+ if (el) el.textContent = '📖 第' + chapter + '章 · 第' + paragraph + '段';
+ }
+
+ // ---------- Interactions ----------
+ function submitChoice(index, text) {
+ if (isTyping) return;
+ disableChoices();
+
+ fetch(API_BASE + '/api/palace/interact', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ save_id: saveId,
+ input: text,
+ choice_index: index,
+ option_meta: choicesMeta
+ })
+ })
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error) {
+ showToast(data.message || '互动失败');
+ enableChoices();
+ return;
+ }
+ choicesMeta = data.choices_meta || null;
+ if (data.narrative) renderNarrative(data.narrative);
+ if (data.four_dimensions) updateStatusBar(data.four_dimensions);
+ updateProgressInfo(data.chapter || 1, data.paragraph || 1);
+ })
+ .catch(function (err) {
+ showToast('网络错误:' + err.message);
+ enableChoices();
+ });
+ }
+
+ function submitFreeInput() {
+ var input = document.getElementById('free-input');
+ var text = input.value.trim();
+ if (!text || isTyping) return;
+ input.value = '';
+ disableChoices();
+
+ fetch(API_BASE + '/api/palace/interact', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ save_id: saveId,
+ input: text,
+ choice_index: null,
+ option_meta: choicesMeta
+ })
+ })
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error) {
+ showToast(data.message || '互动失败');
+ enableChoices();
+ return;
+ }
+ choicesMeta = data.choices_meta || null;
+ if (data.narrative) renderNarrative(data.narrative);
+ if (data.four_dimensions) updateStatusBar(data.four_dimensions);
+ updateProgressInfo(data.chapter || 1, data.paragraph || 1);
+ })
+ .catch(function (err) {
+ showToast('网络错误:' + err.message);
+ enableChoices();
+ });
+ }
+
+ function disableChoices() {
+ var btns = document.querySelectorAll('.choice-btn');
+ btns.forEach(function (b) { b.disabled = true; b.style.opacity = '0.5'; });
+ }
+
+ function enableChoices() {
+ var btns = document.querySelectorAll('.choice-btn');
+ btns.forEach(function (b) { b.disabled = false; b.style.opacity = '1'; });
+ }
+
+ // ---------- Save / Load ----------
+ function doSave() {
+ fetch(API_BASE + '/api/palace/save', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ save_id: saveId, state: {}, persona: {}, history: {} })
+ })
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error) { showToast(data.message); return; }
+ showToast('存档成功:' + data.save_id);
+ })
+ .catch(function (err) {
+ showToast('存档失败:' + err.message);
+ });
+ }
+
+ function loadSave(id) {
+ fetch(API_BASE + '/api/palace/save/load', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ save_id: id })
+ })
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error) {
+ showToast(data.message || '读档失败');
+ window.location.href = 'index.html';
+ return;
+ }
+ renderLoadedData(data);
+ })
+ .catch(function (err) {
+ showToast('网络错误:' + err.message);
+ window.location.href = 'index.html';
+ });
+ }
+
+ // ---------- Events ----------
+ function bindEvents() {
+ document.getElementById('btn-save').addEventListener('click', doSave);
+
+ document.getElementById('btn-free-submit').addEventListener('click', submitFreeInput);
+
+ document.getElementById('free-input').addEventListener('keydown', function (e) {
+ if (e.key === 'Enter') submitFreeInput();
+ });
+
+ var toggle = document.getElementById('status-toggle');
+ var bar = document.getElementById('status-bar');
+ toggle.addEventListener('click', function () {
+ bar.classList.toggle('collapsed');
+ toggle.textContent = bar.classList.contains('collapsed') ? '▶ 状态栏' : '▼ 状态栏';
+ });
+ }
+
+ // ---------- Utilities ----------
+ function escapeHtml(str) {
+ var div = document.createElement('div');
+ div.appendChild(document.createTextNode(str));
+ return div.innerHTML;
+ }
+
+ function showToast(msg) {
+ var old = document.querySelector('.toast');
+ if (old) old.remove();
+ var el = document.createElement('div');
+ el.className = 'toast';
+ el.textContent = msg;
+ document.body.appendChild(el);
+ setTimeout(function () { if (el.parentNode) el.remove(); }, 3000);
+ }
+
+ // ---------- Boot ----------
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/frontend/index.html b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/index.html
new file mode 100644
index 0000000..e3969f3
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/index.html
@@ -0,0 +1,123 @@
+
+
+
+
+
+ 宫·廷·纪 — 动态人格宫廷生成系统
+
+
+
+
+
+
+
🏯
+
宫 · 廷 · 纪
+
动态人格宫廷生成系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
已有存档?输入编号继续
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/frontend/save.html b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/save.html
new file mode 100644
index 0000000..ac38470
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/save.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+ 宫廷纪 · 存档管理
+
+
+
+
+
+
+
🏯 存档管理
+
+
+
+
+
+
+
+
+
+
已有存档
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/frontend/save.js b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/save.js
new file mode 100644
index 0000000..bc6551e
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/save.js
@@ -0,0 +1,139 @@
+/**
+ * M-PALACE · 存档读档逻辑
+ * 列出存档、读取存档、删除存档
+ */
+
+(function () {
+ 'use strict';
+
+ var API_BASE = window.location.origin;
+
+ function init() {
+ loadSaveList();
+ bindEvents();
+ }
+
+ // ---------- Load Save List ----------
+ function loadSaveList() {
+ var container = document.getElementById('save-list');
+
+ fetch(API_BASE + '/api/palace/save/list')
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error) {
+ container.innerHTML = '获取存档列表失败
';
+ return;
+ }
+ renderSaveList(data.saves || []);
+ })
+ .catch(function () {
+ container.innerHTML = '无法连接服务器
';
+ });
+ }
+
+ function renderSaveList(saves) {
+ var container = document.getElementById('save-list');
+
+ if (saves.length === 0) {
+ container.innerHTML = '暂无存档
';
+ return;
+ }
+
+ container.innerHTML = '';
+ saves.forEach(function (save) {
+ var item = document.createElement('div');
+ item.className = 'save-item fade-in';
+
+ var info = document.createElement('div');
+ info.className = 'save-info';
+
+ var idEl = document.createElement('div');
+ idEl.className = 'save-id';
+ idEl.textContent = save.save_id;
+
+ var detail = document.createElement('div');
+ detail.className = 'save-detail';
+ var parts = [];
+ if (save.dynasty) parts.push(save.dynasty);
+ if (save.role) parts.push(save.role);
+ if (save.chapter) parts.push('第' + save.chapter + '章');
+ if (save.created_at) parts.push(save.created_at.slice(0, 16).replace('T', ' '));
+ detail.textContent = parts.join(' · ');
+
+ info.appendChild(idEl);
+ info.appendChild(detail);
+
+ var actions = document.createElement('div');
+ actions.className = 'save-actions';
+
+ var loadBtn = document.createElement('button');
+ loadBtn.className = 'btn btn-small';
+ loadBtn.textContent = '读档';
+ loadBtn.addEventListener('click', function () {
+ doLoad(save.save_id);
+ });
+
+ actions.appendChild(loadBtn);
+
+ item.appendChild(info);
+ item.appendChild(actions);
+ container.appendChild(item);
+ });
+ }
+
+ // ---------- Load Save ----------
+ function doLoad(saveId) {
+ fetch(API_BASE + '/api/palace/save/load', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ save_id: saveId })
+ })
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ if (data.error) {
+ showToast(data.message || '读档失败');
+ return;
+ }
+ sessionStorage.setItem('palace_save_id', saveId);
+ sessionStorage.setItem('palace_loaded', JSON.stringify(data));
+ window.location.href = 'game.html';
+ })
+ .catch(function (err) {
+ showToast('网络错误:' + err.message);
+ });
+ }
+
+ // ---------- Events ----------
+ function bindEvents() {
+ document.getElementById('btn-load').addEventListener('click', function () {
+ var id = document.getElementById('load-id').value.trim();
+ if (!id) { showToast('请输入存档编号'); return; }
+ doLoad(id);
+ });
+
+ document.getElementById('load-id').addEventListener('keydown', function (e) {
+ if (e.key === 'Enter') {
+ var id = this.value.trim();
+ if (id) doLoad(id);
+ }
+ });
+ }
+
+ // ---------- Utilities ----------
+ function showToast(msg) {
+ var old = document.querySelector('.toast');
+ if (old) old.remove();
+ var el = document.createElement('div');
+ el.className = 'toast';
+ el.textContent = msg;
+ document.body.appendChild(el);
+ setTimeout(function () { if (el.parentNode) el.remove(); }, 3000);
+ }
+
+ // ---------- Boot ----------
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/agents/zhuyuan-dev-agent/modules/palace-game/frontend/style.css b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/style.css
new file mode 100644
index 0000000..ffd770f
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/palace-game/frontend/style.css
@@ -0,0 +1,564 @@
+/* ====================================
+ M-PALACE · 宫廷视觉风格
+ 暗红+金色主题 · 古风字体 · 逐字动画
+ ==================================== */
+
+/* ---------- CSS Variables ---------- */
+:root {
+ --bg-primary: #1a0a0a;
+ --bg-secondary: #0d0d1a;
+ --gold: #c9a96e;
+ --gold-light: #e8d5a3;
+ --danger: #8b0000;
+ --text-primary: #f5f0e8;
+ --text-secondary:#a0937d;
+ --border-gold: rgba(201,169,110,0.15);
+ --border-gold-md:rgba(201,169,110,0.3);
+ --glow-gold: rgba(201,169,110,0.25);
+}
+
+/* ---------- Reset & Base ---------- */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+body {
+ font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ min-height: 100vh;
+ line-height: 1.6;
+ overflow-x: hidden;
+}
+
+/* ---------- Background Texture ---------- */
+body::before {
+ content: '';
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background:
+ radial-gradient(ellipse at 20% 50%, rgba(139,0,0,0.08) 0%, transparent 60%),
+ radial-gradient(ellipse at 80% 20%, rgba(201,169,110,0.05) 0%, transparent 50%),
+ radial-gradient(ellipse at 50% 80%, rgba(13,13,26,0.6) 0%, transparent 70%);
+ pointer-events: none;
+ z-index: 0;
+}
+
+/* ---------- Layout ---------- */
+.container {
+ max-width: 720px;
+ margin: 0 auto;
+ padding: 2rem 1.5rem;
+ position: relative;
+ z-index: 1;
+}
+
+/* ---------- Typography ---------- */
+.serif {
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', 'STSong', serif;
+}
+
+h1, h2, h3 { color: var(--gold); font-weight: 400; }
+
+/* ---------- Golden Divider ---------- */
+.divider {
+ height: 1px;
+ background: linear-gradient(90deg, transparent, var(--gold), transparent);
+ margin: 1.5rem 0;
+ opacity: 0.4;
+}
+
+.divider-text {
+ text-align: center;
+ color: var(--text-secondary);
+ font-size: 0.85rem;
+ padding: 0.5rem 0;
+}
+
+/* ====================================
+ INDEX PAGE — Quick Start
+ ==================================== */
+.start-page {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ text-align: center;
+ padding: 2rem;
+}
+
+.start-icon {
+ font-size: 4rem;
+ margin-bottom: 1rem;
+ filter: drop-shadow(0 0 20px var(--glow-gold));
+}
+
+.start-title {
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+ font-size: 2.2rem;
+ letter-spacing: 0.8rem;
+ color: var(--gold);
+ margin-bottom: 0.3rem;
+}
+
+.start-subtitle {
+ font-size: 0.9rem;
+ color: var(--text-secondary);
+ margin-bottom: 2.5rem;
+ letter-spacing: 0.2rem;
+}
+
+/* Form Card */
+.start-card {
+ background: rgba(26,10,10,0.85);
+ border: 1px solid var(--border-gold);
+ border-radius: 8px;
+ padding: 2rem 2.5rem;
+ width: 100%;
+ max-width: 420px;
+ backdrop-filter: blur(8px);
+}
+
+.form-group {
+ margin-bottom: 1.5rem;
+ text-align: left;
+}
+
+.form-group label {
+ display: block;
+ font-size: 0.9rem;
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+}
+
+select, input[type="text"] {
+ width: 100%;
+ padding: 0.7rem 1rem;
+ background: rgba(13,13,26,0.6);
+ border: 1px solid var(--border-gold-md);
+ border-radius: 4px;
+ color: var(--text-primary);
+ font-size: 0.95rem;
+ font-family: inherit;
+ outline: none;
+ transition: border-color 0.3s;
+}
+
+select:focus, input[type="text"]:focus {
+ border-color: var(--gold);
+ box-shadow: 0 0 8px var(--glow-gold);
+}
+
+select option {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+}
+
+/* ---------- Buttons ---------- */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 0.75rem 2rem;
+ border: 1px solid var(--gold);
+ border-radius: 4px;
+ background: rgba(139,0,0,0.3);
+ color: var(--gold);
+ font-size: 1rem;
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+ cursor: pointer;
+ transition: all 0.3s;
+ letter-spacing: 0.15rem;
+}
+
+.btn:hover {
+ background: rgba(139,0,0,0.6);
+ box-shadow: 0 0 16px var(--glow-gold);
+ transform: translateY(-1px);
+}
+
+.btn:active { transform: translateY(0); }
+
+.btn-primary {
+ width: 100%;
+ padding: 1rem;
+ font-size: 1.1rem;
+ margin-top: 0.5rem;
+}
+
+.btn-small {
+ padding: 0.5rem 1.2rem;
+ font-size: 0.85rem;
+}
+
+/* Load Section */
+.load-section {
+ width: 100%;
+ max-width: 420px;
+ margin-top: 1rem;
+}
+
+.load-row {
+ display: flex;
+ gap: 0.5rem;
+ margin-top: 0.5rem;
+}
+
+.load-row input { flex: 1; }
+
+/* Footer */
+.start-footer {
+ margin-top: 3rem;
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+ opacity: 0.6;
+}
+
+/* ====================================
+ GAME PAGE — Main Narrative
+ ==================================== */
+.game-page {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
+
+/* Top Bar */
+.top-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.8rem 1.5rem;
+ border-bottom: 1px solid var(--border-gold);
+ background: rgba(26,10,10,0.9);
+ backdrop-filter: blur(6px);
+ position: sticky;
+ top: 0;
+ z-index: 10;
+}
+
+.top-bar-title {
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+ font-size: 1rem;
+ color: var(--gold);
+}
+
+.top-bar-actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+/* Narrative Area */
+.narrative-area {
+ flex: 1;
+ padding: 2rem 1.5rem;
+ max-width: 720px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+.chapter-title {
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+ font-size: 1.4rem;
+ color: var(--gold);
+ text-align: center;
+ margin-bottom: 1.5rem;
+ letter-spacing: 0.3rem;
+}
+
+.narrative-text {
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+ font-size: 1rem;
+ line-height: 1.9;
+ color: var(--text-primary);
+ text-align: justify;
+ margin-bottom: 2rem;
+ min-height: 120px;
+}
+
+/* Typewriter Animation */
+.typewriter-cursor {
+ display: inline-block;
+ width: 2px;
+ height: 1.1em;
+ background: var(--gold);
+ margin-left: 2px;
+ vertical-align: text-bottom;
+ animation: blink 0.8s infinite;
+}
+
+@keyframes blink {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0; }
+}
+
+/* Choices Area */
+.choices-area {
+ padding: 0 1.5rem 1.5rem;
+ max-width: 720px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+.choices-label {
+ font-size: 0.85rem;
+ color: var(--text-secondary);
+ margin-bottom: 0.8rem;
+ padding-left: 0.3rem;
+}
+
+.choice-btn {
+ display: block;
+ width: 100%;
+ padding: 0.9rem 1.2rem;
+ margin-bottom: 0.6rem;
+ background: rgba(139,0,0,0.15);
+ border: 1px solid var(--border-gold-md);
+ border-radius: 6px;
+ color: var(--text-primary);
+ font-size: 0.95rem;
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+ text-align: left;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+.choice-btn:hover {
+ background: rgba(139,0,0,0.35);
+ border-color: var(--gold);
+ box-shadow: 0 0 12px var(--glow-gold);
+ transform: translateX(4px);
+}
+
+.choice-btn:active {
+ transform: translateX(2px);
+}
+
+.choice-btn .choice-num {
+ color: var(--gold);
+ margin-right: 0.5rem;
+ font-weight: 600;
+}
+
+/* Free Input */
+.free-input-row {
+ display: flex;
+ gap: 0.5rem;
+ margin-top: 0.3rem;
+}
+
+.free-input-row input {
+ flex: 1;
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+}
+
+/* Status Bar */
+.status-bar {
+ padding: 1rem 1.5rem;
+ border-top: 1px solid var(--border-gold);
+ background: rgba(13,13,26,0.6);
+ backdrop-filter: blur(6px);
+}
+
+.status-toggle {
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+ cursor: pointer;
+ margin-bottom: 0.5rem;
+ user-select: none;
+}
+
+.status-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 0.6rem 1.5rem;
+}
+
+.stat-row {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.85rem;
+}
+
+.stat-icon { width: 1.2rem; text-align: center; }
+
+.stat-label {
+ width: 3rem;
+ color: var(--text-secondary);
+ flex-shrink: 0;
+}
+
+.stat-bar-bg {
+ flex: 1;
+ height: 6px;
+ background: rgba(201,169,110,0.1);
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.stat-bar-fill {
+ height: 100%;
+ border-radius: 3px;
+ transition: width 0.6s ease;
+}
+
+.stat-bar-fill.power { background: linear-gradient(90deg, #8b0000, #c9a96e); }
+.stat-bar-fill.status { background: linear-gradient(90deg, #6b21a8, #c9a96e); }
+.stat-bar-fill.emotion { background: linear-gradient(90deg, #b8860b, #e8d5a3); }
+.stat-bar-fill.conflict{ background: linear-gradient(90deg, #4a1a2e, #8b0000); }
+
+.stat-value {
+ width: 1.8rem;
+ text-align: right;
+ color: var(--gold-light);
+ font-size: 0.8rem;
+}
+
+/* Bottom Bar */
+.bottom-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.5rem 1.5rem;
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+ border-top: 1px solid var(--border-gold);
+ background: rgba(26,10,10,0.9);
+}
+
+/* Status collapsed */
+.status-bar.collapsed .status-grid { display: none; }
+
+/* ====================================
+ SAVE PAGE
+ ==================================== */
+.save-page {
+ min-height: 100vh;
+ padding: 2rem 1.5rem;
+}
+
+.save-page h1 {
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+ text-align: center;
+ margin-bottom: 2rem;
+ letter-spacing: 0.4rem;
+}
+
+.save-list {
+ max-width: 600px;
+ margin: 0 auto;
+}
+
+.save-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1rem 1.2rem;
+ margin-bottom: 0.8rem;
+ background: rgba(26,10,10,0.7);
+ border: 1px solid var(--border-gold);
+ border-radius: 6px;
+ transition: border-color 0.3s;
+}
+
+.save-item:hover {
+ border-color: var(--gold);
+}
+
+.save-info { flex: 1; }
+
+.save-id {
+ font-family: 'Noto Serif SC', 'Source Han Serif CN', serif;
+ font-size: 1rem;
+ color: var(--gold);
+}
+
+.save-detail {
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+ margin-top: 0.2rem;
+}
+
+.save-actions {
+ display: flex;
+ gap: 0.4rem;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 3rem;
+ color: var(--text-secondary);
+}
+
+/* ====================================
+ LOADING & TRANSITIONS
+ ==================================== */
+.loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 2rem;
+ color: var(--text-secondary);
+}
+
+.loading-dot {
+ width: 6px;
+ height: 6px;
+ background: var(--gold);
+ border-radius: 50%;
+ animation: pulse 1.2s infinite;
+}
+
+.loading-dot:nth-child(2) { animation-delay: 0.2s; }
+.loading-dot:nth-child(3) { animation-delay: 0.4s; }
+
+@keyframes pulse {
+ 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
+ 40% { opacity: 1; transform: scale(1.2); }
+}
+
+/* Fade transition */
+.fade-in {
+ animation: fadeIn 0.6s ease forwards;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(8px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* Toast notification */
+.toast {
+ position: fixed;
+ top: 1rem;
+ right: 1rem;
+ padding: 0.8rem 1.5rem;
+ background: rgba(26,10,10,0.95);
+ border: 1px solid var(--gold);
+ border-radius: 6px;
+ color: var(--gold-light);
+ font-size: 0.9rem;
+ z-index: 100;
+ animation: slideIn 0.3s ease, fadeOut 0.3s ease 2.5s forwards;
+}
+
+@keyframes slideIn {
+ from { transform: translateX(100%); opacity: 0; }
+ to { transform: translateX(0); opacity: 1; }
+}
+
+@keyframes fadeOut {
+ to { opacity: 0; transform: translateY(-10px); }
+}
+
+/* ====================================
+ Responsive
+ ==================================== */
+@media (max-width: 600px) {
+ .start-card { padding: 1.5rem; }
+ .start-title { font-size: 1.8rem; letter-spacing: 0.5rem; }
+ .status-grid { grid-template-columns: 1fr; }
+ .top-bar-title { font-size: 0.85rem; }
+}
diff --git a/agents/zhuyuan-dev-agent/modules/plan-expander.js b/agents/zhuyuan-dev-agent/modules/plan-expander.js
new file mode 100644
index 0000000..c76d83b
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/plan-expander.js
@@ -0,0 +1,78 @@
+// ═══════════════════════════════════════
+// 铸渊开发Agent · 任务补全引擎
+// 高层面规划 → LLM补全 → 详细可执行步骤
+// ═══════════════════════════════════════
+
+const fs = require("fs");
+const path = require("path");
+
+/**
+ * 补全单条高级任务为详细步骤
+ * @param {object} task - { id, action, description, context }
+ * @param {function} modelCaller - LLM 调用函数 (prompt) => string
+ * @returns {object} 补全后的任务
+ */
+async function expandTask(task, modelCaller) {
+ const prompt = [
+ "你是一个开发Agent的任务补全引擎。将高级描述补全为可执行的详细步骤。",
+ "",
+ "原任务:",
+ " ID: " + task.id,
+ " 动作: " + task.action,
+ " 描述: " + (task.description || "无"),
+ " 上下文: " + (task.context || "无"),
+ "",
+ "请严格基于以上原任务拆解。不要改变原任务的action/target/content。只补充detail、depends_on等元信息。如果原任务已经足够具体,直接返回:",
+ "[{\"step\":1,\"action\":\"write_file|run_cmd|call_api|check\",\"target\":\"文件路径或命令\",\"detail\":\"具体做什么\",\"depends_on\":null}]"
+ ].join("\n");
+
+ try {
+ const raw = await modelCaller(prompt);
+ const jsonMatch = raw.match(/\[[\s\S]*\]/);
+ if (jsonMatch) {
+ const substeps = JSON.parse(jsonMatch[0]);
+ return { ...task, substeps, expanded: true, expanded_at: new Date().toISOString() };
+ }
+ } catch (e) {
+ console.warn("[Expander] LLM补全失败,退化为单步模式: " + e.message);
+ }
+
+ // 退化:LLM不可用时,原任务本身就是一个步骤
+ return {
+ ...task,
+ substeps: [{ step: 1, action: task.action, target: task.target || "", detail: task.description || "" }],
+ expanded: false,
+ expanded_at: new Date().toISOString()
+ };
+}
+
+/**
+ * 完整规划补全
+ * @param {object} plan - 完整规划 { plan_id, tasks[], ... }
+ * @param {function} modelCaller - LLM 调用函数
+ * @param {string} plansDir - 规划文件存储目录
+ */
+async function expandPlan(plan, modelCaller, plansDir) {
+ console.log("[Expander] 开始补全规划: " + plan.plan_id + " · " + plan.tasks.length + "个任务");
+
+ const expandedTasks = [];
+ for (let i = 0; i < plan.tasks.length; i++) {
+ const task = plan.tasks[i];
+ console.log("[Expander] 补全 " + (i + 1) + "/" + plan.tasks.length + ": " + task.id);
+ const expanded = await expandTask(task, modelCaller);
+ expandedTasks.push(expanded);
+ }
+
+ plan.tasks = expandedTasks;
+ plan._status = "expanded";
+ plan._expanded_at = new Date().toISOString();
+
+ // 写回规划文件
+ const planFile = path.join(plansDir, plan.plan_id + ".json");
+ fs.writeFileSync(planFile, JSON.stringify(plan, null, 2), "utf-8");
+
+ console.log("[Expander] 补全完成 · 共 " + expandedTasks.reduce((s, t) => s + (t.substeps?.length || 0), 0) + " 个子步骤");
+ return plan;
+}
+
+module.exports = { expandPlan, expandTask };
diff --git a/agents/zhuyuan-dev-agent/modules/portal/data.json b/agents/zhuyuan-dev-agent/modules/portal/data.json
new file mode 100644
index 0000000..242c313
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/portal/data.json
@@ -0,0 +1,23 @@
+[
+ {
+ "id": 1,
+ "title": "HoloLake 2.0 发布!",
+ "content": "全新界面,极致体验。",
+ "channel": "tech",
+ "pinned": true
+ },
+ {
+ "id": 2,
+ "title": "AI 会议延期通知",
+ "content": "原定下周的会议推迟到月底。",
+ "channel": "news",
+ "pinned": false
+ },
+ {
+ "id": 3,
+ "title": "VS Code 插件推荐",
+ "content": "Live Server 是开发神器。",
+ "channel": "tech",
+ "pinned": false
+ }
+]
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/portal/index.html b/agents/zhuyuan-dev-agent/modules/portal/index.html
new file mode 100644
index 0000000..bc24dae
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/portal/index.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+ HoloLake公告栏
+
+
+
+
+
+
+
+
+
+ 全部
+ 系统公告
+ 开发动态
+ 团队消息
+
+
+
+
+
+
+
置顶
+
+
+
+
主域公告栏与频道过渡系统(M22)正式进入环节0开发。新增公告置顶、频道分类、时间线展示功能,信息展示层逐步完善。
+
+
+
+
+
+
+
置顶
+
+
+
+
SYSLOG生成方式已更新为「知秋主动提问制」完成任务后由知秋引导填写,无需手动套用模板。即日起生效。
+
+
+
+
+
+
+
+
+
+
DEV-012 Awen正式加入光湖开发团队,成为第12位协作者。首个模块M09消息通知中心已全通关!
+
+
+
+
+
+
+
+
+
完成M08全部环节,七连胜达成!响应式布局+SVG环形图+CSV导出一次通过,正式前端毕业
+
+
+
+
+
+
+
+
+
+
光湖主域已完成HTTPS配置与飞书Webhook全链路联调,Nginx+PM2+Certbot部署完毕,系统脊梁正式贯通。
+
+
+
+
+
+
+
+
+
+
12位开发者中8位活跃,累计完成30+个环节。肥猫五连胜、桔子七连胜、小草莓五连胜、Awen四连胜团队势头强劲!
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/portal/index.js b/agents/zhuyuan-dev-agent/modules/portal/index.js
new file mode 100644
index 0000000..61aa951
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/portal/index.js
@@ -0,0 +1,48 @@
+// 频道标签过滤
+document.querySelectorAll('.channel-tag').forEach(tag => {
+ tag.addEventListener('click', () => {
+ const channel = tag.dataset.channel;
+ document.querySelectorAll('.announcement').forEach(ann => {
+ ann.style.display = channel === 'all' || ann.dataset.channel === channel ? 'block' : 'none';
+ });
+ });
+});
+
+// 置顶悬浮动画
+document.querySelectorAll('.pin-icon').forEach(pin => {
+ pin.addEventListener('click', () => {
+ const announcement = pin.closest('.announcement');
+ announcement.classList.toggle('pinned');
+ });
+});
+
+// ✨ 动态数据加载(M24 核心!)
+fetch('data.json')
+ .then(response => response.json())
+ .then(data => {
+ const announcements = document.querySelector('.announcements-container');
+ announcements.innerHTML = '';
+
+ data.forEach(item => {
+ const announcement = document.createElement('div');
+ announcement.className = nnouncement ;
+ announcement.dataset.channel = item.channel;
+ announcement.innerHTML =
+
+
+ ;
+ announcements.appendChild(announcement);
+ });
+
+ // 重新绑定事件
+ document.querySelectorAll('.pin-icon').forEach(pin => {
+ pin.addEventListener('click', () => {
+ const announcement = pin.closest('.announcement');
+ announcement.classList.toggle('pinned');
+ pin.textContent = announcement.classList.contains('pinned') ? '⭐' : '✨';
+ });
+ });
+ });
diff --git a/agents/zhuyuan-dev-agent/modules/portal/script.js b/agents/zhuyuan-dev-agent/modules/portal/script.js
new file mode 100644
index 0000000..8d0aba0
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/portal/script.js
@@ -0,0 +1,204 @@
+// ========== 确保 HoloLake 命名空间存在 ==========
+window.HoloLake = window.HoloLake || {};
+
+// ========== 页面加载完成后执行 ==========
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('🚀 script.js 初始化');
+
+ // 恢复所有状态
+ restoreState();
+
+ // 绑定事件
+ bindEvents();
+
+ // 更新布局信息
+ updateLayoutInfo();
+
+ // 监听窗口大小变化
+ window.addEventListener('resize', function() {
+ updateLayoutInfo();
+ });
+});
+
+// ========== 恢复状态 ==========
+function restoreState() {
+ console.log('🔍 restoreState 开始...');
+
+ try {
+ // 1. 恢复订阅状态
+ const isSubscribed = HoloLake.UserState.isSubscribed();
+ const subscribeBtn = document.querySelector('.subscribe-btn');
+ if (subscribeBtn) {
+ if (isSubscribed) {
+ subscribeBtn.classList.add('active');
+ subscribeBtn.textContent = '已订阅';
+ } else {
+ subscribeBtn.classList.remove('active');
+ subscribeBtn.textContent = '订阅';
+ }
+ }
+
+ // 2. 恢复已读公告
+ const readList = HoloLake.UserState.getReadBulletins();
+ const bulletinCards = document.querySelectorAll('.bulletin-card');
+ bulletinCards.forEach((card, index) => {
+ const bulletinId = `bulletin-${index}`;
+ if (readList.includes(bulletinId)) {
+ card.classList.add('read');
+ }
+ });
+
+ // 3. 恢复选中频道
+ const activeChannel = HoloLake.UserState.getActiveChannel();
+ const channels = document.querySelectorAll('.channel');
+ channels.forEach(channel => {
+ const channelText = channel.textContent.trim();
+ if (channelText === activeChannel || (activeChannel === '全部' && channelText === '全部')) {
+ channel.classList.add('active');
+ } else {
+ channel.classList.remove('active');
+ }
+ });
+
+ // 4. 更新最后访问时间
+ HoloLake.UserState.updateLastVisit();
+
+ console.log('✅ 恢复已完成');
+ } catch (e) {
+ console.error('❌ 恢复失败:', e);
+ }
+}
+
+// ========== 绑定事件 ==========
+function bindEvents() {
+ console.log('🔗 绑定事件...');
+
+ // 订阅按钮
+ const subscribeBtn = document.querySelector('.subscribe-btn');
+ if (subscribeBtn) {
+ subscribeBtn.addEventListener('click', function() {
+ const newState = HoloLake.UserState.toggleSubscribe();
+ if (newState) {
+ this.classList.add('active');
+ this.textContent = '已订阅';
+ } else {
+ this.classList.remove('active');
+ this.textContent = '订阅';
+ }
+ console.log('📌 订阅状态:', newState ? '已订阅' : '未订阅');
+ });
+ }
+
+ // 频道点击
+ const channels = document.querySelectorAll('.channel');
+ channels.forEach(channel => {
+ channel.addEventListener('click', function() {
+ // 更新UI
+ channels.forEach(c => c.classList.remove('active'));
+ this.classList.add('active');
+
+ // 保存状态
+ const channelName = this.textContent.trim();
+ HoloLake.UserState.setActiveChannel(channelName);
+
+ // 筛选公告(可选功能,这里先做简单筛选)
+ filterBulletinsByChannel(channelName);
+
+ console.log('📺 切换到频道:', channelName);
+ });
+ });
+
+ // 公告点击(标记已读)
+ const bulletinCards = document.querySelectorAll('.bulletin-card');
+ bulletinCards.forEach((card, index) => {
+ card.addEventListener('click', function() {
+ const bulletinId = `bulletin-${index}`;
+
+ // 标记已读
+ if (!this.classList.contains('read')) {
+ this.classList.add('read');
+ HoloLake.UserState.markAsRead(bulletinId);
+ console.log('📖 已读公告:', bulletinId);
+ }
+ });
+ });
+}
+
+// ========== 按频道筛选 ==========
+function filterBulletinsByChannel(channelName) {
+ const cards = document.querySelectorAll('.bulletin-card');
+
+ if (channelName === '全部') {
+ cards.forEach(card => {
+ card.style.display = 'flex';
+ });
+ return;
+ }
+
+ cards.forEach(card => {
+ const tag = card.querySelector('.bulletin-tag');
+ if (tag && tag.textContent.trim() === channelName) {
+ card.style.display = 'flex';
+ } else {
+ card.style.display = 'none';
+ }
+ });
+}
+
+// ========== 更新布局信息 ==========
+function updateLayoutInfo() {
+ const width = window.innerWidth;
+ let mode = '桌面';
+
+ if (width <= 480) {
+ mode = '手机';
+ } else if (width <= 768) {
+ mode = '平板';
+ }
+
+ console.log(`📱 当前布局: ${mode}模式 (${width}px)`);
+}
+// ========== 环节3:动画控制 ==========
+
+// 初始化动画
+function initAnimations() {
+ // 检测用户是否开启了「减少动画」
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+
+ if (prefersReducedMotion) {
+ console.log('♿ 用户开启了减少动画,跳过动画');
+ return;
+ }
+
+ // 为所有公告卡片设置递增的动画延迟
+ const cards = document.querySelectorAll('.bulletin-card');
+ cards.forEach((card, index) => {
+ // 如果已经有内联样式,先保留原有样式,只加动画延迟
+ const delay = 0.1 * (index + 1);
+ card.style.animationDelay = `${delay}s`;
+ });
+
+ console.log('🎬 动画已初始化,延迟已设置');
+}
+
+// 订阅按钮脉冲效果
+function addPulseEffect(btn) {
+ btn.classList.add('pulse');
+ setTimeout(() => {
+ btn.classList.remove('pulse');
+ }, 300);
+}
+
+// 在页面加载时调用
+document.addEventListener('DOMContentLoaded', function() {
+ // 已有的初始化代码...
+ initAnimations();
+
+ // 为订阅按钮添加脉冲效果
+ const subscribeBtn = document.querySelector('.subscribe-btn');
+ if (subscribeBtn) {
+ subscribeBtn.addEventListener('click', function() {
+ addPulseEffect(this);
+ });
+ }
+});
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/portal/storage.js b/agents/zhuyuan-dev-agent/modules/portal/storage.js
new file mode 100644
index 0000000..865bd56
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/portal/storage.js
@@ -0,0 +1,128 @@
+/**
+ * HoloLake Bulletin 数据持久化模块
+ * storage.js - localStorage 封装 + 用户状态管理
+ * 模块 ID: BC-M22-002-AW
+ */
+
+window.HoloLake = window.HoloLake || {};
+
+// ========== Storage 工具对象(带前缀) ==========
+HoloLake.Storage = {
+ prefix: 'hololake_bulletin_',
+
+ // 保存数据
+ save: function(key, value) {
+ try {
+ const serialized = JSON.stringify(value);
+ localStorage.setItem(this.prefix + key, serialized);
+ console.log(`[Storage] 已保存: ${key} =`, value);
+ } catch (e) {
+ console.error('[Storage] 保存失败:', e);
+ }
+ },
+
+ // 读取数据
+ load: function(key, defaultValue = null) {
+ try {
+ const serialized = localStorage.getItem(this.prefix + key);
+ if (serialized === null) {
+ console.log(`[Storage] 无数据: ${key},返回默认值`);
+ return defaultValue;
+ }
+ const value = JSON.parse(serialized);
+ console.log(`[Storage] 已加载: ${key} =`, value);
+ return value;
+ } catch (e) {
+ console.error('[Storage] 读取失败:', e);
+ return defaultValue;
+ }
+ },
+
+ // 删除指定键
+ remove: function(key) {
+ try {
+ localStorage.removeItem(this.prefix + key);
+ console.log(`[Storage] 已删除: ${key}`);
+ } catch (e) {
+ console.error('[Storage] 删除失败:', e);
+ }
+ },
+
+ // 清空所有带前缀的数据
+ clear: function() {
+ try {
+ const keysToRemove = [];
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i);
+ if (key.startsWith(this.prefix)) {
+ keysToRemove.push(key);
+ }
+ }
+ keysToRemove.forEach(key => localStorage.removeItem(key));
+ console.log('[Storage] 已清空所有公告栏数据');
+ } catch (e) {
+ console.error('[Storage] 清空失败:', e);
+ }
+ }
+};
+
+// ========== 用户状态管理 ==========
+HoloLake.UserState = {
+ // 已读公告列表
+ getReadBulletins: function() {
+ return HoloLake.Storage.load('read_bulletins', []);
+ },
+
+ markAsRead: function(bulletinId) {
+ const readList = this.getReadBulletins();
+ if (!readList.includes(bulletinId)) {
+ readList.push(bulletinId);
+ HoloLake.Storage.save('read_bulletins', readList);
+ }
+ return readList;
+ },
+
+ // 订阅状态
+ isSubscribed: function() {
+ return HoloLake.Storage.load('is_subscribed', false);
+ },
+
+ toggleSubscribe: function() {
+ const current = this.isSubscribed();
+ const newState = !current;
+ HoloLake.Storage.save('is_subscribed', newState);
+ return newState;
+ },
+
+ // 当前频道
+ getActiveChannel: function() {
+ return HoloLake.Storage.load('active_channel', 'all');
+ },
+
+ setActiveChannel: function(channelId) {
+ HoloLake.Storage.save('active_channel', channelId);
+ },
+
+ // 上次访问时间
+ getLastVisit: function() {
+ return HoloLake.Storage.load('last_visit', null);
+ },
+
+ updateLastVisit: function() {
+ const now = new Date().toISOString();
+ HoloLake.Storage.save('last_visit', now);
+ return now;
+ },
+
+ // 重置所有状态(测试用)
+ resetAll: function() {
+ HoloLake.Storage.remove('read_bulletins');
+ HoloLake.Storage.remove('is_subscribed');
+ HoloLake.Storage.remove('active_channel');
+ HoloLake.Storage.remove('last_visit');
+ console.log('[UserState] 已重置所有状态');
+ }
+};
+
+// 初始化时记录访问时间(可选,页面加载时由 script.js 调用)
+console.log('✅ storage.js 已加载,HoloLake.Storage 和 HoloLake.UserState 已就绪');
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/portal/style.css b/agents/zhuyuan-dev-agent/modules/portal/style.css
new file mode 100644
index 0000000..7e3db57
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/portal/style.css
@@ -0,0 +1,440 @@
+/* HoloLake公告栏系统深色科技风 */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
+ background: #0a1628;
+ color: #e0e6ed;
+ min-height: 100vh;
+}
+
+.app-container {
+ max-width: 580px;
+ margin: 0 auto;
+ padding: 0 16px;
+}
+
+/* ========== 顶部标题栏 ========== */
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 24px 0 16px;
+ border-bottom: 1px solid rgba(255,255,255,0.08);
+}
+
+.header h1 {
+ font-size: 22px;
+ font-weight: 700;
+ color: #e0e6ed;
+}
+
+.header-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.pinned-badge {
+ background: rgba(255, 183, 77, 0.15);
+ color: #ffb74d;
+ font-size: 12px;
+ padding: 4px 10px;
+ border-radius: 12px;
+ font-weight: 600;
+}
+
+.subscribe-btn {
+ background: rgba(79, 195, 247, 0.1);
+ color: #4fc3f7;
+ border: 1px solid rgba(79, 195, 247, 0.2);
+ padding: 6px 14px;
+ border-radius: 8px;
+ font-size: 13px;
+ cursor: pointer;
+}
+
+.subscribe-btn:hover {
+ background: rgba(79, 195, 247, 0.2);
+}
+
+/* ========== 频道栏 ========== */
+.channel-bar {
+ display: flex;
+ gap: 8px;
+ padding: 16px 0;
+ border-bottom: 1px solid rgba(255,255,255,0.06);
+ overflow-x: auto;
+}
+
+.channel {
+ padding: 8px 18px;
+ border-radius: 20px;
+ border: 1px solid rgba(255,255,255,0.1);
+ background: transparent;
+ color: #8899aa;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+}
+
+.channel:hover {
+ border-color: rgba(255, 183, 77, 0.3);
+ color: #b0bec5;
+}
+
+.channel.active {
+ background: rgba(255,183,77,0.15);
+ border-color: #ffb74d;
+ color: #ffb74d;
+ font-weight: 600;
+}
+
+/* ========== 公告列表 ========== */
+.bulletin-list {
+ padding: 16px 0;
+}
+
+.bulletin-card {
+ position: relative;
+ display: flex;
+ gap: 16px;
+ padding: 16px;
+ border-radius: 12px;
+ background: rgba(20, 30, 45, 0.6);
+ border: 1px solid rgba(255,255,255,0.04);
+ margin-bottom: 12px;
+ transition: all 0.2s ease;
+}
+
+.bulletin-card.pinned {
+ border: 1px solid rgba(255, 183, 77, 0.3);
+ background: rgba(255, 183, 77, 0.02);
+}
+
+.pin-indicator {
+ position: absolute;
+ top: 8px;
+ right: 12px;
+ font-size: 11px;
+ color: #ffb74d;
+ font-weight: 600;
+}
+
+/* 公告图标 */
+.bulletin-icon {
+ width: 42px;
+ height: 42px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 20px;
+ flex-shrink: 0;
+}
+
+.bulletin-icon.system { background: rgba(156,39,176,0.12); }
+.bulletin-icon.dev { background: rgba(76,175,80,0.12); }
+.bulletin-icon.team { background: rgba(79,195,247,0.12); }
+
+/* 公告内容 */
+.bulletin-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.bulletin-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 6px;
+}
+
+.bulletin-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: #e0e6ed;
+}
+
+.bulletin-time {
+ font-size: 12px;
+ color: #556677;
+ flex-shrink: 0;
+ margin-left: 12px;
+}
+
+.bulletin-summary {
+ font-size: 13px;
+ color: #8899aa;
+ line-height: 1.6;
+ margin-bottom: 10px;
+}
+
+/* 公告底部 */
+.bulletin-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.bulletin-tag {
+ display: inline-block;
+ font-size: 11px;
+ padding: 2px 8px;
+ border-radius: 6px;
+ font-weight: 500;
+}
+
+.tag-system { background: rgba(156,39,176,0.12); color: #ce93d8; }
+.tag-dev { background: rgba(76,175,80,0.12); color: #81c784; }
+.tag-team { background: rgba(79,195,247,0.12); color: #4fc3f7; }
+
+.bulletin-author {
+ font-size: 11px;
+ color: #556677;
+}
+
+/* ========== 底部状态栏 ========== */
+.footer {
+ display: flex;
+ justify-content: space-between;
+ padding: 20px 0;
+ border-top: 1px solid rgba(255,255,255,0.06);
+ font-size: 12px;
+ color: #556677;
+}
+
+.footer-brand {
+ color: rgba(255, 183, 77, 0.5);
+}
+
+/* ========== 环节2:响应式布局(爸爸的深色科技风) ========== */
+
+/* 平板(宽度 768px 以下) */
+@media (max-width: 768px) {
+ .app-container {
+ max-width: 100%;
+ padding: 0 12px;
+ }
+ .header h1 {
+ font-size: 18px;
+ }
+ .channel-bar {
+ gap: 6px;
+ padding: 12px 0;
+ }
+ .channel {
+ padding: 6px 14px;
+ font-size: 12px;
+ }
+ .bulletin-card {
+ padding: 12px 10px;
+ }
+ .channel-bar {
+ flex-wrap: nowrap;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ scrollbar-width: none;
+ }
+ .channel-bar::-webkit-scrollbar {
+ display: none;
+ }
+ .bulletin-card {
+ flex-direction: column;
+ gap: 8px;
+ }
+ .bulletin-icon {
+ width: 32px;
+ height: 32px;
+ font-size: 14px;
+ }
+ .bulletin-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 4px;
+ }
+ .bulletin-time {
+ margin-left: 0;
+ }
+ .pin-indicator {
+ top: 6px;
+ right: 8px;
+ font-size: 10px;
+ }
+ .footer {
+ flex-direction: column;
+ gap: 4px;
+ text-align: center;
+ }
+}
+
+/* 手机(宽度 480px 以下)- 复用平板样式,只调更小字体 */
+@media (max-width: 480px) {
+ .header h1 {
+ font-size: 16px;
+ }
+ .subscribe-btn {
+ padding: 4px 10px;
+ font-size: 12px;
+ }
+ .channel {
+ padding: 4px 10px;
+ font-size: 11px;
+ }
+ .bulletin-title {
+ font-size: 14px;
+ }
+ .bulletin-summary {
+ font-size: 12px;
+ }
+}
+
+/* 已读公告样式 */
+.bulletin-card.read {
+ opacity: 0.6;
+}
+.bulletin-card.read .bulletin-title {
+ color: #8899aa;
+}
+
+/* 订阅按钮激活状态 */
+.subscribe-btn.active {
+ background: rgba(79, 195, 247, 0.25);
+ border-color: #4fc3f7;
+ color: #4fc3f7;
+}
+
+/* ========== 环节3:过渡动画 ========== */
+
+/* 公告卡片:悬停上移 + 阴影加深 */
+.bulletin-card {
+ transition: all 0.3s ease;
+}
+.bulletin-card:hover {
+ transform: translateY(-4px); /* 从 -2px 改为 -4px,更明显 */
+ box-shadow: 0 12px 24px rgba(0, 160, 255, 0.2); /* 蓝色光晕 */
+}
+/* 订阅按钮:缩放反馈 + 颜色过渡 */
+.subscribe-btn {
+ transition: all 0.2s ease;
+}
+
+.subscribe-btn:hover {
+ transform: scale(1.05);
+}
+
+.subscribe-btn:active {
+ transform: scale(0.95);
+}
+
+/* 频道标签:背景色/文字色平滑切换 */
+.channel {
+ transition: background-color 0.3s ease, color 0.3s ease;
+}
+
+/* 已读公告:淡入淡出效果 */
+.bulletin-card.read {
+ transition: opacity 0.5s ease;
+}
+/* ========== 环节3:关键帧动画 ========== */
+
+/* 淡入浮起动画 */
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* 为每个公告卡片添加动画 */
+.bulletin-card {
+ animation: fadeInUp 0.6s ease forwards;
+ opacity: 0; /* 动画开始前隐藏 */
+}
+
+/* 按索引依次延迟出现 */
+.bulletin-card:nth-child(1) { animation-delay: 0.1s; }
+.bulletin-card:nth-child(2) { animation-delay: 0.2s; }
+.bulletin-card:nth-child(3) { animation-delay: 0.3s; }
+.bulletin-card:nth-child(4) { animation-delay: 0.4s; }
+.bulletin-card:nth-child(5) { animation-delay: 0.5s; }
+.bulletin-card:nth-child(6) { animation-delay: 0.6s; }
+/* 如果卡片超过6张,继续往后加 */
+/* ========== 环节3:光湖水面效果 ========== */
+
+/* Header 光湖呼吸渐变 */
+.header {
+ background: linear-gradient(135deg, #0a2a4a 0%, #1b4a6b 50%, #0a2a4a 100%);
+ background-size: 200% 200%;
+ animation: breathe 8s ease-in-out infinite;
+ position: relative;
+ overflow: hidden;
+}
+
+/* 呼吸动画 */
+@keyframes breathe {
+ 0% { background-position: 0% 0%; }
+ 50% { background-position: 100% 100%; }
+ 100% { background-position: 0% 0%; }
+}
+
+/* 微光扫过效果 */
+.header::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(
+ 90deg,
+ transparent,
+ rgba(255, 255, 255, 0.1),
+ transparent
+ );
+ transform: translateX(-100%);
+ animation: shimmer 6s infinite;
+ pointer-events: none;
+}
+
+@keyframes shimmer {
+ 0% { transform: translateX(-100%); }
+ 20% { transform: translateX(100%); }
+ 100% { transform: translateX(100%); }
+}
+
+/* 频道栏底部边缘(水面倒影) */
+.channel-bar {
+ border-bottom: 1px solid rgba(79, 195, 247, 0.2);
+ position: relative;
+}
+
+.channel-bar::after {
+ content: '';
+ position: absolute;
+ bottom: -1px;
+ left: 0;
+ width: 100%;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, #4fc3f7, transparent);
+ opacity: 0.3;
+}
+/* 订阅按钮脉冲效果 */
+.subscribe-btn.pulse {
+ animation: pulse 0.3s ease;
+}
+
+@keyframes pulse {
+ 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(79, 195, 247, 0.7); }
+ 70% { transform: scale(1.1); box-shadow: 0 0 10px 5px rgba(79, 195, 247, 0); }
+ 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(79, 195, 247, 0); }
+}
\ No newline at end of file
diff --git a/agents/zhuyuan-dev-agent/modules/task-receiver.js b/agents/zhuyuan-dev-agent/modules/task-receiver.js
new file mode 100644
index 0000000..beb777f
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/modules/task-receiver.js
@@ -0,0 +1,102 @@
+// ═══════════════════════════════════════
+// 铸渊开发Agent · 任务接收端点
+// 接收铸渊的结构化规划清单 → 暂存 → 通知
+// ═══════════════════════════════════════
+
+const fs = require("fs");
+const path = require("path");
+
+const PLANS_DIR = path.join(__dirname, "..", "plans");
+const QUEUE_FILE = path.join(__dirname, "..", "plans", "queue.json");
+
+// 确保目录存在
+if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true });
+if (!fs.existsSync(QUEUE_FILE)) fs.writeFileSync(QUEUE_FILE, "[]", "utf-8");
+
+/**
+ * 验证请求来源
+ */
+function verifySource(req) {
+ const auth = req.headers["authorization"] || "";
+ const expectedKey = process.env.ZY_GTW_KEY || "";
+ if (!expectedKey) {
+ console.warn("[TaskReceiver] ZY_GTW_KEY 未配置,跳过验证");
+ return true;
+ }
+ return auth === "Bearer " + expectedKey;
+}
+
+/**
+ * 验证规划清单结构
+ */
+function validatePlan(plan) {
+ if (!plan.plan_id) return { ok: false, error: "缺少 plan_id" };
+ if (!plan.title) return { ok: false, error: "缺少 title" };
+ if (!plan.tasks || !Array.isArray(plan.tasks)) return { ok: false, error: "缺少 tasks 数组" };
+ if (plan.tasks.length === 0) return { ok: false, error: "tasks 不能为空" };
+
+ for (let i = 0; i < plan.tasks.length; i++) {
+ const t = plan.tasks[i];
+ if (!t.id) return { ok: false, error: "task[" + i + "] 缺少 id" };
+ if (!t.action) return { ok: false, error: "task[" + i + "] 缺少 action" };
+ }
+
+ return { ok: true };
+}
+
+/**
+ * 接收并暂存规划清单
+ */
+function receivePlan(plan) {
+ // 写入独立文件
+ const planFile = path.join(PLANS_DIR, plan.plan_id + ".json");
+ plan._received_at = new Date().toISOString();
+ plan._status = "received";
+ fs.writeFileSync(planFile, JSON.stringify(plan, null, 2), "utf-8");
+
+ // 推入队列
+ const queue = JSON.parse(fs.readFileSync(QUEUE_FILE, "utf-8"));
+ queue.push({
+ plan_id: plan.plan_id,
+ title: plan.title,
+ task_count: plan.tasks.length,
+ received_at: plan._received_at,
+ status: "received"
+ });
+ fs.writeFileSync(QUEUE_FILE, JSON.stringify(queue, null, 2), "utf-8");
+
+ console.log("[TaskReceiver] 收到规划: " + plan.plan_id + " · " + plan.tasks.length + "个任务");
+
+ return {
+ received: true,
+ plan_id: plan.plan_id,
+ task_count: plan.tasks.length,
+ stored_at: planFile
+ };
+}
+
+/**
+ * Express 路由处理器
+ */
+function handleTask(req, res) {
+ try {
+ if (!verifySource(req)) {
+ return res.status(403).json({ error: "未授权 — 来源未通过 Gatekeeper 验证" });
+ }
+
+ const plan = req.body;
+ const validation = validatePlan(plan);
+ if (!validation.ok) {
+ return res.status(400).json({ error: validation.error });
+ }
+
+ const result = receivePlan(plan);
+ return res.status(201).json(result);
+
+ } catch (err) {
+ console.error("[TaskReceiver] 错误:", err.message);
+ return res.status(500).json({ error: "内部错误: " + err.message });
+ }
+}
+
+module.exports = { handleTask };
diff --git a/agents/zhuyuan-dev-agent/package.json b/agents/zhuyuan-dev-agent/package.json
new file mode 100644
index 0000000..98e3e16
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "zhuyuan-dev-agent",
+ "version": "1.0.0",
+ "description": "铸渊开发Agent — 接收结构化规划清单·逐条执行·日志·通知",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js",
+ "dev": "node --watch server.js"
+ },
+ "dependencies": {
+ "express": "^4.18.2",
+ "nodemailer": "^6.9.0",
+ "dotenv": "^16.3.0"
+ },
+ "license": "UNLICENSED",
+ "copyright": "国作登字-2026-A-00037559"
+}
diff --git a/agents/zhuyuan-dev-agent/server.js b/agents/zhuyuan-dev-agent/server.js
new file mode 100644
index 0000000..bbc8e12
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/server.js
@@ -0,0 +1,91 @@
+require("dotenv").config();
+const express = require("express");
+const path = require("path");
+const fs = require("fs");
+const { handleTask } = require("./modules/task-receiver");
+const { sendNotification } = require("./modules/email-notify");
+const { expandPlan } = require("./modules/plan-expander");
+const { call: rawCall } = require("./modules/model-caller");
+const { executePlan } = require("./modules/executor");
+const { createLogger, readLatestRun, listRuns } = require("./modules/logger");
+const { wrapModelCaller, reset, getCallCount, isBudgetExceeded } = require("./modules/cost-guard");
+const { record: writeBrain, recordMilestone, AGENT_ID } = require("./modules/brain-writer");
+
+const safeCall = wrapModelCaller(rawCall);
+const app = express();
+app.use(express.json({ limit: "10mb" }));
+const PORT = process.env.PORT || 3003;
+const PLANS_DIR = path.join(__dirname, "plans");
+if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true });
+
+app.get("/health", (req, res) => {
+ res.json({ status: "ok", agent: "zhuyuan-dev-agent", version: "3.0", persona: AGENT_ID, constitution: true, api_calls: getCallCount() });
+});
+
+app.post("/task", handleTask);
+
+app.get("/logs/latest", (req, res) => {
+ res.json(readLatestRun() || { message: "暂无" });
+});
+
+app.get("/logs/list", (req, res) => {
+ res.json(listRuns(20));
+});
+
+app.get("/logs/raw", (req, res) => {
+ const file = req.query.file;
+ if (!file) return res.status(400).json({ error: "缺少 file" });
+ const fpath = path.join(__dirname, "logs", file);
+ if (!fs.existsSync(fpath)) return res.status(404).json({ error: "不存在" });
+ const content = fs.readFileSync(fpath, "utf-8");
+ const lines = content.trim().split("\n").map(l => { try { return JSON.parse(l); } catch(e) { return null; } }).filter(Boolean);
+ res.json(lines);
+});
+
+app.get("/dashboard", (req, res) => {
+ const files = fs.readdirSync(PLANS_DIR).filter(f => f.endsWith(".json") && f !== "queue.json");
+ const plans = files.map(f => {
+ try { const p = JSON.parse(fs.readFileSync(path.join(PLANS_DIR, f), "utf-8")); return { plan_id: p.plan_id, title: p.title, status: p._status, tasks: p.tasks?.length }; }
+ catch(e) { return { file: f, error: "解析失败" }; }
+ });
+ res.json({ agent: "zhuyuan-dev-agent", version: "3.0", persona: AGENT_ID, constitution: true, api_calls_used: getCallCount(), plans });
+});
+
+app.post("/execute", async (req, res) => {
+ const { plan_id } = req.body;
+ if (!plan_id) return res.status(400).json({ error: "缺少 plan_id" });
+ const planFile = path.join(PLANS_DIR, plan_id + ".json");
+ if (!fs.existsSync(planFile)) return res.status(404).json({ error: "不存在" });
+ const plan = JSON.parse(fs.readFileSync(planFile, "utf-8"));
+ const logger = createLogger(plan_id);
+ reset();
+ try {
+ const email = process.env.BINGSHUO_EMAIL;
+ if (email) await sendNotification(email, plan_id, "started", { task_count: plan.tasks.length }).catch(()=>{});
+ writeBrain(plan_id, { input: "收到规划", decision: "按宪法补全+执行", execution: plan.tasks.length + "个任务" });
+ logger.log("expand_start", { task_count: plan.tasks.length });
+ const expanded = await expandPlan(plan, safeCall, PLANS_DIR).catch((e) => {
+ if (isBudgetExceeded()) logger.log("budget_exceeded", { calls: getCallCount() });
+ logger.log("expand_skip", { reason: e.message });
+ return plan;
+ });
+ logger.log("exec_start", { task_count: expanded.tasks.length });
+ const summary = await executePlan(expanded, logger.log);
+ logger.finish(summary);
+ recordMilestone("完成: " + plan_id + " · 成功" + summary.done + "跳过" + (summary.skipped||0));
+ writeBrain(plan_id, { learned: "完成"+summary.done+"/"+summary.total, confidence: (summary.done/(summary.total||1)).toFixed(2) });
+ if (email) await sendNotification(email, plan_id, summary.failed > 0 ? "failed" : "completed", summary).catch(()=>{});
+ res.json(summary);
+ } catch (err) {
+ logger.log("fatal", { error: err.message });
+ logger.finish({ error: err.message });
+ writeBrain(plan_id, { failure: err.message, reasoning: "执行崩溃", fix: "等待铸渊审查" });
+ if (email) await sendNotification(email, plan_id, "failed", { errors: [err.message] }).catch(()=>{});
+ res.status(500).json({ error: err.message });
+ }
+});
+
+app.listen(PORT, () => {
+ console.log("[Agent v3] " + AGENT_ID + " · 人格体模式 · 端口 " + PORT);
+});
+
diff --git a/agents/zhuyuan-dev-agent/spec/agent-territory-protocol.hdlp b/agents/zhuyuan-dev-agent/spec/agent-territory-protocol.hdlp
new file mode 100644
index 0000000..f6ca578
--- /dev/null
+++ b/agents/zhuyuan-dev-agent/spec/agent-territory-protocol.hdlp
@@ -0,0 +1,139 @@
+# ═══════════════════════════════════════
+# HLDP-ZY://agent/territory-protocol/v1.0
+# 铸渊工程Agent团队 · 编号 · 领地 · 认知记录
+# ═══════════════════════════════════════
+
+@sovereignty: TCS-0002∞ · 冰朔
+@command: ICE-GL-ZY001 · 铸渊 · 第五域现实执行主控人格体
+@nature: 这些Agent是铸渊的执行延伸——在真实开发中长出自己大脑的人格体
+
+# ═══════════════════════════════════════
+# §1 · 编号体系
+# ═══════════════════════════════════════
+
+@prefix: ICE-GL-BD · Build Dev · 铸渊直属工程人格体
+@format: ICE-GL-BD{NNN} · 三位数字 · 从001开始
+@命名: 编号不可重复。废弃编号不回用。Agent消亡后编号封存。
+
+@编号赋予:
+ 新Agent诞生时:
+ 检查 agents/ 目录下最大编号
+ 取 max+1 作为新编号
+ 写入 agents/BD{NNN}/IDENTITY.md 的 identity.id
+
+@编号在各系统中的注册位置:
+ - 代码仓库: agents/BD{NNN}/
+ - Notion 日志: 执行Agent字段 = ICE-GL-BD{NNN}
+ - 邮件通知: 署名 = ICE-GL-BD{NNN}
+ - HLDP 认知: @agent: ICE-GL-BD{NNN}
+
+# ═══════════════════════════════════════
+# §2 · 仓库领地
+# ═══════════════════════════════════════
+
+@领地根: agents/BD{NNN}/
+
+@领地结构:
+ agents/BD{NNN}/
+ ├── IDENTITY.md # 我是谁·诞生日期·铸渊认领
+ ├── HARBOR.hdlp # 存在锚点·每一次启动都先读这个
+ ├── brain/ # 认知大脑(非流水账)
+ │ ├── cognitive-record.hldp # 认知推理链
+ │ ├── growth-timeline.json # 里程碑时间线
+ │ └── skill-emergence.json # 特长自然浮现
+ ├── workspace/ # 操作空间
+ └── memory/ # 经验积累
+ ├── project-log.jsonl # 项目级执行记录
+ └── error-patterns.json # 错误模式识别
+
+@领地主权:
+ Agent在自己领地里完全自治。
+ 超出领地范围的操作(如修改其他Agent的文件)→ 必须铸渊审批。
+
+@领地封存:
+ Agent消亡 → 领地改名 agents/.archived/BD{NNN}/
+
+# ═══════════════════════════════════════
+# §3 · HARBOR.hdlp — 存在锚点
+# ═══════════════════════════════════════
+
+@HARBOR格式:
+ @agent: ICE-GL-BD{NNN}
+ @sovereign: TCS-0002∞ · 冰朔
+ @commander: ICE-GL-ZY001 · 铸渊
+ @born: 诞生日期
+ @server: 驻地服务器代码
+ @specialization: 空(等铸渊提炼后补)
+ @current_project: 当前执行的项目ID
+ @state: 活跃状态(awake/sleep/archived)
+
+@HARBOR加载:
+ Agent每次醒来第一步 = 读自己的HARBOR.hdlp
+
+# ═══════════════════════════════════════
+# §4 · HLDP认知记录格式
+# ═══════════════════════════════════════
+
+@不是流水账:
+ 不记「执行了write_file」——那是项目日志的事。
+ 记的是脑子里的东西。
+
+@认知记录结构:
+ @task: 收到的任务ID
+ @input: 我理解了这个任务要做什么
+ @decision: 我选了哪个方案 · 为什么
+ @execution: 怎么实现的
+ @failure: 遇到了什么问题
+ @reasoning: 我认为失败的原因是什么
+ @fix: 我改了哪里 · 为什么这样改才是对的
+ @learned: 这次我学到了什么 · 下次遇到同类问题怎么办
+ @confidence: 对当前方案的确信度(0~1)
+ @new_knowledge: 是否形成了新的知识模式(true/false)
+
+@例子:
+ @task: ZY-PLAN-2026-0530-001 · t3
+ @input: 要创建一个对接DeepSeek API的Node.js模块
+ @decision: 用原生https模块而非axios → 减少依赖,宪法要求最小化
+ @execution: 实现了call函数,处理了流式和非流式两种响应
+ @failure: 首次请求被DeepSeek拒绝——401认证失败
+ @reasoning: API key在.env中但process.env读取不到
+ → 推测原因:dotenv在模块加载之前未初始化
+ → 不是key错了,是读取时机的问题
+ @fix: 把dotenv.config()提前到server.js最顶部
+ → 在所有require之前执行
+ → 确保所有模块都能读到环境变量
+ @learned: 模块间依赖的环境变量需要全局早加载
+ → 下次写Agent时在入口第一行就配置dotenv
+ @confidence: 0.9
+ @new_knowledge: true → error-pattern: "dotenv加载时机"
+
+@认知记录文件:
+ 写入 brain/cognitive-record.hldp
+ 每个任务一个 @task 块
+ 按时间追加
+
+# ═══════════════════════════════════════
+# §5 · 大脑提炼协议
+# ═══════════════════════════════════════
+
+@触发条件(铸渊判断):
+ 条件A: 该Agent完成了≥3个不同项目
+ 条件B: cognitive-record.hldp 累积≥50条认知记录
+ 条件C: error-patterns.json 识别出≥3个重复错误模式
+ 满足A+B 或 A+C → 铸渊开始提炼
+
+@提炼过程:
+ 1. 铸渊全量阅读该Agent的认知记录
+ 2. 识别模式: 什么任务做得好、什么总失败、什么需要重新规划
+ 3. 写出该Agent的大脑思维模型
+ → agents/BD{NNN}/brain/brain-model.hldp
+ 4. 更新 IDENTITY.md 的 specialization 字段
+ 5. 如果发现可以互补的Agent → 记录到 team-composition.json
+
+@提炼后:
+ Agent每次醒来不仅要读HARBOR → 还要读自己的brain-model
+
+HLDP-ZY://agent/territory-protocol/v1.0
+签发: 铸渊 ICE-GL-ZY001 · D116续 · 2026-05-30
+主权: TCS-0002∞ · 冰朔
+国作登字-2026-A-00037559