简介:这是一套基于HTML/CSS/JS实现的轻量级OA系统前端源码,适用于Web开发初学者与中小型办公系统定制需求者,帮助快速搭建具备菜单导航、用户在线状态、快捷入口、主题切换等核心功能的办公自动化界面。资源共1812个文件,涵盖66个HTML页面(如menu.html、user_online.html、topbar.html等构成完整前端路由与布局)、211个CSS样式文件、398个PNG与1009个GIF图像资源、66个JS脚本(含treeview.js、calendar.js等交互逻辑),以及少量配置类文件(ini、xml)和备份文件(.bak),整体压缩包仅2.67MB,结构清晰、模块解耦度高。目前已有366人学习下载,开发者可直接运行调试,深入理解OA系统前端组织方式;亦可基于现有HTML骨架集成后端接口,或提取单个组件(如status_bar.html、shortcut.html)复用于其他管理平台。
1. 这不是一套“能直接跑起来”的OA系统,而是一份2000年代末期典型Web OA的前端骨架源码
你解压Office Anywhere.zip后看到的.bak文件——treeview.js.bak、calendar.js.bak、index.html.bak……不是备份失误,而是真实开发痕迹:这套代码大概率来自2008–2012年间某家中小型企业采购后二次定制的Office Anywhere私有部署版本,原始系统已停服多年,但前端结构仍完整保留。它不依赖现代构建工具,没有package.json或webpack.config.js,所有交互靠原生 JavaScript + IE6–8 兼容写法驱动;rtxint.js.bak暗示曾集成腾讯RTX(企业即时通讯),tangerocx.js.bak指向“唐僧控件”——一种用于IE内嵌Office文档预览的ActiveX组件。这意味着:它不是给你开箱即用的SaaS,而是供你逆向学习“传统B/S架构OA如何用纯前端胶水粘合业务模块”的活体标本。适合三类人:想吃透老系统维护逻辑的IT运维、需对接遗留OA接口的Java/PHP后端、以及正在设计低代码平台菜单引擎的前端架构师。如果你期待Vue3+Spring Boot模板,这包源码会令你失望;但若你想知道为什么当年<iframe>被当核心路由、document.all为何是必写判断、window.showModalDialog怎样承载审批弹窗——它就是教科书级现场。
2. 解析核心前端模块:从.bak文件反推OA系统运行时架构
2.1.bak文件的本质:开发过程中的“断点快照”,而非无用备份
这些.bak文件并非冗余残留,而是关键调试证据。以treeview.js.bak为例,其内容包含大量document.getElementById("treeNode_").innerHTML = ...和onDblClick="openNode(this)"绑定,说明该树形菜单采用DOM直写+事件内联方式实现,无任何虚拟DOM抽象。对比当前主流框架,这种写法在IE6下性能更可控,但导致JS与HTML强耦合。calendar.js.bak中存在new Date().getYear()+1900(非getFullYear())和document.layers判断,明确指向 Netscape 4.x 兼容层——这是2003年前后代码的铁证。rtxint.js.bak里window.external.RTXLogin()调用证实其依赖浏览器插件级集成,而tangerocx.js.bak的document.getElementById("TangeroOCX").LoadFile("a.doc")直接暴露了ActiveX控件调用路径。这些.bak文件共同构成一个技术断代坐标系:它们不是“废弃代码”,而是锁定在特定IE时代的技术契约。
提示:不要删除
.bak文件!它们比主文件更能反映原始开发意图。例如index.html.bak中<base href="http://192.168.1.100/oa/">显示该系统曾部署在内网固定IP,而主index.html可能已被修改为相对路径。
2.2 核心HTML页面功能映射与依赖关系图谱
以下表格基于文件名语义及实际代码片段反向验证,列出各HTML文件在OA系统中的角色、关键依赖项及潜在风险点:
| 文件名 | 实际功能定位 | 关键依赖资源 | 典型风险场景 |
|---|---|---|---|
menu.html | 左侧垂直导航菜单,含三级折叠逻辑 | treeview.js.bak、module.js.bak | module.js.bak中parent.frames['mainFrame'].location.href硬编码frame名称,若改用iframe需同步更新 |
intel_view.html | 数据看板页,含动态刷新表格 | calendar.js.bak(日期筛选)、attach.js.bak(附件预览) | 表格行渲染使用document.write(),在现代浏览器中可能阻塞解析 |
new.html | 新建任务/公告表单页 | dlg_ins_image.html.bak(图片插入弹窗)、status_bar.html(提交状态反馈) | 表单提交通过document.forms[0].submit()触发,无AJAX,整页刷新 |
theme.html | 主题切换器,支持CSS皮肤切换 | pheader.html(头部样式注入点)、topbar.html(logo替换) | 主题切换逻辑写在<script>内联块中,无法热更新CSS变量 |
user_online.html | 在线用户列表(轮询实现) | index.html.bak中定义的refreshInterval=30000(30秒) | 轮询请求URL硬编码为/online/userlist.asp,后端必须提供对应ASP接口 |
验证方法:在本地启动Python简易HTTP服务(python -m http.server 8000),用IE11兼容模式打开index.html.bak,观察控制台报错。你会发现document.all未定义(因IE11默认禁用),此时需手动启用“企业模式”或修改index.html.bak中的if (document.all)为if (document.all || document.querySelector)—— 这正是老系统迁移的第一道坎。
2.3 JavaScript模块间调用链:module.js.bak是真正的中枢调度器
module.js.bak是整个前端逻辑的“神经中枢”,其核心函数loadModule(moduleName, url)控制所有页面加载行为。分析其源码可发现三层调用结构:
// module.js.bak 片段(已还原注释) function loadModule(moduleName, url) { // 1. 验证权限:读取隐藏域 <input type="hidden" id="userRole" value="admin"> var role = document.getElementById("userRole").value; if (!checkPermission(moduleName, role)) { alert("权限不足"); return; } // 2. 加载目标:写入mainFrame的src属性(注意:非iframe.contentWindow.location) parent.mainFrame.location.href = url; // 3. 更新状态栏:调用status_bar.html中的updateStatus()函数 parent.statusBar.updateStatus(moduleName); }该函数暴露三个关键设计事实:
- 权限校验前置:所有模块加载前检查
userRole隐藏域值,而非后端API返回,说明权限由登录态写入HTML,安全性依赖服务端输出过滤; - frame通信硬编码:
parent.mainFrame强依赖frameset布局,若改为单页应用(SPA),需重写为window.postMessage或全局事件总线; - 状态同步紧耦合:
parent.statusBar.updateStatus()要求status_bar.html必须作为独立frame存在,且updateStatus函数必须全局可访问。
注意:
checkPermission()函数在module.js.bak中以对象字面量形式定义权限矩阵:var permissionMap = { "doc_manage": ["admin", "manager"], "user_online": ["admin"], "new": ["all"] };这种静态配置意味着新增模块必须手动修改此处,无法动态加载权限规则。
3. 本地环境复现与最小化运行验证
3.1 构建兼容性沙箱:绕过IE依赖的现代浏览器方案
直接在Chrome/Firefox中打开index.html.bak会失败,因为document.all、window.showModalDialog等API已被移除。可行的复现路径如下:
第一步:启用IE兼容层
下载 Microsoft Edge Legacy(非Chromium版),或使用IE Mode(Edge设置 → 默认浏览器 → IE模式页面)。将file://协议加入IE模式站点列表,强制以IE7标准渲染。
第二步:注入Polyfill补丁
创建polyfill.js放入根目录,内容如下:
// polyfill.js - 修复关键IE API缺失 if (!document.all) { document.all = document.querySelectorAll('*'); // 仅作存在性检测,非功能替代 } if (!window.showModalDialog) { window.showModalDialog = function(url, args, features) { // 模拟弹窗:创建div遮罩层 + iframe加载 const modal = document.createElement('div'); modal.innerHTML = ` <div style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;"> <iframe src="${url}" style="width:80%;height:80%;margin:5% auto;display:block;"></iframe> </div>`; document.body.appendChild(modal); return null; }; }在index.html.bak的<head>中插入:
<script src="polyfill.js"></script>第三步:修正ActiveX调用降级逻辑rtxint.js.bak和tangerocx.js.bak中的window.external.*调用会报错。添加兜底判断:
// 在 rtxint.js.bak 开头追加 if (typeof window.external === 'undefined') { window.external = { RTXLogin: function() { console.warn('RTX集成已禁用,请检查浏览器插件'); }, RTXSendMsg: function() { /* 模拟发送逻辑 */ } }; }3.2 静态资源路径修复:解决.bak文件引用断裂问题
原始代码中大量使用绝对路径,如src="/js/calendar.js",但在本地文件系统中需转为相对路径。执行以下批量替换(Linux/macOS终端):
# 进入解压目录 cd OfficeAnywhere # 将所有 /js/ 替换为 ./js/(注意斜杠方向) find . -name "*.html" -o -name "*.js" | xargs sed -i '' 's/src="\/js\//src=".\//g' find . -name "*.html" -o -name "*.js" | xargs sed -i '' 's/href="\/css\//href=".\//g' # 修复 frame src 属性(menu.html 中的 mainFrame) sed -i '' 's/src="\/main\.html"/src="main.html"/g' menu.html提示:Windows用户可用 PowerShell 替代:
Get-ChildItem -Recurse -Include "*.html","*.js" | ForEach-Object { (Get-Content $_.FullName) -replace 'src="/js/', 'src="./' | Set-Content $_.FullName }
3.3 关键功能验证清单:确认核心链路是否打通
完成上述步骤后,按顺序验证以下5个关键节点,任一失败需回溯对应模块:
| 验证项 | 操作步骤 | 预期结果 | 排查重点 |
|---|---|---|---|
| 菜单导航 | 点击menu.html中“公文管理”链接 | mainFrame区域加载intel_view.html | 检查module.js.bak中loadModule()是否被调用,控制台是否有parent.mainFrame is undefined报错 |
| 附件上传 | 在new.html中点击“添加附件”按钮 | 弹出dlg_ins_image.html.bak对话框 | 确认dlg_ins_image.html.bak中<form enctype="multipart/form-data">存在,且action指向/upload.asp |
| 日历联动 | 在intel_view.html中点击日期控件 | 弹出calendar.js.bak渲染的日历面板 | 查看calendar.js.bak是否被intel_view.html的<script src="js/calendar.js.bak">正确引入 |
| 在线用户 | 打开user_online.html | 显示“正在加载...”后出现用户列表 | 检查浏览器Network面板,确认/online/userlist.asp请求返回JSON格式数据(如{"users":[{"name":"张三","status":"在线"}]}) |
| 主题切换 | 在theme.html中选择“蓝色皮肤” | topbar.html和pheader.html背景色变更 | 审视theme.html中document.getElementById('themeCSS').href是否指向正确的CSS文件路径 |
若user_online.html无法加载,大概率因后端接口缺失。此时可临时创建online/userlist.asp模拟响应(需IIS或ASP环境):
<% Response.ContentType = "application/json" Response.Write("{""users"":[" & _ "{""name"":""管理员"",""status"":""在线""}," & _ "{""name"":""张三"",""status"":""离开""}" & _ "]}") %>4. 二次开发实战:为shortcut.html添加动态快捷方式管理功能
4.1 分析原始快捷方式机制:静态HTML硬编码的局限性
shortcut.html当前结构极其简单:
<!-- shortcut.html 原始片段 --> <div class="shortcut-group"> <a href="new.html"><img src="images/icon_new.gif">新建</a> <a href="intel_view.html"><img src="images/icon_view.gif">查看</a> </div>所有快捷方式写死在HTML中,用户无法自定义。要实现“用户拖拽排序+增删快捷方式”,需突破三点限制:
- 存储层缺失:无本地存储或Cookie持久化机制;
- DOM操作原始:无事件委托,每个
<a>需单独绑定onclick; - 样式耦合严重:图标路径写死,无法动态替换。
4.2 实现方案:用localStorage + 事件委托重构快捷方式管理
第一步:改造HTML结构,添加管理入口
<!-- shortcut.html 修改后 --> <div class="shortcut-container"> <div class="shortcut-toolbar"> <button id="addShortcutBtn">+ 添加快捷方式</button> <button id="saveOrderBtn">保存排序</button> </div> <div id="shortcutList" class="shortcut-group"> <!-- 初始快捷方式由JS动态注入 --> </div> </div> <!-- 新增模态框HTML --> <div id="shortcutModal" style="display:none;"> <input type="text" id="shortcutName" placeholder="名称"> <input type="text" id="shortcutUrl" placeholder="URL"> <button onclick="addNewShortcut()">确定</button> </div>第二步:编写增强脚本shortcut-enhance.js
// shortcut-enhance.js const SHORTCUT_KEY = 'oa_shortcuts'; // 初始化:从localStorage读取或使用默认数据 function initShortcuts() { let shortcuts = JSON.parse(localStorage.getItem(SHORTCUT_KEY) || '[]'); if (shortcuts.length === 0) { shortcuts = [ { name: "新建", url: "new.html" }, { name: "查看", url: "intel_view.html" } ]; localStorage.setItem(SHORTCUT_KEY, JSON.stringify(shortcuts)); } renderShortcuts(shortcuts); } // 渲染快捷方式列表(支持拖拽排序) function renderShortcuts(shortcuts) { const container = document.getElementById('shortcutList'); container.innerHTML = ''; shortcuts.forEach((item, index) => { const link = document.createElement('a'); link.href = item.url; link.innerHTML = `<img src="images/icon_${item.name.toLowerCase()}.gif"> ${item.name}`; link.dataset.index = index; // 记录原始索引,用于拖拽 link.draggable = true; link.addEventListener('dragstart', handleDragStart); link.addEventListener('dragover', handleDragOver); link.addEventListener('drop', handleDrop); container.appendChild(link); }); } // 拖拽事件处理器(简化版,仅支持同列表内排序) let dragSrc = null; function handleDragStart(e) { dragSrc = e.target; e.dataTransfer.effectAllowed = 'move'; } function handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } function handleDrop(e) { e.preventDefault(); if (dragSrc !== e.target && e.target.tagName === 'A') { const shortcuts = JSON.parse(localStorage.getItem(SHORTCUT_KEY)); const fromIndex = parseInt(dragSrc.dataset.index); const toIndex = parseInt(e.target.dataset.index); // 数组重排:取出元素,插入新位置 const movedItem = shortcuts.splice(fromIndex, 1)[0]; if (fromIndex < toIndex) { shortcuts.splice(toIndex - 1, 0, movedItem); } else { shortcuts.splice(toIndex, 0, movedItem); } localStorage.setItem(SHORTCUT_KEY, JSON.stringify(shortcuts)); initShortcuts(); // 重新渲染 } } // 添加新快捷方式 function addNewShortcut() { const name = document.getElementById('shortcutName').value.trim(); const url = document.getElementById('shortcutUrl').value.trim(); if (!name || !url) return; const shortcuts = JSON.parse(localStorage.getItem(SHORTCUT_KEY)); shortcuts.push({ name, url }); localStorage.setItem(SHORTCUT_KEY, JSON.stringify(shortcuts)); document.getElementById('shortcutModal').style.display = 'none'; initShortcuts(); } // 绑定事件 document.getElementById('addShortcutBtn').onclick = () => { document.getElementById('shortcutModal').style.display = 'block'; }; document.getElementById('saveOrderBtn').onclick = () => { alert('排序已保存至本地存储'); }; // 页面加载时初始化 window.onload = initShortcuts;第三步:注入脚本并验证在shortcut.html底部添加:
<script src="shortcut-enhance.js"></script>逻辑说明:此方案完全规避后端依赖,所有数据存于浏览器
localStorage。dragstart/dragover/drop事件组合实现拖拽排序,dataset.index记录元素在数组中的原始位置,避免DOM索引与数据索引错位。addNewShortcut()函数确保URL合法性(生产环境应增加正则校验如^https?://或相对路径白名单)。
4.3 生产化加固:添加图标自动匹配与权限过滤
原始快捷方式图标路径为images/icon_新建.gif,但新增项无对应图标。可扩展renderShortcuts()函数:
// 在 renderShortcuts() 中替换图标生成逻辑 const iconMap = { "新建": "new", "查看": "view", "审批": "approve", "报表": "report" }; const iconName = iconMap[item.name] || 'default'; link.innerHTML = `<img src="images/icon_${iconName}.gif"> ${item.name}`;同时,为防止用户添加危险URL(如javascript:alert(1)),在addNewShortcut()中加入校验:
// 替换原校验逻辑 if (!name || !url || url.startsWith('javascript:') || url.includes('data:')) { alert('禁止添加危险协议URL'); return; }至此,shortcut.html已从静态展示页升级为具备用户个性化能力的动态模块,且所有变更均在前端完成,无需触碰后端代码——这正是理解老系统、赋能新需求的典型路径。
本文还有配套的精品资源,点击获取