ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WPF企业OA系统源码实战:C/S架构下的审批、考勤与二次开发避坑指南

WPF企业OA系统源码实战:C/S架构下的审批、考勤与二次开发避坑指南 简介这是一款基于C#与WPF技术的客户端服务器企业办公自动化系统源代码专门面向.NET桌面应用开发者、企业内部信息化实施人员可覆盖审批流程、工作计划、日程安排、考勤统计、工资管理等多个办公场景。资源包共五百八十二个文件582大小约十七点三兆字节17.3MB包括253个C#源代码文件、38个XAML界面布局文件、74个BAML编译界面资源还包含图标、配置、数据库备份等辅助文件能支持代码阅读与本地部署调试。已有七百九十人浏览学习。系统覆盖审批中心、计划中心、日程管理、考勤管理、劳资管理等模块并有人事管理、客户管理、内置记事本等附加工具整体细分十七个功能子模块工程目录清晰自带数据库备份采用Windows客户端运行方式可在Visual Studio 2012与SQL Server 2008 R2中快速搭建环境适合学习WPF桌面应用架构、数据绑定与OA业务逻辑实现。1. WPF 企业 OA 系统源码一套能跑通审批、考勤和劳资的 C/S 桌面办公套件先说结论这份 C# 基于 WPF 的企业 OA 系统源码不是那种只有登录页和导航栏的教学 demo而是能在 Windows 桌面上直接跑审批流程、计划管理、日程、考勤、劳资和人事客户管理的完整 C/S 应用。技术栈是老朋友组合——Visual Studio 2012 开发、WPF 界面、SQL Server 2008 R2 存数据4 大功能块、17 个子模块全都围绕一个目标让企业内部办公离线也能干局域网里点开就能用。适合正在给公司选内部 OA 的 IT 岗、需要完整项目做课设的开发者以及想把 WPF 从控件练习提升到业务落地的 C# 从业者。拿它做二次开发底子比从空窗口搭审批流快得多。2. 环境没对齐先别动手VS2012 与 SQL Server 2008 R2 的选型真相2.1 为什么锚定 VS2012 和 SQL Server 2008 R2很多第一次见到这份源码的人会问都已经到 .NET 8 的年代了为什么还锁 VS2012答案是这份 OA 的业务价值不在用了多新的框架而在在企业现有环境里能不能一声不响地跑起来。C/S 模式意味着客户端装一台算一台服务器端就是一台装了 SQL Server 2008 R2 的机器不需要 IIS、不需要域名、不需要外网局域网内数据交互全部走数据库连接串。这个约束放在十年前的企业内网是常态放在今天大量还没升级数据库的中小企业里依然成立。VS2012 对应的目标框架是 .NET Framework 4.0/4.5WPF 在这个版本上已经非常成熟XAML 的布局、绑定、模板、DataGrid 这些核心能力全都有企业 OA 需要的交互完全够用。如果强行用 VS2022 打开整个解决方案虽然大概率能编译通过但项目属性里的目标框架、ClickOnce 发布配置、甚至 SQL Server 的连接方式都可能有兼容性警告。我的血泪经验是第一遍复现就严格按 VS2012 SQL Server 2008 R2 来跑通之后再考虑升级不要一上来就把环境变量全部拉高那只会让排查范围失控。2.2 数据库附加与连接串动手前的第一道门槛这套 OA 是典型的数据库驱动型应用几乎所有模块都挂在同一套库上。源码包里的 OASystem 数据库文件.mdf 和 .ldf需要先附加到本地 SQL Server 实例。用 SQL Server Management Studio 连接到本机实例后执行附加脚本USE master; GO EXEC sp_attach_db dbname NOASystem, filename1 ND:\OASystem\OASystem.mdf, filename2 ND:\OASystem\OASystem_log.ldf; GO如果使用的是 SQL Server 2008 R2sp_attach_db可以直接用如果数据库文件是从另外一台机器拷过来的附加前要注意 .mdf 和 .ldf 文件必须放置在被 SQL Server 服务账户有读写权限的目录下比如默认的C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA。附加成功后打开解决方案里 WPF 主项目的 App.config 或 App.xaml.cs 中定义连接串的位置核心参数如下connectionStrings add nameOAConnection connectionStringData Source.;Initial CatalogOASystem;User IDsa;Password你的密码; providerNameSystem.Data.SqlClient / /connectionStringsData Source.表示本机实例如果数据库装在别的主机这里要改成对方的主机名或 IP比如Data Source192.168.1.100。Initial CatalogOASystem对应附加出来的库名User ID和Password换成实际登录名。我一般会在第一步就确认 SqlClient 能连通再启动 WPF 客户端否则界面里所有加载数据的窗口都会在构造函数里抛异常。2.3 BAML 与 ClickOnce编译输出里的文件到底有什么用资源包里那一串.game\application、.vshost.application和.baml文件会让不熟悉 WPF 部署的人误以为是源码本体。实际拆开看.application是 ClickOnce 部署清单.vshost.application是 Visual Studio 调试宿主用的清单这俩是发布产物不是源码.baml才是 WPF 的关键——它把 XAML 源文件编译成二进制资源运行时由PresentationFramework加载。源码包里出现的MainWindow.baml是主窗口JHZX.baml按命名习惯大概率是计划中心窗口NOTEPAD.baml对应内置记事本RSDAK_handle.baml应该是某个业务处理窗口。结论如果你拿到的包里只有这些编译产物没有.xaml源文件那就需要自己对照.baml对应的界面重建 XAML如果源工程里 xaml 文件齐全那这些 baml 会在编译时自动生成不需要手动处理。真正要盯紧的是 ClickOnce 的发布缓存——二次开发改了 XAML 重新编译但运行入口还是旧的.application清单界面很可能还是老样子这个问题留到避坑章节细说。3. 审批、计划、考勤、劳资四大模块的流程链路与权限落库3.1 审批中心从新建工作流到待办审核的权限链路审批中心是这套 OA 里业务密度最高的部分分新建工作流和待办工作流两块。新建工作流覆盖三类申请人事申请、项目申请、公文申请。发起人填写表单提交后记录进入待办池具备管理权限的角色才能看到并审核。数据库层的常见做法是两张表头表存一次申请的概要信息明细表存审批路径上的每个节点。一级模块子模块面向角色对应业务审批中心新建工作流 / 待办工作流全员发起管理角色审批人事、项目、公文申请计划中心销售计划 / 年度业绩经理权限计划制定与下达日程管理日程制定 / 历史查询全员个人日程考勤管理上下班签到 / 出勤统计全员签到人事调取考勤记录劳资管理工资出账统计经理及财务工资核算内置记事本个人便签全员日常记录系统设置用户权限 / 角色管理员账号与权限头表里一般会有申请类型、发起人、当前审批人、状态、创建时间这些字段明细表则记录每一步的审批人 ID 和处理结果。待办列表最核心的查询逻辑是按当前审批人 当前登录用户和状态 待审批过滤对应 SQL 大致是这样的SELECT w.ID, w.TypeName, w.Applicant, w.Summary, w.CreateTime FROM WF_Header w WHERE w.CurrentApprover userID AND w.Status N待审批 ORDER BY w.CreateTime DESC;userID是当前登录用户在系统里的 IDStatus建议用固定字符串或数字枚举不建议用中文做关联键编码差异偶尔会给你惹麻烦。这里的权限判断要放在服务端逻辑里做二次校验界面隐藏按钮只是体验设计不是安全边界。按钮点击响应建议用 WPF 命令绑定而不是事件硬编码后续加新流程类型时能少改一个窗口。3.2 计划中心与日程管理经理权限与全员权限落库计划中心明确标注为经理权限。经理在此制定销售计划、年度业绩等目标普通员工看不到计划管理入口。数据库侧通常是 Plan 头表和 Plan 明细表头表存计划名称、类型、制定人、制定日期明细表存每一条可量化的目标项。界面侧用 DataGrid 绑定计划集合非常合适WPF 的 DataGrid 自带排序、列宽调整、行选择配合 MVVM 的 ObservableCollection 能省掉大量刷新代码。日程管理的权限就完全不同它面向所有用户每个人只能操作自己的日程。这里有个最容易越权的设计点查询历史日程时SQL 的 WHERE 条件必须拼上当前用户 ID不能只按日期过滤。我在实际项目里不止一次见过把用户名传到前端、再由前端拼 SQL 的写法——这在内部 OA 里风险非常大因为客户端可以直接改参数。正确做法是把登录用户 ID 放在会话变量里后端查询强制注入。日程表至少要包含日期、时间段、内容、提醒开关这几个字段。WPF 端用 Calendar 控件选中日期后刷新右侧列表体验上和 Outlook 的日视图接近。提醒功能如果在 Sql Server Agent 里做作业也可以但桌面 OA 更常见的是客户端启动时检查一次当天日程弹窗提示。3.3 考勤与劳资签到打卡到工资出账的数据链路考勤管理拆成两块员工上下班签到以及人事权限管理者调取出勤记录做统计。签到的落库时间建议直接用数据库服务器时间而不是本机时间否则员工改自己电脑时钟就能伪造打卡这类功能我在 C/S 系统里见得太多了。Attendance 表最少要有这几列用户 ID、签到日期、上班签到时间、下班签退时间。出勤统计的汇总 SQL 大概是SELECT UserName, COUNT(*) AS WorkDays, SUM(DATEDIFF(MINUTE, CheckIn, CheckOut)) AS TotalMinutes FROM Attendance WHERE CheckDate BETWEEN startDate AND endDate GROUP BY UserName;DATEDIFF计算分钟数时只适合粗粒度统计如果考勤规则有午休不计工时迟到半小时内不扣款这类条件就得在后端逻辑里先做裁剪再聚合。劳资管理的数据来源在多数 OA 里并不自动关联考勤而是财务或经理手动录入出账项——这套资源的边界也在这里它把考勤记录和工资统计做在同一模块里但并不表示工资表会自动按考勤算出来二次开发时别对这个自动化抱过高期待。3.4 内置记事本与小工具藏在后台的辅助能力除四大主功能外OA 里还藏着内置记事本和其他小工具。记事本在 C/S 结构下实现成本很低一个多行 TextBox 加一个保存按钮数据存本地文本文件或数据库 Note 表都行关键是保存时机——最好在窗口关闭事件里强制写盘别只依赖用户点保存按钮。这部分对新手是很好的练手入口能练习基本的 WPF 布局、事件绑定和文件读写又不会像审批中心那样牵扯复杂的状态流转。系统设置模块则是整棵树的根用户管理、角色管理、权限分配都在这里。四个主功能块的权限最终都收敛到角色和菜单的映射表。我建议把角色表的主键 ID 通过全局静态变量保存各模块读取当前用户角色时统一走一个静态类避免每个窗口自己连库查一遍既慢又容易出现权限数据不一致。4. 把源码跑起来编译、数据库附加与首次登录核实4.1 编译前的检查清单启动项目、目标框架与资源文件拿到源码解决方案后先别急着 F5按下面的清单过一遍能省出一个下午的排查时间。首先是启动项目确认在解决方案管理器里右键解决方案确保启动项目是 WPF 客户端主工程而不是类库或数据库项目否则一启动就是不是可执行程序的报错。其次是目标框架VS2012 环境默认支持 .NET Framework 4 和 4.5右键项目 → 属性 → 应用程序确认目标框架是 4.x不要改成 .NET Core 或 5.0 以上版本这份源码的依赖程序集没有为高版本迁移做过适配。然后是资源文件检查把所有.xaml文件的生成操作确认改为Page复制到输出目录设为不复制如果某个窗口的 XAML 被标记成了Resource或None运行时加载 BAML 资源时会直接抛资源找不到异常。检查清单的最后一项是清理旧编译产物。源码包里自带的.application和.vshost.application是历史发布痕迹建议先把 bin 和 obj 目录清空一次再编译避免 ClickOnce 缓存里的旧清单干扰新程序集。4.2 数据库附加与登录名配置一次性到位第 2 章写过附加脚本这里说生产向的完整流程。附加库之后还要给 OA 创建专用登录名最好别用sa跑业务——一旦密码泄露整个库就裸奔了。在 SSMS 里执行USE [master]; GO CREATE LOGIN [OAUser] WITH PASSWORD NStrongPass_2024; GO USE [OASystem]; GO CREATE USER [OAUser] FOR LOGIN [OAUser]; GO ALTER ROLE [db_owner] ADD MEMBER [OAUser]; GO把连接串换成User IDOAUser;PasswordStrongPass_2024。这样做的直接好处是以后 SQL 注入或客户端被破解攻击者拿到的也不是sa权限。连接串测试可以在编译前先用 UDL 文件或 PowerShell 验证确认能连到OASystem库再启动程序powershell -Command Get-Process | Out-Null; (New-Object System.Data.SqlClient.SqlConnection(Data Source.;Initial CatalogOASystem;User IDOAUser;PasswordStrongPass_2024;)).Open(); Write-Host OKPowerShell 里先Open()再输出 OK能看到 OK 就代表连接串有效。执行这行测试时 PowerShell 执行策略如果受限直接改用cmd /c sqlcmd -S . -d OASystem -U OAUser -P StrongPass_2024 -Q SELECT 1也行。4.3 首次登录完整走查从签到到审批的一件事数据库通、编译过接下来就是把关键业务线走一遍。下面是第一次登录后应该完成的验证路径步骤操作预期结果失败排查方向1启动客户端输入管理员账号进入主界面菜单完整连接串失败 / 系统设置里账号未启用2在考勤管理点上班签到提示签到成功且时间为服务器时间表结构缺列 / 事务未提交3在审批中心发起一笔人事申请待办列表出现新记录WF_Header 写入失败 / 申请人 ID 为空4切换到管理者账号进入待办工作流看到刚发起的申请可操作审核当前审批人字段未更新5审核通过回到发起人视角申请状态变为已归档状态字段更新条件不匹配我通常会把第 2 步和第 4 步拆开验证因为考勤写的是 Attendance 表审批写的是 WF 系列表报错位置完全不一样。首次走查不需要把所有模块全部点一遍只要这几条主链路通剩下的就是体力活。5. WPF OA 二次开发避坑五个最容易翻车的现场5.1 数据库附加报错文件已在使用或版本升级现象附加 .mdf 时提示File already in use或者提示数据库版本高于当前实例支持版本。 原因文件已在使用一般是被另一个 SQL Server 实例占用或者上一轮附加异常导致文件句柄没释放版本报错则常见于把 SQL Server 2008 R2 的数据文件往 SQL Server 2016 上挂。 解决File already in use先用sys.dm_exec_sessions查一下有没有残留连接或者干脆重启一次 SQL Server 服务释放全部句柄版本不兼容就不要硬挂改用脚本迁移表结构或者装一个 2008 R2 实例把库还原到本机。5.2 改了 XAML 重新编译界面还是老版本ClickOnce 缓存现象改了 MainWindow.xaml 里的布局F5 运行看到的还是旧界面窗口标题、按钮文本全部没变。 原因VS 用 ClickOnce 方式启动时会优先执行.application清单而且本机指纹缓存里存了旧版本的部署清单导致最新程序集没被加载。 解决把C:\Users\你的用户名\AppData\Local\Apps\2.0下的 ClickOnce 缓存目录清掉或者放弃 ClickOnce 启动方式在项目属性 —— 发布里改为从文件系统安装直接运行bin\Debug\OASystem.exe。从那以后我一旦改了界面布局就绝不走 ClickOnce 调试路径强制执行 exe 确认效果。5.3 登录后直接白屏卡死渲染线程异常的黑匣子现象输入账号密码点登录界面卡住不动有时直接退出事件查看器里能看到System.Windows.Markup.XamlParseException。 原因WPF 在 UI 线程解析 XAML 时遇到无法识别的类型、资源路径错误或绑定的属性不存在任何异常都会让窗口渲染中断比较隐蔽的是某些自定义控件在 Application 启动早期就被 MainWindow 引用而资源字典注册顺序靠后。 解决在App.xaml.cs的构造函数里挂上DispatcherUnhandledException把异常详情写到本地日志文件这样白屏时至少能拿到栈信息如果确认是资源字典问题把自定义控件的ResourceDictionary合并提前到App.xaml里。5.4 审批状态不流转卡在待办不是玄学现象发起人提交申请管理者账号里永远看不到或者审批通过后发起人那边还是待审批。 原因大多数情况是WF_Header的CurrentApprover字段在提交时没有从发起人切到审批人另一个常见原因是发起人和审批使用的角色 ID 不是同一套审批者读的是另一个角色编码。 解决直接在 SQL Server 里跑一条SELECT * FROM WF_Header WHERE Applicant 当前用户ID观察CurrentApprover和Status的值。如果提交后Status还是 0 或者核准值没写进去就去发起界面的事件代码里查 UPDATE 语句的条件是否带了WHERE ID id AND Status 0这样的乐观锁多一次并发提交就多一层保障。5.5 考勤统计对不上服务器时间与客户端时间的拉锯现象某员工 8:59 打卡统计出来却是 8:00或者月底汇总总天数少一天。 原因考勤表写入的是客户端本地时间而员工的笔记本时钟和服务器差了 1 小时跨月场景更明显——31 号的签到被写成 1 号统计区间就漏了。 解决所有签到、签退时间统一改用数据库的GETDATE()SQL 类型用datetime默认约束或在存储过程里写入GETDATE()如果系统里有多个应用共用同一库还可以约定所有时间字段都存 UTC展示层再转本地时区。这样虽然实现上多一次转换但统计口径永远一致。6. 扩展一个自定义申请流程从模板表到待办刷新的完整手法如果审批中心是模板驱动那新增流程类型就非常轻量——在模板表里插一条记录前端分发逻辑加一个 case待办列表自动就能收敛到新类型。INSERT INTO WF_Template (TypeCode, TypeName, IsEnabled) VALUES (A0104, N采购申请, 1);TypeCode是整个审批中心的类型主键前端所有分发逻辑都以它为准。IsEnabled置 1 才能被发起界面拉到这个字段是新流程上线时最先要确认的。然后在发起工作流的窗口里对 TypeCode 做分发switch (selectedType.TypeCode) { case A0101: contentFrame.Navigate(new RSCQWindow(loginUser)); // 人事申请 break; case A0102: contentFrame.Navigate(new GWSQWindow(loginUser)); // 公文申请 break; case A0104: contentFrame.Navigate(new PSSQWindow(loginUser)); // 采购申请 break; }loginUser通过构造函数传入是因为新窗口需要知道发起人是谁才能在建单时把Applicant写进 WF_Header。最后把待办列表的查询条件复用第 3 章的 SQLTypeCode不需要改——它天然会把新类型的待办捞出来因为过滤条件是CurrentApprover和Status跟类型无关。这套手法的坑只有一个新窗口的 XAML 必须出现在主工程的编译资源里否则Navigate到窗口时同样会白屏。检查办法是看新建的.xaml文件生成操作是否自动变成了Page有时候从旧工程复制文件进来生成操作会残留为None跑起来才发现找不到类型。所以从那以后我每次给 OA 加完新流程都强制把发起 → 待办 → 审核 → 归档从头到尾走一遍再顺手确认一次 XAML 的生成操作这个习惯帮我挡住了很多次无头绪的翻车。希望这些拆解和填坑记录对你在同一套 WPF OA 源码上的落地上有帮助。本文还有配套的精品资源点击获取
返回列表