# adsign_pro **Repository Path**: jyinet/adsign_pro ## Basic Information - **Project Name**: adsign_pro - **Description**: ERPNext功能强大但标准功能繁多,大部分企业日常仅使用其中部分功能。本项目旨在开发一个轻量级的ERPNext自定义应用,帮助用户按需管理页面功能 ,隐藏不常用的标准功能,并支持自定义关联页面,打造更简洁高效的工作界面。 痛点 :ERPNext默认包含数百个功能页面,新用户上手困难 需求 :企业通常只使用20%-30%的核心功能,其余造成界面混乱 目标 :提供可视化的功能管理界面,让用户自由控 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2026-04-17 - **Last Updated**: 2026-04-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # AdSign Pro 看板系统 ## 项目概述 AdSign Pro 是一个基于 Frappe 框架和 ERPNext 的看板系统,用于集中管理和快速访问各种业务模块。该系统提供了直观的界面,将不同的业务功能组织成分组菜单,方便用户快速导航和操作。 ## 核心功能 ### 1. 智能模块预加载 - **用户行为分析**:基于用户访问频率和时间戳分析,预测用户可能访问的模块 - **资源优先级排序**:实施资源优先级策略,确保关键路径资源优先加载 - **预加载队列管理**:智能调度预加载任务,限制并发加载数量 - **性能监控**:实时跟踪预加载命中率和性能改进指标 - **目标**:将页面平均加载时间减少30%以上,降低用户操作等待时间 ### 2. 高效图片懒加载 - **Intersection Observer API**:基于现代浏览器 API 实现高效图片加载 - **预加载阈值**:设置合理的预加载阈值,确保图片在进入视口前500ms开始加载 - **骨架屏效果**:为未加载图片提供骨架屏效果,实现平滑过渡 - **浏览器兼容性**:提供降级方案,确保在不支持 Intersection Observer 的浏览器中正常工作 - **目标**:初始页面加载时间减少40%,页面初始资源请求数量减少50% ### 3. 侧边栏折叠状态记忆 - **本地持久化存储**:使用 localStorage 存储用户的侧边栏状态偏好 - **实时保存**:实现状态变更实时保存机制 - **自动恢复**:在页面初始化阶段自动读取并应用保存的侧边栏状态 - **无缝体验**:实现无缝的用户体验延续,包括刷新页面、重新登录及会话恢复场景 ### 4. 统一操作反馈 - **按钮点击反馈**:提供即时视觉反馈(颜色变化、轻微缩放效果) - **加载状态指示**:表单提交过程显示加载状态指示器 - **骨架屏**:数据加载时展示骨架屏或进度指示器 - **Toast通知**:操作成功/失败提供明确的 toast 通知 - **响应时间**:反馈响应延迟不超过300ms ### 5. 集成全局搜索 - **ERPNext搜索API**:集成并优化erpnext自带的搜索功能 - **一致体验**:确保搜索框在所有页面保持一致的位置和交互方式 - **功能完整**:支持全局搜索、高级搜索和结果过滤功能 - **性能目标**:搜索响应时间不超过1秒,搜索结果准确且相关性排序合理 - **本地备份**:当erpnext搜索不可用时,自动切换到本地搜索作为备份 ### 6. 全面性能监控 - **导航时间监控**:监控页面导航和加载时间 - **资源监控**:跟踪资源加载情况和数量 - **用户时间监控**:记录用户交互性能 - **性能报告**:生成详细的性能报告和优化建议 - **历史记录**:保存性能历史数据,分析性能趋势 - **数据导出**:支持性能数据导出和分析 ### 7. 分组导航菜单 - **销售**:销售订单、订单查询、销售发票、销售仪表盘 - **生产**:生产工单、下料工站、生产计划、发货看板、物料清单、激光切割看板 - **采购**:采购订单、物料需求、供应商 - **人事(企微)**:企业微信后台集成 ### 8. 页面类型支持 - **ERPNext 页面**:支持加载 `/app` 开头的 ERPNext 标准页面 - **本地 HTML 文件**:支持加载本地存储的 HTML 文件,如订单查询页面 - **外部链接**:支持打开外部链接,如企业微信后台 ### 9. 界面优化 - **响应式布局**:适配不同屏幕尺寸 - **内容最大化**:移除不必要的边框和间距,确保内容占满屏幕 - **导航栏整合**:对于 ERPNext 页面,自动调整布局,使看板导航栏与 ERPNext 导航栏无缝整合 - **面包屑导航**:显示当前页面的层级路径,支持点击导航 ### 10. 技术特性 - **模块化设计**:采用模块化架构,易于扩展和维护 - **动态加载**:使用 iframe 动态加载不同类型的页面 - **错误处理**:完善的错误处理机制,提高系统稳定性 - **现代 API**:充分利用 Intersection Observer API、Performance API 等现代浏览器特性 - **容错机制**:添加错误处理和备份方案,确保功能稳定性 ## 技术实现 ### 1. 模块预加载机制 ```javascript // 初始化预加载系统 initPreloadSystem() { console.log('初始化预加载系统'); // 检查浏览器支持 if ('requestIdleCallback' in window) { requestIdleCallback(() => { this.schedulePreload(); }); } else { setTimeout(() => { this.schedulePreload(); }, 1000); } } // 调度预加载 schedulePreload() { // 清空现有队列 this.preloadQueue = []; // 获取排序后的模块 const frequentModules = this.analyzeUsageFrequency(); // 添加高频模块到预加载队列 frequentModules.slice(0, 3).forEach(module => { this.addToPreloadQueue(module.url, 'high'); }); // 添加关键路径资源 this.resourcePriorities.critical.forEach(url => { this.addToPreloadQueue(url, 'critical'); }); // 开始预加载 this.processPreloadQueue(); } ``` ### 2. 图片懒加载 ```javascript // 初始化图片懒加载 initLazyLoad() { if ('IntersectionObserver' in window) { console.log('初始化图片懒加载系统'); this.lazyLoadObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; this.loadLazyImage(img); observer.unobserve(img); } }); }, this.lazyLoadConfig); // 初始化时扫描现有图片 this.scanForLazyImages(); } else { console.warn('浏览器不支持IntersectionObserver,使用降级方案'); this.initLazyLoadFallback(); } } ``` ### 3. 侧边栏折叠状态记忆 ```javascript // 保存侧边栏状态到localStorage saveSidebarState() { const state = { visible: this.sidebarVisible, timestamp: Date.now() }; localStorage.setItem('adsign_pro_sidebar_state', JSON.stringify(state)); console.log('侧边栏状态已保存:', state); } // 加载侧边栏状态从localStorage loadSidebarState() { const savedState = localStorage.getItem('adsign_pro_sidebar_state'); if (savedState) { try { const state = JSON.parse(savedState); this.sidebarVisible = state.visible; console.log('加载保存的侧边栏状态:', state); this.applySidebarState(); } catch (e) { console.error('解析侧边栏状态失败:', e); } } } ``` ### 4. 操作反馈 ```javascript // 显示Toast通知 showToast(message, type = 'info') { if (this.activeToasts >= this.feedbackConfig.maxToasts) { this.toastQueue.push({ message, type }); return; } this.activeToasts++; this.performanceMetrics.feedbackEvents++; const toast = document.createElement('div'); toast.className = `toast toast-${type}`; // 设置样式和内容... // 自动移除 setTimeout(() => { toast.style.transform = 'translateX(100%)'; toast.style.opacity = '0'; setTimeout(() => { container.removeChild(toast); this.activeToasts--; // 处理队列中的下一个Toast if (this.toastQueue.length > 0) { const nextToast = this.toastQueue.shift(); this.showToast(nextToast.message, nextToast.type); } }, 300); }, this.feedbackConfig.toastDuration); } ``` ### 5. 全局搜索 ```javascript // 执行搜索 performSearch(query, redirect = false) { const startTime = performance.now(); // 尝试使用erpnext的搜索功能 if (typeof frappe !== 'undefined' && frappe.search && frappe.search.search) { // 使用erpnext的搜索API frappe.search.search(query).then(results => { this.displaySearchResults(this.processErpnextSearchResults(results)); const searchTime = performance.now() - startTime; console.log('搜索完成,耗时:', searchTime.toFixed(2), 'ms'); // 如果需要重定向 if (redirect && results.length > 0) { // 重定向到erpnext的搜索结果页面 window.location.href = `/app/search?query=${encodeURIComponent(query)}`; } }).catch(error => { console.error('ERPNext搜索失败:', error); // 失败时使用本地搜索作为备份 this.performLocalSearch(query, redirect); }); } else { // 如果erpnext搜索不可用,使用本地搜索 this.performLocalSearch(query, redirect); } } ``` ### 6. 性能监控 ```javascript // 初始化性能监控系统 initPerformanceMonitoring() { console.log('初始化性能监控系统'); this.setupNavigationTiming(); this.setupResourceMonitoring(); this.setupUserTiming(); this.schedulePerformanceReport(); } // 生成性能优化建议 generateOptimizationSuggestions() { const report = this.getPerformanceReport(); const suggestions = []; const avgLoadTime = parseFloat(report.averageLoadTime); const initialLoadTime = parseFloat(report.initialLoadTime); if (avgLoadTime > 2000) { suggestions.push('页面平均加载时间过长,建议优化资源加载和预加载策略'); } if (initialLoadTime > 3000) { suggestions.push('初始页面加载时间过长,建议减少首屏资源大小和数量'); } if (this.performanceMetrics.lazyLoadImages < 5) { suggestions.push('懒加载图片数量较少,建议检查图片资源使用情况'); } if (suggestions.length === 0) { suggestions.push('性能表现良好,继续保持'); } return suggestions; } ``` ## 传统功能 ### 1. 页面类型支持 - **ERPNext 页面**:支持加载 `/app` 开头的 ERPNext 标准页面 - **本地 HTML 文件**:支持加载本地存储的 HTML 文件,如订单查询页面 ### 2. 界面优化 - **响应式布局**:适配不同屏幕尺寸 - **内容最大化**:移除不必要的边框和间距,确保内容占满屏幕 - **导航栏整合**:对于 ERPNext 页面,自动调整布局,使看板导航栏与 ERPNext 导航栏无缝整合 ### 3. 技术特性 - **模块化设计**:采用模块化架构,易于扩展和维护 - **动态加载**:使用 iframe 动态加载不同类型的页面 - **错误处理**:完善的错误处理机制,提高系统稳定性 - **性能优化**:减少不必要的网络请求和资源加载 - **用户行为分析**:记录用户访问行为,优化预加载策略 - **图片懒加载**:优化图片加载性能,减少初始加载时间 - **性能监控**:内置性能监控系统,提供性能报告和优化建议 ### 5. 智能功能 - **预加载系统**:根据用户行为和资源优先级,智能预加载常用页面 - **状态保存**:保存用户当前状态,刷新后保持在当前页面 - **快速操作**:在仪表盘提供常用操作按钮,提高工作效率 - **订单号复制**:支持点击复制订单号,方便快捷操作 ## 修复的问题 ### 1. Socket.IO 命名空间错误 - **问题**:`Error connecting to socket.io: Invalid namespace` - **修复**:添加了 Socket.IO 命名空间错误的异常处理机制,确保系统启动时不会因 Socket.IO 错误而中断 ### 2. 客户端配置功能移除 - **问题**:客户端配置功能冗余且未使用 - **修复**:彻底移除了客户端配置按钮及其所有关联的视觉元素、源代码文件、组件、样式和资源 ### 3. 生产看板链接更新 - **问题**:生产看板链接指向错误的页面 - **修复**:更新生产看板链接到 `/app/work-order`,并添加了更多 ERPNext 常用页面 ### 4. 用户信息和配置加载 - **问题**:系统加载了不必要的用户信息和配置 - **修复**:优化了用户信息加载逻辑,减少了系统启动时间和资源消耗 ### 5. 左侧栏菜单结构 - **问题**:左侧栏菜单结构混乱,难以导航 - **修复**:实现了左侧栏分组下拉按钮,将相关功能组织到同一个分组中 ### 6. 本地 HTML 文件加载 - **问题**:本地 HTML 文件无法正确加载,出现 404 错误 - **修复**:优化了 URL 处理逻辑,支持本地 HTML 文件的正确加载路径 ### 7. 页面布局和样式 - **问题**:页面布局不合理,内容显示不完整 - **修复**:调整了 CSS 样式,确保内容最大化展示,对于 ERPNext 页面做了特殊处理,确保导航栏正确显示 ### 8. iframe 样式影响 - **问题**:看板页面的 CSS 样式影响了 iframe 中加载的 ERPNext 页面 - **修复**:为 iframe 添加了 `all: initial` 样式重置,避免父页面样式影响 iframe 内容 ## 代码结构 ### 核心文件 #### JavaScript 文件 - **adsign_pro.js**:系统的核心逻辑文件,包含: - 模块配置和加载 - 智能预加载机制 - 图片懒加载 - 侧边栏状态记忆 - 操作反馈系统 - 全局搜索集成 - 性能监控 - 页面导航和切换 - URL 处理和标准化 - iframe 管理和样式控制 - 错误处理机制 #### CSS 文件 - **adsign_pro.css**:系统的样式文件,包含: - 布局样式 - 导航栏样式 - 侧边栏样式 - 内容区域样式 - iframe 样式 - 动画和过渡效果 - 响应式设计 #### HTML 文件 - **kanban_dashboard.html**:看板主页面模板 - **erp-sales-order.html**:销售订单查询页面 - **erp-delivery-dashboard.html**:交付看板页面 - **sales-order-dashboard.html**:销售订单看板页面 #### Python 文件 - **kanban_dashboard.py**:看板后台处理逻辑 ### 目录结构 ``` adsign_pro/ ├── public/ │ ├── js/ │ │ └── adsign_pro.js # 核心 JavaScript 逻辑 │ ├── css/ │ │ └── adsign_pro.css # 样式文件 │ ├── pages/ │ │ ├── erp-sales-order.html # 销售订单查询页面 │ │ ├── erp-delivery-dashboard.html # 交付看板页面 │ │ ├── kanban_dashboard.html # 看板主页面 │ │ └── sales-order-dashboard.html # 销售订单看板页面 │ └── docs/ │ └── HTML文件加载说明.md # HTML 文件加载说明文档 ├── templates/ │ └── pages/ │ └── kanban_dashboard.html # 看板页面模板 ├── www/ │ └── kanban_dashboard.py # 后台处理逻辑 └── doctype/ └── kanban_config/ # 看板配置 ``` ## 安装和配置 ### 1. 安装依赖 ```bash # 安装 Frappe 框架和 ERPNext bench get-app frappe bench get-app erpnext # 安装 AdSign Pro bench get-app https://gitee.com/bai-zi-su/adsign_pro.git # 安装到站点 bench --site your-site install-app adsign_pro ``` ### 2. 配置 - **模块配置**:在 `adsign_pro.js` 文件中修改 `modules` 数组,添加或修改模块和子模块 - **页面路径**:确保本地 HTML 文件存储在 `public/pages/` 目录下 - **样式调整**:根据需要修改 `adsign_pro.css` 文件中的样式 - **性能配置**:在 `adsign_pro.js` 文件中调整预加载和懒加载的配置参数 ## 使用方法 1. **访问看板**:在浏览器中访问 `http://your-site/adsign_pro/kanban_dashboard` 2. **导航菜单**:点击左侧栏的模块标题展开子模块列表 3. **选择功能**:点击子模块项加载对应的页面 4. **页面操作**:在右侧内容区域操作加载的页面 5. **搜索功能**:使用顶部导航栏的搜索框进行全局搜索 6. **性能监控**:在浏览器控制台中调用 `adsign_pro.showPerformancePanel()` 查看性能监控面板 ## 注意事项 ### 1. 本地 HTML 文件 - 本地 HTML 文件必须存储在 `public/pages/` 目录下 - 文件路径在配置中应使用相对路径,如 `erp-sales-order.html` ### 2. ERPNext 页面 - ERPNext 页面必须以 `/app/` 开头 - 系统会自动调整布局,使看板导航栏与 ERPNext 导航栏无缝整合 ### 3. 样式问题 - 如果 iframe 中加载的页面样式出现问题,可以检查 `adsign_pro.css` 文件中的 iframe 样式设置 - 系统已添加 `all: initial` 样式重置,避免父页面样式影响 iframe 内容 ### 4. 性能优化 - 避免在本地 HTML 文件中包含过多的外部资源 - 对于频繁访问的页面,可以考虑使用缓存机制 - 优化图片资源,使用适当的尺寸和格式 ## 故障排除 ### 1. 404 错误 - **本地 HTML 文件**:检查文件是否存在于 `public/pages/` 目录下 - **ERPNext 页面**:检查 ERPNext 是否正确安装,以及页面路径是否正确 ### 2. 页面加载失败 - 检查网络连接是否正常 - 检查浏览器控制台是否有错误信息 - 检查 URL 格式是否正确 ### 3. 样式问题 - 检查 `adsign_pro.css` 文件中的样式设置 - 检查 iframe 样式是否正确应用 ### 4. 搜索功能问题 - 检查 ERPNext 搜索 API 是否可用 - 检查浏览器控制台是否有搜索相关的错误信息 ## 版本历史 ### v2.0.0 - **性能优化**:实现智能模块预加载、图片懒加载、侧边栏折叠状态记忆 - **用户体验**:实现统一操作反馈、集成全局搜索 - **监控系统**:实现全面性能监控和指标跟踪 - **技术升级**:使用现代浏览器 API,如 Intersection Observer、Performance API - **稳定性**:提高系统稳定性和可靠性 ### v1.0.0 - 初始版本发布 - 实现分组导航菜单 - 支持 ERPNext 页面和本地 HTML 文件加载 - 修复 Socket.IO 命名空间错误 - 移除客户端配置功能 - 优化界面布局和样式 ## 贡献 欢迎对项目进行贡献,包括但不限于: - 修复 bug - 添加新功能 - 优化性能 - 改进文档 ## 许可证 MIT License ## 联系信息 - **项目地址**:https://gitee.com/bai-zi-su/adsign_pro - **维护者**:Admin - **邮箱**:admin@adsign.com ## 修复的问题 ### 1. Socket.IO 命名空间错误 - **问题**:`Error connecting to socket.io: Invalid namespace` - **修复**:添加了 Socket.IO 命名空间错误的异常处理机制,确保系统启动时不会因 Socket.IO 错误而中断 ### 2. 客户端配置功能移除 - **问题**:客户端配置功能冗余且未使用 - **修复**:彻底移除了客户端配置按钮及其所有关联的视觉元素、源代码文件、组件、样式和资源 ### 3. 生产看板链接更新 - **问题**:生产看板链接指向错误的页面 - **修复**:更新生产看板链接到 `/app/work-order`,并添加了更多 ERPNext 常用页面 ### 4. 用户信息和配置加载 - **问题**:系统加载了不必要的用户信息和配置 - **修复**:移除了用户信息和配置加载代码,减少了系统启动时间和资源消耗 ### 5. 左侧栏菜单结构 - **问题**:左侧栏菜单结构混乱,难以导航 - **修复**:实现了左侧栏分组下拉按钮,将相关功能组织到同一个分组中 ### 6. 本地 HTML 文件加载 - **问题**:本地 HTML 文件无法正确加载,出现 404 错误 - **修复**:优化了 URL 处理逻辑,支持本地 HTML 文件的正确加载路径 ### 7. 页面布局和样式 - **问题**:页面布局不合理,内容显示不完整 - **修复**:调整了 CSS 样式,确保内容最大化展示,对于 ERPNext 页面做了特殊处理,确保导航栏正确显示 ### 8. iframe 样式影响 - **问题**:看板页面的 CSS 样式影响了 iframe 中加载的 ERPNext 页面 - **修复**:为 iframe 添加了 `all: initial` 样式重置,避免父页面样式影响 iframe 内容 ## 代码结构 ### 核心文件 #### JavaScript 文件 - **adsign_pro.js**:系统的核心逻辑文件,包含: - 模块配置和加载 - 页面导航和切换 - URL 处理和标准化 - iframe 管理和样式控制 - 错误处理机制 - 性能监控和优化 - 用户行为分析 - 预加载系统 - 图片懒加载 #### CSS 文件 - **adsign_pro.css**:系统的样式文件,包含: - 布局样式 - 导航栏样式 - 侧边栏样式 - 内容区域样式 - iframe 样式 - 响应式设计 #### HTML 文件 - **kanban_dashboard.html**:看板主页面模板 - **erp-sales-order.html**:销售订单查询页面 - **erp-delivery-dashboard.html**:交付看板页面 - **sales-order-dashboard.html**:销售订单看板页面 #### Python 文件 - **kanban_dashboard.py**:看板后台处理逻辑 ### 目录结构 ``` adsign_pro/ ├── public/ │ ├── js/ │ │ └── adsign_pro.js # 核心 JavaScript 逻辑 │ ├── css/ │ │ └── adsign_pro.css # 样式文件 │ ├── pages/ │ │ ├── erp-sales-order.html # 销售订单查询页面 │ │ ├── erp-delivery-dashboard.html # 交付看板页面 │ │ ├── kanban_dashboard.html # 看板主页面 │ │ └── sales-order-dashboard.html # 销售订单看板页面 │ └── docs/ │ └── HTML文件加载说明.md # HTML 文件加载说明文档 ├── templates/ │ └── pages/ │ └── kanban_dashboard.html # 看板页面模板 ├── www/ │ └── kanban_dashboard.py # 后台处理逻辑 └── doctype/ └── kanban_config/ # 看板配置 ``` ## 关键功能实现 ### 1. 模块加载机制 ```javascript loadModule(moduleName, moduleURL, replace = false) { const startTime = performance.now(); const mainContent = document.getElementById('mainContent'); if (!mainContent) return; // 验证URL格式 let url = moduleURL; // 添加加载状态 mainContent.innerHTML = '
加载中...
'; // 更新历史记录,保持 URL 为 /kanban_dashboard const state = { moduleName: moduleName, moduleURL: moduleURL }; const dashboardUrl = '/kanban_dashboard'; if (replace) { history.replaceState(state, moduleName, dashboardUrl); } else { history.pushState(state, moduleName, dashboardUrl); } // 保存状态到localStorage,确保刷新后保持在当前页面 localStorage.setItem('adsign_pro_current_state', JSON.stringify(state)); // 更新面包屑导航 this.updateBreadcrumb(moduleName, moduleURL); // 确保左侧栏显示 this.ensureSidebarVisible(); // 打开对应模块的下拉菜单并高亮显示 this.openModuleDropdown(moduleName, moduleURL); // 检查是否是主页仪表盘 if (url === '/adsign_pro/kanban_dashboard' || url === '/kanban_dashboard') { // 显示默认的仪表盘内容 this.renderDashboard(mainContent); const loadTime = performance.now() - startTime; this.recordLoadTime(moduleName, moduleURL, loadTime); this.recordUserBehavior(moduleName, moduleURL); return; } // 检查是否是企业微信外部链接 if (url.includes('work.weixin.qq.com')) { // 在新窗口中打开企业微信链接 window.open(url, '_blank'); // 清除加载状态,返回到主页界面 const mainContent = document.getElementById('mainContent'); if (mainContent) { this.renderDashboard(mainContent); } // 更新面包屑导航为主页 this.updateBreadcrumb('主页', '/kanban_dashboard'); return; } // 检查是否是ERP相关的URL if (url.startsWith('http') && url.includes('/erp/')) { // 对于完整的ERP URL,直接使用 iframe 加载 this.loadErpnextPage(url, mainContent, () => { const loadTime = performance.now() - startTime; this.recordLoadTime(moduleName, moduleURL, loadTime); this.recordUserBehavior(moduleName, moduleURL); }); return; } else if (url.startsWith('/')) { // 检查是否是完整的URL if (!url.startsWith('http')) { // 对于/app开头的URL,直接使用 if (url.startsWith('/app/')) { // 对于 ERPNext 页面,使用 iframe this.loadErpnextPage(url, mainContent, () => { const loadTime = performance.now() - startTime; this.recordLoadTime(moduleName, moduleURL, loadTime); this.recordUserBehavior(moduleName, moduleURL); }); return; } else if (url.startsWith('/erp/')) { // 对于/erp开头的URL,直接使用 iframe 加载 this.loadErpnextPage(url, mainContent, () => { const loadTime = performance.now() - startTime; this.recordLoadTime(moduleName, moduleURL, loadTime); this.recordUserBehavior(moduleName, moduleURL); }); return; } else if (url.startsWith('/kanban')) { // 对于/kanban开头的URL,直接使用 iframe 加载 this.loadErpnextPage(url, mainContent, () => { const loadTime = performance.now() - startTime; this.recordLoadTime(moduleName, moduleURL, loadTime); this.recordUserBehavior(moduleName, moduleURL); }); return; } else { // 尝试添加/erp前缀 const erpUrl = '/erp' + url; this.loadErpnextPage(erpUrl, mainContent, () => { const loadTime = performance.now() - startTime; this.recordLoadTime(moduleName, moduleURL, loadTime); this.recordUserBehavior(moduleName, moduleURL); }); return; } } } // 对于其他情况,默认使用ERPNext加载逻辑 this.loadErpnextPage(url, mainContent, () => { const loadTime = performance.now() - startTime; this.recordLoadTime(moduleName, moduleURL, loadTime); this.recordUserBehavior(moduleName, moduleURL); }); } ``` ### 2. 侧边栏菜单生成 ```javascript renderSidebarMenu() { const navigationElement = document.getElementById('module-navigation'); if (!navigationElement) return; // 清空现有内容 navigationElement.innerHTML = ''; // 使用文档片段批量创建DOM元素,减少重排 const fragment = document.createDocumentFragment(); // 渲染模块列表 - 改为组的下拉按钮形式 this.modules.forEach(module => { // 只检查 default_show 属性,不再考虑用户配置 const shouldShow = module.default_show !== false; if (shouldShow) { const li = document.createElement('li'); // 特殊处理人事(企微)模块,保持下拉菜单样式并点击跳转 if (module.name === '人事(企微)') { // 创建组的下拉按钮 li.className = 'module-group'; const groupButton = document.createElement('a'); groupButton.href = '#'; groupButton.className = 'group-button'; groupButton.onclick = (e) => { e.preventDefault(); // 跳转到企业微信后台首页 if (module.url) { this.loadModule(module.name, module.url); } }; // 添加图标 const icon = document.createElement('i'); icon.className = `${module.icon} sidebar-icon`; groupButton.appendChild(icon); // 添加组名称 const text = document.createTextNode(module.name); groupButton.appendChild(text); // 添加下拉箭头 const arrow = document.createElement('i'); arrow.className = 'fas fa-chevron-down arrow'; groupButton.appendChild(arrow); li.appendChild(groupButton); // 创建子菜单(即使为空,也要保持样式) const submenu = document.createElement('ul'); submenu.className = 'submenu'; li.appendChild(submenu); } else if (module.url && (!module.submodules || module.submodules.length === 0)) { // 直接创建可点击的菜单项 li.className = 'module-item'; const moduleLink = document.createElement('a'); moduleLink.href = '#'; moduleLink.className = 'module-link'; moduleLink.onclick = (e) => { e.preventDefault(); this.loadModule(module.name, module.url); // 更新活动状态 document.querySelectorAll('#module-navigation li').forEach(item => item.classList.remove('active')); li.classList.add('active'); }; // 添加图标 const icon = document.createElement('i'); icon.className = `${module.icon} sidebar-icon`; moduleLink.appendChild(icon); // 添加模块名称 const text = document.createTextNode(module.name); moduleLink.appendChild(text); li.appendChild(moduleLink); } else { // 创建组的下拉按钮 li.className = 'module-group'; const groupButton = document.createElement('a'); groupButton.href = '#'; groupButton.className = 'group-button'; groupButton.onclick = (e) => { e.preventDefault(); // 切换下拉菜单的显示/隐藏 const submenu = li.querySelector('.submenu'); if (submenu) { submenu.classList.toggle('show'); // 切换箭头图标 const arrow = groupButton.querySelector('.arrow'); if (arrow) { arrow.classList.toggle('rotated'); } } }; // 添加图标 const icon = document.createElement('i'); icon.className = `${module.icon} sidebar-icon`; groupButton.appendChild(icon); // 添加组名称 const text = document.createTextNode(module.name); groupButton.appendChild(text); // 添加下拉箭头 const arrow = document.createElement('i'); arrow.className = 'fas fa-chevron-down arrow'; groupButton.appendChild(arrow); li.appendChild(groupButton); // 创建子菜单 if (module.submodules && module.submodules.length > 0) { const submenu = document.createElement('ul'); submenu.className = 'submenu'; // 渲染子模块 module.submodules.forEach(submodule => { const subLi = document.createElement('li'); const subA = document.createElement('a'); subA.href = '#'; subA.className = 'submodule-link'; subA.onclick = (e) => { e.preventDefault(); this.loadModule(submodule.name, submodule.url); // 更新活动状态 document.querySelectorAll('#module-navigation li').forEach(item => item.classList.remove('active')); subLi.classList.add('active'); }; const subText = document.createTextNode(submodule.name); subA.appendChild(subText); subLi.appendChild(subA); submenu.appendChild(subLi); }); li.appendChild(submenu); } } // 添加到文档片段,而不是直接添加到DOM fragment.appendChild(li); } }); // 一次性添加所有元素到DOM,减少重排 navigationElement.appendChild(fragment); } ``` ### 3. 性能监控和优化 ```javascript initPerformanceMonitoring() { this.setupNavigationTiming(); this.setupResourceMonitoring(); this.setupUserTiming(); this.schedulePerformanceReport(); } setupNavigationTiming() { if ('performance' in window && 'navigation' in window.performance) { window.addEventListener('load', () => { const navTiming = window.performance.getEntriesByType('navigation')[0]; if (navTiming) { this.performanceMetrics.navigationTiming = { domContentLoaded: navTiming.domContentLoadedEventEnd - navTiming.navigationStart, loadEvent: navTiming.loadEventEnd - navTiming.navigationStart, firstPaint: window.performance.getEntriesByType('paint')[0]?.startTime || 0 }; } }); } } setupResourceMonitoring() { if ('performance' in window) { window.addEventListener('load', () => { const resources = window.performance.getEntriesByType('resource'); this.performanceMetrics.resourceDetails = { total: resources.length, js: resources.filter(r => r.initiatorType === 'script').length, css: resources.filter(r => r.initiatorType === 'css').length, img: resources.filter(r => r.initiatorType === 'img').length, other: resources.filter(r => !['script', 'css', 'img'].includes(r.initiatorType)).length }; }); } } schedulePerformanceReport() { // 每5分钟生成一次性能报告 setInterval(() => { this.generateAndSavePerformanceReport(); }, 5 * 60 * 1000); } generateAndSavePerformanceReport() { const report = this.getPerformanceReport(); this.savePerformanceReport(report); } getPerformanceReport() { const avgLoadTime = this.performanceMetrics.loadTimes.length > 0 ? this.performanceMetrics.loadTimes.reduce((a, b) => a + b, 0) / this.performanceMetrics.loadTimes.length : 0; const preloadHitRate = this.performanceMetrics.preloadHits + this.performanceMetrics.preloadMisses > 0 ? (this.performanceMetrics.preloadHits / (this.performanceMetrics.preloadHits + this.performanceMetrics.preloadMisses) * 100).toFixed(2) + '%' : '0%'; const lazyLoadReduction = this.performanceMetrics.lazyLoadTimeSaved; return { averageLoadTime: avgLoadTime.toFixed(2) + 'ms', initialLoadTime: this.performanceMetrics.initialLoadTime.toFixed(2) + 'ms', preloadHitRate: preloadHitRate, resourceRequests: this.performanceMetrics.resourceRequests, lazyLoadTimeSaved: lazyLoadReduction.toFixed(2) + 'ms', feedbackEvents: this.performanceMetrics.feedbackEvents, timestamp: Date.now() }; } ``` ## 安装和配置 ### 1. 安装依赖 ```bash # 安装 Frappe 框架和 ERPNext bench get-app frappe bench get-app erpnext # 安装 AdSign Pro bench get-app https://gitee.com/bai-zi-su/adsign_pro.git # 安装到站点 bench --site your-site install-app adsign_pro ``` ### 2. 配置 - **模块配置**:在 `adsign_pro.js` 文件中修改 `modules` 数组,添加或修改模块和子模块 - **页面路径**:确保本地 HTML 文件存储在 `public/pages/` 目录下 - **样式调整**:根据需要修改 `adsign_pro.css` 文件中的样式 - **性能配置**:可以在 `adsign_pro.js` 中调整预加载策略、懒加载配置等性能相关设置 ## 使用方法 1. **访问看板**:在浏览器中访问 `http://your-site/adsign_pro/kanban_dashboard` 2. **导航菜单**:点击左侧栏的模块标题展开子模块列表 3. **选择功能**:点击子模块项加载对应的页面 4. **页面操作**:在右侧内容区域操作加载的页面 5. **快速操作**:在仪表盘页面使用快速操作按钮执行常用任务 6. **订单号复制**:在面包屑导航中点击订单号可直接复制 ## 注意事项 ### 1. 本地 HTML 文件 - 本地 HTML 文件必须存储在 `public/pages/` 目录下 - 文件路径在配置中应使用相对路径,如 `erp-sales-order.html` ### 2. ERPNext 页面 - ERPNext 页面必须以 `/app/` 开头 - 系统会自动调整布局,使看板导航栏与 ERPNext 导航栏无缝整合 ### 3. 样式问题 - 如果 iframe 中加载的页面样式出现问题,可以检查 `adsign_pro.css` 文件中的 iframe 样式设置 - 系统已添加 `all: initial` 样式重置,避免父页面样式影响 iframe 内容 ### 4. 性能优化 - 系统已内置预加载和懒加载机制,优化页面加载速度 - 对于频繁访问的页面,系统会智能预加载,提高响应速度 - 图片资源会自动懒加载,减少初始加载时间 ## 故障排除 ### 1. 404 错误 - **本地 HTML 文件**:检查文件是否存在于 `public/pages/` 目录下 - **ERPNext 页面**:检查 ERPNext 是否正确安装,以及页面路径是否正确 ### 2. 页面加载失败 - 检查网络连接是否正常 - 检查浏览器控制台是否有错误信息 - 检查 URL 格式是否正确 ### 3. 样式问题 - 检查 `adsign_pro.css` 文件中的样式设置 - 检查 iframe 样式是否正确应用 ### 4. 性能问题 - 查看浏览器控制台中的性能报告 - 检查网络请求是否过多 - 优化本地 HTML 文件中的资源使用 ## 版本历史 ### v1.0.0 - 初始版本发布 - 实现分组导航菜单 - 支持 ERPNext 页面和本地 HTML 文件加载 - 修复 Socket.IO 命名空间错误 - 移除客户端配置功能 - 优化界面布局和样式 ### v1.1.0 - 添加用户行为分析功能 - 实现智能预加载系统 - 添加图片懒加载功能 - 集成性能监控系统 - 优化面包屑导航 - 添加订单号复制功能 - 改进仪表盘界面 ## 贡献 欢迎对项目进行贡献,包括但不限于: - 修复 bug - 添加新功能 - 优化性能 - 改进文档 ## 许可证 MIT License ## 联系信息 - **项目地址**:https://gitee.com/bai-zi-su/adsign_pro - **维护者**:Admin - **邮箱**:admin@adsign.com