首页 > 代码库 > 代码“小白”的温故而知新(一)-----OA管理系统
代码“小白”的温故而知新(一)-----OA管理系统
古人云:温故而知新。这是极好的,近来,作为一个小白,利用点空闲时间把之前几个月自己写过的一个作为练手的一个OA系统又重新拿来温习一番,希望在巩固基础之上能得到新的启示。现在回想起来,之前一个人,写写停停,不觉感叹,平时工作中团队的重要性以及个人力量的渺小。因为是练手的项目,整个系统从数据库到前端都是自己设计,所以不免显得有点寒碜,不喜勿喷,但该有的重点还是有的,至于美工,哈哈,简洁也是一种美不是吗,只能这样安慰自己了。
准备工作:
1.进行初步的需求分析(有四大板块:我的桌面,人力资源管理,考勤管理,工作流管理)
2.每个大板块下又分小块
(我的桌面---》每日考勤,提交申请,我的审批【高级员工】,我的申请;
人力资源----》员工管理,部门管理,职位管理,角色管理;
考勤管理----》工作日设置,工作时间设置,考勤记录查询;
工作流管理----》流程管理)
3.技术实现:ASP.NET MVC,EF,Jquery,T4,log4Net,MD5,Jquery-easy-uI,Sqlser2008,Membercache,后期用到spring.net
先看看前期大概的一个页面展示图:
主页面:
员工信息页面:
添加员工:
编辑员工:
--------------------------------------------------------------------------------------------------------------------------------------------------------------
正文:
好了,闲话不多说,先设计数据库吧,在设计数据库之前,有几点我们也需要注意,
1.对于表的命名一定要规范;
2.表的设计原则是一张表只记录一件事,如果表与表有关系的,通过外键关联。
在这里面,建表需要注意的是对菜单表ActionInfo的设计,因为它分级别,有一级菜单,二级菜单,对于这种表的设计,
ActionId 标识列 主键---》(渲染到前端的是id)
Title 标题 nvarchar(20)---》(text)
Leval 1 2 int 决定将来显示的层级图标(1代表菜单,2代表菜单项)
URL:允许为空,一级没有,二级有【通过这个url访问】
PrentId int not null 对于一级菜单,取值为0【重要】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下来就是搭建框架了,采用简单抽象工厂三层的项目结构
框架搭完,先做主页面,Home/Index Home控制下的Index的html代码 主页面
1 @using Model.Models 2 @{ 3 Layout = null; 4 EmployeeInfo emp = ViewData["user"] as EmployeeInfo; 5 6 } 7 8 <!DOCTYPE html> 9 10 <html> 11 <head> 12 <meta name="viewport" content="width=device-width" /> 13 <title>通达OA</title> 14 <script src=http://www.mamicode.com/"~/Scripts/jquery-1.7.1.js"></script> 15 <script src=http://www.mamicode.com/"~/Scripts/MyAjaxForm.js"></script> 16 <script src=http://www.mamicode.com/"~/Scripts/jquery.easyui.min.js"></script> 17 <script src=http://www.mamicode.com/"~/Scripts/easyui-lang-zh_CN.js"></script> 18 <link href=http://www.mamicode.com/"~/Content/easyui.css" rel="stylesheet" /> 19 <link href=http://www.mamicode.com/"~/Content/icon.css" rel="stylesheet" /> 20 <style type="text/css"> 21 a 22 { 23 text-decoration:none; 24 } 25 #emp 26 { 27 position:absolute; 28 color:red; 29 bottom:5px; 30 left:5px; 31 32 } 33 34 </style> 35 </head> 36 37 <body class="easyui-layout" onselectstart=" return false;"> 38 <div data-options="region:‘north‘,split:false" style="height: 110px; background: url(/Content/Images/OA2.png) no-repeat 0px -52px ;position:relative"> 39 <p id="emp">欢迎您的登录:@emp.EmpName <a href=http://www.mamicode.com/"/User/Login?state=false">[注销用户]</a></p> 40 </div> 41 42 <div data-options="region:‘west‘,title:‘导航菜单‘,split:false" style="width: 150px; background: url(/Content/Images/OA.jpg) no-repeat -20px "> 43 <ul id="tt"> 44 </ul> 45 </div> 46 47 <div data-options="region:‘center‘,title:‘主页面‘" style="padding: 5px; background: url(/Content/Images/OA3.jpg) no-repeat; opacity:0.88"> 48 <div id="p" style="padding: 10px;"> 49 50 </div> 51 </div> 52 53 @* 弹出的独立窗口 *@ 54 <div id="editwin"> 55 <iframe id="editframe" width="100%" frameborder="0" scrolling="no"> 56 57 </iframe> 58 </div> 59 60 61 <script type="text/javascript"> 62 63 //刷新页面 64 function afterSave() { 65 $("#editwin").window("close"); 66 $("#editframe").attr("src", "null"); 67 $("#dg").datagrid("reload"); 68 }; 69 70 71 $(‘#tt‘).tree({ 72 url: "/Action/LoadData", 73 checkbox: ‘true‘, 74 lines: ‘true‘, 75 dnd: ‘false‘, 76 animate: ‘true‘, 77 formatter: function (node) { 78 return ("[" + node.text + "]"); 79 }, 80 onClick: function (node) { 81 $(‘#p‘).panel({ 82 fit:true, 83 title: node.text, 84 href:node.url 85 86 }); 87 } 88 89 }); 90 91 //弹窗 92 function popEditWindow(caption,width,src) 93 { 94 $("#editwin").css("display", "block"); 95 $(‘#editwin‘).window({ 96 title: caption, 97 width: width, 98 resizable: false, 99 shadow:false, 100 modal: true 101 }); 102 103 $("#editframe").attr("src", src); 104 $("#editframe").load(function () { 105 var mainheight = $(this).contents().find("body").height() + 30; 106 $(this).height(mainheight); 107 }); 108 109 } 110 111 112 113 </script> 114 </body> 115 </html>
我整体的页面布局用的是Easy-UI,首页内容使用的是panel组件,菜单栏使用的是tree组件。
需要注意的是:
1.panel组件可以通过设置href属性从远程加载页面,但只会加载页面的body内容,可是像添加员工,编辑员工这样的,我们需要额外进行一些css样式的改变时,他却变得无效了,所以在面板之内,我添加了一个Iframe框架,Iframe 框架 有一个src属性,这个属性可以请求一个远程界面(完整的,独立的页面),你可以在里面做上你自己的css样式进行改变。
2.iframe框架如何自适应高度?
解决方案:
$("#editframe").load(function () {
var mainheight = $(this).contents().find("body").height() + 30;
$(this).height(mainheight);
});
3.如何在iframe框架内嵌的网页中去访问他所在的主页面的资源?
解决方案:
在主页面定义要使用的资源方法,然后再包含iframe的子页面中调用window.parent.方法名()。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Action/Index ||菜单栏代码
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Script.Serialization; using BLL; using Model.Models; namespace UI.Controllers { public class ActionController :BaseController { // // GET: /Action/ public ActionResult Index() { return View(); } /// <summary> /// 一次性加载菜单数据 /// </summary> /// /// <returns> /// 符合tree树形控件的json格式的对象 /// </returns> public ActionResult LoadData() { //没有进行权限分配,默认登录后加载所有菜单列表 List<ActionInfo> actionList = new ActionService().GetActionList(a => true); //调用存储过程进行过滤 // List<ActionInfo> actionList = new ActionService().GetActionListByEmp(this.user.EmpId); //采用EF查询进行过滤 //List<ActionInfo> actionList = new ActionService().GetActionByEmpId(this.user.EmpId); //构造符合tree树形控件的json格式的对象 List<MenuItem> menulist = new List<MenuItem>(); foreach (var item in actionList.Where(a=>a.Leval==1)) { //第一级菜单 MenuItem first = new MenuItem { id = item.ActionId, text = item.Title, state = "closed", url = null }; List<MenuItem> second = new List<MenuItem>(); List<ActionInfo> secondActionList = actionList.Where(a=>a.PrentId==item.ActionId).ToList(); foreach (var i in secondActionList) { second.Add(new MenuItem { id = i.ActionId, text = i.Title,state="open",url = i.URL }); } first.children = second; menulist.Add(first); } //JSON序列化 JavaScriptSerializer jss = new JavaScriptSerializer(); string result = jss.Serialize(menulist); return Content(result); } } //构造符合tree组件的实体类 public class MenuItem { public int id { get; set; } public string text { get; set; } public string state { get; set; } public string url { get; set; } public List<MenuItem> children { get; set; } } }
Employee ||控制器下代码
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Model.Models; using BLL; using UI.Models; using System.Web.Script.Serialization; using Newtonsoft.Json; namespace UI.Controllers { public class EmployeeController : BaseController { // // GET: /Employee/ public ActionResult List() { return View(); } public ActionResult LoadData(int page, int rows) { int totalCount = 0; List<EmployeeInfo> emplist = new EmployeeService().GetEmpListByPage(page, rows, ref totalCount); //解决方案二:使用Newtonsoft程序集 var result = JsonConvert.SerializeObject(new { total = totalCount, rows = emplist }); return Content(result); } [HttpGet] public ActionResult AddEmp(int? id) { string url = "/Employee/AddEmp"; List<DepartmentInfo> depList = new DepartmentService().GetDepList(d => true); List<PositionInfo> posList = new PositionService().GetPosList(p => true); EmployeeInfo emp; ViewEmpModel model = new ViewEmpModel(); if (id != null) { url = "/Employee/Update"; emp = new EmployeeService().GetDepByEmp(e => e.EmpId == id).SingleOrDefault(); model = new ViewEmpModel { EmpName = emp.EmpName, EmpBirthday = emp.EmpBirthday.ToString(), EmpEmail = emp.EmpEmail, EmpTelephone = emp.EmpTelephone, EmpUrl = url, LoginId = emp.LoginId, EmpGender = emp.EmpGender, DepId = emp.DepId, EmpId = emp.EmpId, PosId = emp.PosId, LoginPwd = emp.LoginPwd, DelFlag = emp.DelFlag }; ViewData["DepId"] = new SelectList(depList, "DepId", "DepName", emp.DepId); ViewData["PosId"] = new SelectList(posList, "PosId", "PosName", emp.PosId); } else { ViewData["DepId"] = new SelectList(depList, "DepId", "DepName"); ViewData["PosId"] = new SelectList(posList, "PosId", "PosName"); ViewData["deplist"] = depList; ViewData["poslist"] = posList; } ViewData.Model = model; return View(); } [HttpPost] public ActionResult AddEmp(EmployeeInfo emp) { emp.DelFlag = false; emp.LoginPwd = "888888"; bool flag = new EmployeeService().AddEmp(emp); return flag ? Content("ok") : Content("fail"); } //展示员工详细信息 public ActionResult ShowEmp(int id) { EmployeeInfo emp = new EmployeeService().GetDepByEmp(e => e.EmpId == id).SingleOrDefault(); List<AdjustPosition> adplist = new AdjustPositionService().GetAdpList(a => a.EmpId == emp.EmpId); List<AdjustDepartment> addlist = new AdjustDepartmentService().GetAddList(a => a.EmpId == emp.EmpId); string gender = emp.EmpGender ? "女" : "男"; ViewData["gender"] = gender; ViewData["adp"] = adplist; ViewData["add"]=addlist; return View(emp); } //修改员工 [HttpPost] public ActionResult Update(EmployeeInfo emp) { bool falg = new EmployeeService().UpdateEmp(emp); return falg ? Content("ok") : Content("fail"); } /// <summary> /// 删除员工 /// </summary> /// <param name="idlist"></param> /// <returns></returns> public ActionResult Delete(string idlist) { bool falg = new EmployeeService().DeleteEmpList(idlist); return falg ? Content("ok") : Content("fail"); } //员工调整 [HttpGet] public ActionResult AdjustEmp(int id) { List<DepartmentInfo> depList = new DepartmentService().GetDepList(d => true); List<PositionInfo> posList = new PositionService().GetPosList(p => true); EmployeeInfo emp = new EmployeeInfo(); emp = new EmployeeService().GetDepByEmp(e => e.EmpId == id).SingleOrDefault(); //如何将查询到的数据绑定到视图中的下拉列表框,第四个参数是选中的值 ViewData["DepId"] = new SelectList(depList, "DepId", "DepName", emp.DepId); ViewData["PosId"] = new SelectList(posList, "PosId", "PosName", emp.PosId); ViewData["deplist"] = depList; ViewData["poslist"] = posList; ViewData.Model = emp; ViewData["EmpId"]=emp.EmpId; ViewData["OldDepartmentId"]=emp.DepId; ViewData["OldPositionId"] = emp.PosId; return View(); } [HttpPost] public ActionResult Adjust(EmployeeInfo emp,AdjustDepartment add,AdjustPosition adp) { //接收原来部门的编号 int OldDepartmentId = Convert.ToInt32(Request["OldDepartmentId"]); //接收原来职位的编号 int OldPositionId = Convert.ToInt32(Request["OldPositionId"]); AdjustManagerService am = new AdjustManagerService(); adp.NewPositionId = emp.PosId; add.NewDepartmentId = emp.DepId; adp.AdjustTime = DateTime.Now; add.AdjustTime = DateTime.Now; bool falg; //职位调整并且部门没调整 if (add.NewDepartmentId == add.OldDepartmentId&&adp.NewPositionId != adp.OldPositionId) { falg = am.Add(adp); } //部门调整并且职位没调整 else if (add.NewDepartmentId != add.OldDepartmentId && adp.NewPositionId == adp.OldPositionId) { falg = am.Add(add); } //部门和职位都调整了 else if (add.NewDepartmentId != add.OldDepartmentId && adp.NewPositionId != adp.OldPositionId) { falg = am.Add(add,adp); } falg = new EmployeeService().UpdateEmp(emp); return falg ? Content("ok") : Content("fail"); } } }
给出Employee的List的视图代码,后面部门类似参考
1 @{ 2 Layout = null; 3 } 4 <!DOCTYPE html> 5 <html> 6 <head> 7 <meta name="viewport" content="width=device-width" /> 8 <title>员工界面</title> 9 </head> 10 <body> 11 <table id="dg"> 12 </table> 13 <script type="text/javascript"> 14 var fieldName; 15 $("#editwin").css("display", "none"); 16 $(‘#dg‘).datagrid({ 17 url: ‘/Employee/LoadData‘, 18 pagination: true,//page=1&rows=10 19 pageList: [10, 15, 20], 20 columns: [[ 21 { field: ‘check‘, checkbox: true, width: 50 }, 22 { field: ‘EmpId‘, title: ‘员工编号‘, width: 50 }, 23 { field: ‘EmpName‘, title: ‘员工姓名‘, width: 100 }, 24 { 25 field: ‘EmpGender‘, title: ‘员工性别‘, width: 100, formatter: function (value, row, index) { 26 return value ? "女" : "男"; 27 } 28 }, 29 { field: ‘EmpBirthday‘, title: ‘员工生日‘, width: 100 }, 30 { field: ‘EmpTelephone‘, title: ‘员工电话‘, width: 100 }, 31 { field: ‘EmpEmail‘, title: ‘邮件地址‘, width: 200 }, 32 { 33 field: ‘Operator‘, title: ‘员工操作‘, width: 300, 34 formatter: function () { 35 return "<a href=http://www.mamicode.com/‘#‘ class=‘editemp‘>编辑员工 | 详细信息 |员工调整 |分配权限 "; 36 } 37 } 38 39 ]], 40 toolbar: [{ 41 iconCls: ‘icon-add‘, 42 text: ‘添加员工‘, 43 handler: function () { 44 //调用在主页面Index中封装的弹窗函数 45 popEditWindow("添加员工", 400, "/Employee/AddEmp"); 46 } 47 }, ‘-‘, { 48 iconCls: ‘icon-cancel‘, 49 text: ‘删除员工‘, 50 handler: function () { 51 var rows = $("#dg").datagrid(‘getSelections‘); 52 if (rows.length == 0) { 53 $.messager.alert("提示", "请选择删除行!"); 54 return; 55 } 56 $.messager.confirm(‘确认‘, ‘确定删除吗?‘, function (r) { 57 if (r) { 58 //获取编号id,并以一定的规则做成字符串 59 var idlist = ""; 60 for (var i = 0; i < rows.length; i++) { 61 idlist = idlist + rows[i]["EmpId"] + ","; 62 } 63 //截取 64 idlist = idlist.substr(0, idlist.length - 1); 65 //异步请求发送要删除的字符串 66 $.ajax({ 67 url: "/Employee/Delete", 68 type: "post", 69 data: { "idlist": idlist }, 70 dataType: "text", 71 success: function (res) { 72 if (res == "ok") { 73 $("#dg").datagrid("reload"); 74 } 75 } 76 }) 77 } 78 }); 79 } 80 }], 81 onClickCell: function (rowIndex, field, value) { 82 fieldName = field; 83 }, 84 onSelect: function (rowIndex, rowData) { 85 if (fieldName == "Operator") { 86 $("#dg").datagrid("unselectRow", rowIndex); 87 } 88 }, 89 onl oadSuccess: function () { 90 $(".editemp").click(function () { 91 var empid = $(this).parents("tr").children("td").eq(1).text(); 92 popEditWindow("编辑员工", 400, "/Employee/AddEmp/"+empid); 93 }) 94 } 95 }); 96 97 //展示员工详细信息 98 function showEmp(node) { 99 $(function(){ 100 var empid = $(node).parents("tr").children().eq(1).text(); 101 popEditWindow("员工详细信息", 500, "/Employee/ShowEmp/" + empid); 102 }) 103 } 104 105 //员工调整弹窗 106 function AdjustEmp(node) { 107 $(function () { 108 var empid = $(node).parents("tr").children().eq(1).text(); 109 popEditWindow("员工调整", 400, "/Employee/AdjustEmp/" + empid); 110 }) 111 } 112 //分配权限 113 function SetAction(node) { 114 $(function () { 115 var empid = $(node).parents("tr").children().eq(1).text(); 116 popEditWindow("分配权限", 400, "/Role/SetRole/" + empid); 117 }) 118 } 119 </script> 120 121 </body> 122 123 </html>
【注】:
常见错误:检测到序列化循环引用
解决方案一:创建一个VO模型对象,不包含导航属性;
解决方法二:推荐使用牛顿JSON程序集
好了,今天的复习就到这。期待下一节的“温故而知新(二)”。
代码“小白”的温故而知新(一)-----OA管理系统