我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(三)图形化机构树
一、前言
组织机构是国内管理系统中很重要的一个概念,以前我们基本都是采用数据列表的形式展现,最多只是采用树形列表展现。虽然够用,但是如果能做成图形化当然是最好不过了。这里我不用任何图形控件,就用最原始的方式,用脚本画html的方式来展现一个图形化的机构树。
二、功能分析
当然我们除了生成图形的功能还有其它的维护机构数据的功能:
1、展现机构图形
2、新增组织机构
3、编辑组织机构
4、删除组织机构
5、给组织机构设置拥有的角色
三、具体实现
图形展示的实现前面已经说了用脚本画页面html,
新增修改节点则利用easyui的window或dialog控件弹出窗口编辑
设置角色也弹出窗口选择,除了展现图形其它的应该都没什么难度,后台还是采用webapi来处理数据。
1、还是从mvc控制器开始,新建一个名叫Organize的控制器,代码如下:
public class OrganizeController : Controller
{
public ActionResult Index()
{
var model = new sys_organizeService().GetModelList();
return View(model);
}
}
这里直接把机构数据取出来传到view中使用,当然也可以在前台脚本中ajax请求到webapi中获得。
2、接下来再创建对应的view
@{
ViewBag.Title = "title";
Layout = "~/Views/Shared/_Layout.cshtml";
} @section scripts{
@Scripts.Render("~/Resource/Sys/Organize.js") <script type="text/javascript">
var data = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));
ko.bindingViewModel(new viewModelOrganize(data));
</script>
}
<div class="z-toolbar">
<a id="a_refresh" href="#" plain="true" class="easyui-linkbutton" icon="icon-rfs" title="刷新" data-bind="click:refreshClick">刷新</a>
<a id="a_add" href="#" plain="true" class="easyui-linkbutton" icon="icon-add" title="新增" data-bind="click:addClick">新增</a>
<a id="a_edit" href="#" plain="true" class="easyui-linkbutton" icon="icon-edit" data-bind="click:editClick" title="编辑">编辑</a>
<a id="a_del" href="#" plain="true" class="easyui-linkbutton" icon="icon-cross" title="删除" data-bind="click:deleteClick">删除</a>
<a id="a_role" href="#" plain="true" class="easyui-linkbutton" icon="icon-group" title="设置角色" data-bind="click:roleClick">设置角色</a>
</div> <div class="wrapper" style="width: 100%; height: 100%; margin-top:15px;"></div> <script type="text/html" id="edit-template">
<div class="container_16" style="width:90%;margin:5%;">
<div class="grid_3 lbl" >上级机构</div>
<div class="grid_13 val" ><input class="z-text easyui-combotree" data-bind="datasource:combotreeData,combotreeValue:form.ParentCode" /><span data-bind="text:form.ParentCode" style="margin:5px;"></span></div>
<div class="grid_3 lbl">机构编码</div>
<div class="grid_13 val"><input class="z-txt easyui-validatebox" style="width:145px;" data-bind="value:form.OrganizeCode" data-options="required:true" /></div>
<div class="grid_3 lbl">机构名称</div>
<div class="grid_13 val"><input class="z-txt easyui-validatebox" style="width:145px;" data-bind="value:form.OrganizeName" data-options="required:true" /></div>
<div class="grid_3 lbl">备注</div>
<div class="grid_13 val"><textarea class="z-txt" style="width:220px;height:50px;" data-bind="value:form.Description" ></textarea></div>
<div class="clear"></div>
</div>
<div style="text-align:center;">
<a class="easyui-linkbutton" data-options="iconCls:'icon-ok'" data-bind="click:confirmClick" href="javascript:void(0)" >确定</a>
<a class="easyui-linkbutton" data-options="iconCls:'icon-cancel'" data-bind="click:cancelClick" href="javascript:void(0)">取消</a>
</div>
</script> <script type="text/html" id="setrole-template">
<style type="text/css">
.listview{ margin:0 !important;}
.listview li{width:100px !important;background-color:skyblue !important;float:left;margin:3px;}
</style>
<div style="margin:5px;height:370px;overflow:auto;" >
<div style="border-bottom:1px solid #CCC; margin-bottom:5px;">
<span class="icon32 icon-org32" style="padding-left:48px;font-weight:bold; font-size:14px;color:#666;" data-bind="text:OrganizeName">机构名称</span>
</div>
<div> 拥有角色(请点击勾选):</div>
<div class="metrouicss">
<ul class="listview"></ul>
</div>
</div>
<div style="text-align:center;">
<a class="easyui-linkbutton" data-options="iconCls:'icon-ok'" data-bind="click:confirmClick" href="javascript:void(0)" >确定</a>
<a class="easyui-linkbutton" data-options="iconCls:'icon-cancel'" data-bind="click:cancelClick" href="javascript:void(0)">取消</a>
</div>
</script> <script type="text/html" id="tr-node-template">
<tr class="tr-node"><td colspan="{0}">
<table align="center"border="1" cellpadding="2" cellspacing="0">
<tr>
<td class="td-node" id='td{3}' data-node='{2}' align="center" valign="top">{1}</td>
</tr>
</table>
</td></tr>
</script> <script type="text/html" id="tr-hline-template">
<tr class="tr-hline">
<td><table><tr><td class="treeempty"></td><td class="treedot"></td><td class="treedot"></td></tr></table></td>
<td class="treedot" colspan="{0}"></td>
<td><table><tr><td class="treedot"></td><td class="treedot"></td><td class="treempty"></td></tr></table></td>
</tr>
</script>
这个view还是很简单的,图形区只需要一个class="wrapper"的div即可,其它都是html模板,弹出窗口及生成图形时用到。
3、前端UI与数据交互的viewModel
/**
* 模块名:mms viewModel
* 程序名: organize.js
* Copyright(c) 2013-2015 liuhuisheng [ liuhuisheng.xm@gmail.com ]
**/ function viewModelOrganize(data) {
var self = this;
this.refreshClick = function () {
window.location.reload();
};
this.save = function (vm,win) {
var post = { form: ko.toJS(vm.form) };
com.ajax({
type: 'POST',
url: '/api/sys/organize/edit',
data: JSON.stringify(post),
success: function (d) {
com.message('success', '保存成功!');
win.dialog('close');
self.initGraph(d);
}
});
}
this.addClick = function () {
var defaults = { ParentCode: (self.selectNode || {}).OrganizeCode || 0 };
self.openDiloag("添加新机构", defaults, function (vm, win) {
if (com.formValidate(win)) {
vm.form._OrganizeCode = vm.form.OrganizeCode();
self.save(vm,win);
}
});
};
this.editClick = function () {
if (!self.selectNode) return com.message('warning', '请先选择一个机构!');
self.openDiloag("编辑机构-"+self.selectNode.OrganizeName,self.selectNode, function (vm, win) {
if (com.formValidate(win)) {
self.save(vm,win);
}
});
};
this.deleteClick = function () {
if (!self.selectNode) return com.message('warning', '请先选择一个机构!');
com.message('confirm', '确认要删除选中的机构吗?', function (b) {
if (b) {
com.ajax({
type: 'DELETE',
url: '/api/sys/organize/' + self.selectNode.OrganizeCode,
success: function (d) {
com.message('success', '删除成功!');
self.initGraph(d);
}
});
}
});
};
this.roleClick = function () {
if (!self.selectNode)
return com.message('warning', '请先选择一个机构!');
com.dialog({
title: "设置角色",
width: 600,
height: 450,
html: "#setrole-template",
viewModel: function (w) {
var thisRole = this;
this.OrganizeName = ko.observable(self.selectNode.OrganizeName);
com.loadCss('/Resource/css/metro/css/modern.css', parent.document);
com.ajax({
type: 'GET',
url: '/api/sys/organize/getrolewithorganizecheck/' + self.selectNode.OrganizeCode,
success: function (d) {
var ul = w.find(".listview");
for (var i in d)
ul.append(utils.formatString('<li role="{0}" class="{2}">{1}</li>', d[i].RoleCode, d[i].RoleName, d[i].Checked == 'true' ? 'selected' : ''));
ul.find("li").click(function () {
if ($(this).hasClass('selected'))
$(this).removeClass('selected');
else
$(this).addClass('selected');
});
}
});
this.confirmClick = function () {
var roles = [];
w.find("li.selected").each(function () {
roles.push({ RoleCode: $(this).attr('role') });
});
com.ajax({
url: '/api/sys/organize/editorganizeroles/' + self.selectNode.OrganizeCode,
data: ko.toJSON(roles),
success: function (d) {
thisRole.cancelClick();
com.message('success', '保存成功!');
}
});
};
this.cancelClick = function () {
w.dialog('close');
};
}
});
};
this.openDiloag = function (title,node,fnConfirm) {
com.dialog({
title: title,
height: 250,
width: 400,
html: "#edit-template",
viewModel: function (w) {
var that = this;
this.combotreeData = function () {
var list = utils.filterProperties(data, ['OrganizeCode as id', 'ParentCode as pid', 'OrganizeName as text']);
var treeData = utils.toTreeData(list, "id", "pid", "children");
treeData.unshift({ "id": 0, "text": "==请选择==" });
return treeData;
};
this.form = {
_OrganizeCode:node.OrganizeCode,
ParentCode: ko.observable(node.ParentCode),
OrganizeCode: ko.observable(node.OrganizeCode),
OrganizeName: ko.observable(node.OrganizeName),
Description: ko.observable(node.Description)
};
this.calcCode = function (v) { //新增时 自动计算OrganizeCode
if (!that.form._OrganizeCode) {
v = v == 0 ? "" : v;
var list = [], suffix;
for (var i in self.data) {
list.push(self.data[i].OrganizeCode);
}
for (var j = 1; j < 100; j++) {
suffix = j < 10 ? ("0" + j.toString()) : j.toString();
if ($.inArray(v + suffix,list) == -1)
break;
}
that.form.OrganizeCode(v + suffix);
}
}; this.form.ParentCode.subscribe(this.calcCode);
this.calcCode(node.ParentCode); this.confirmClick = function () {
fnConfirm(this,w);
};
this.cancelClick = function () {
w.dialog('close');
};
}
});
};
this.initGraph = function (data) {
self.data = data;
var wrapper = $("div.wrapper").empty();
var treeData = utils.toTreeData(data, "OrganizeCode", "ParentCode", "children"); var tb = renderTreeGraph(treeData);
tb.appendTo(wrapper); //绑定事件
$(wrapper).find(".td-node").click(function () {
$(".td-node").css({ "background-color": "#f6f6ff", "color": "" });
$(this).css({ "background-color": "#faffbe", "color": "#FF0000" });
self.selectNode = $(this).data("node");
}).dblclick(self.editClick);
if (self.selectNode) {
$("#td" + self.selectNode.OrganizeCode).css({ "background-color": "#faffbe", "color": "#FF0000" });
}
};
this.initGraph(data);
} function renderTreeGraph(treeData) {
//生成图形
var tb = $('<table class="tb-node" cellspacing="0" cellpadding="0" align="center" border="0" style="border-width:0px;border-collapse:collapse;margin:0 auto;vertical-align:top"></table>');
var tr = $('<tr></tr>');
for (var i in treeData) {
if (i > 0) $('<td> </td>').appendTo(tr);
$('<td style="vertical-align:top;text-align:center;"></td>').append(createChild(treeData[i])).appendTo(tr);
}
tr.appendTo(tb);
return tb;
} //递归生成机构树图形
function createChild(node, ischild) {
var length = (node.children || []).length;
var colspan = length * 2 - 1;
if (length == 0)
colspan = 1; var fnTrVert = function () {
var tr1 = $('<tr class="tr-vline"><td colspan="'+colspan+'"><img class="img-v" src="/Resource/images/tree/Tree_Vert.gif" ></td></tr>');
return tr1;
};
//1.创建容器
var tb = $('<table class="tb-node" cellspacing="0" cellpadding="0" align="center" border="0"></table>'); //2.如果本节点是子节点,添加竖线在节点上面
if (ischild) {
fnTrVert().appendTo(tb);
} // 3.添加本节点到图表
var tr3 = $("#tr-node-template").html();
tr3 = utils.formatString(tr3, colspan, node.OrganizeName, JSON.stringify(node),node.OrganizeCode);
$(tr3).appendTo(tb); // 4.增加上下级的连接线
if (length > 1) {
//增加本级连接下级的首节点竖线,在节点下方
fnTrVert().appendTo(tb); //增加本级连接下级的中间横线
var tr4 = $("#tr-hline-template").html();
tr4 = utils.formatString(tr4, colspan - 2);
$(tr4).appendTo(tb);
} //5.递归增加下级所有子节点到图表
if (length > 0) {
var tr5 = $('<tr></tr>'); for (var i in node.children) {
if (i > 0) {
$('<td</td>').appendTo(tr5);
}
$('<td></td>').append(createChild(node.children[i], true)).appendTo(tr5);
} tr5.appendTo(tb);
} return tb;
}
这段交互的逻辑和之前的viewModel一样,基本上定义了工具栏上的按钮对应的事件,通过data-bind绑定到UI上。熟悉knockoutjs的朋友就很容易理解了。
在新增机构时我有做一个处理根据新增节点的父节点自动计算本节点的编码。
生成图形的处理基本上是通过生成table来配合一些背景图片来实现图形展示,我注释写得很清楚了。
4、后台webapi中的数据处理
主要是viewModel中ajax调用的方法,有以下:
1 添加或编辑机构 POST /api/sys/organize/edit
2 删除机构 DELETE /api/sys/organize/id
3 获取机构拥有的角色 GET /api/sys/organize/getrolewithorganizecheck/id
4 保存机构拥有的角色 POST /api/sys/organize/editorganizeroles/id
那么我们的web api controller中:
public class OrganizeApiController : ApiController
{
public dynamic GetRoleWithOrganizeCheck(string id)
{
var service = new sys_organizeService();
return service.GetOrganizeRole(id);
} [System.Web.Http.HttpPost]
public dynamic Edit(dynamic data)
{
var formWrapper = RequestWrapper.Instance().LoadSettingXmlString(@"
<settings>
<table>
sys_organize
</table>
<where>
<field name='OrganizeCode' cp='equal' variable='_OrganizeCode'></field>
</where>
</settings>");
var service = new sys_organizeService();
service.Edit(formWrapper, null, data);
var result = service.GetModelList();
return result;
} public dynamic Delete(string id)
{
var service = new sys_organizeService();
service.RecursionDelete(id);
var result = service.GetModelList();
return result;
} [System.Web.Http.HttpPost]
public void EditOrganizeRoles(string id, dynamic data)
{
var service = new sys_organizeService();
service.SaveOrganizeRoles(id, data as JToken);
}
}
在webapi中后台的处理很简单,每个方法只有几句代码。至此我们已经大功告成了。
四、效果图
打开页面
选择总务后点击新增时,会自动把总务设置为父节点,并计算出新的机构编码
保存成功
双击修改总务节点
给总务节点设置角色
五、后述
由于一些私事,这段时间一直都没去办公室,今晚偷空写了这篇,感谢大家对我的支持。
这个系列的博客写了后很多博友问我要这个框架的源码,我们打算让大家团了,300人起团,人数到了就统一发给大家。
如果你觉得不错就帮我【推荐】一下吧,你的支持才是我能坚持写完这个系列文章的动力。
技术交流QQ群:群一:328510073(已满),群二:167813846,欢迎大家来交流,想团源码的朋友进群后把城市-名字-手机-QQ发给我即可。
系列博客链接:
我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(一)
我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(二)菜单导航
我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(三)图形化机构树的更多相关文章
- 我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(四)授权代码维护
一.前言 权限系统设计中,授权代码是用来控制数据访问权限的.授权代码说白了只是一树型结构的数据,没有什么其它的业务意义.那么这个页面的功能也就非常简单授权代码维护:新增.修改.删除授权代码数据. 二. ...
- 我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(二)菜单导航
一.前言 上篇博客中已经总体的说了一下权限系统的思路和表结构设计,那接下来我们就要进入正文了,先从菜单导航这个功能开始. 二.实现 这个页面基本不用什么需求分析了,大家都很明白,不过在这个页面要多维护 ...
- 我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(一)
一.前言 之前的博客一直都还没写到框架的实现及权限系统,今天开始写我的权限系统,我以前做过的项目基本上都有权限管理这个模块,但各个系统都会有一些不太一样,有些简单点,有些稍微复杂一点,一句话,我们做的 ...
- 我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(五)框架及Web项目的组件化
一.组件化印象 1.先给大家看一张截图 如果我告诉大家,这就是一个web管理系统发布后的所有内容,你们会不会觉得太简洁了,只有一个web.config.一个Global.asax文件,其它的都是dll ...
- 权限系统设计实现MVC4 + WebAPI + EasyUI + Knouckout
权限系统设计实现MVC4 + WebAPI + EasyUI + Knouckout (一) 一.前言 之前的博客一直都还没写到框架的实现及权限系统,今天开始写我的权限系统,我以前做过的项目基本上都有 ...
- MVC4 + WebAPI + EasyUI + Knockout-授权代码维护
我的权限系统设计实现MVC4 + WebAPI + EasyUI + Knockout(四)授权代码维护 一.前言 权限系统设计中,授权代码是用来控制数据访问权限的.授权代码说白了只是一树型结构的数据 ...
- SNF快速开发平台3.0之BS页面展示和九大优点-部分页面显示效果-Asp.net+MVC4.0+WebAPI+EasyUI+Knockout
一)经过多年的实践不断优化.精心维护.运行稳定.功能完善: 能经得起不同实施策略下客户的折腾,能满足各种情况下客户的复杂需求. 二)编码实现简单易懂.符合设计模式等理念: 上手快,见效快.方便维护,能 ...
- 建筑材料系统 ASP.NET MVC4.0 + WebAPI + EasyUI + Knockout 的架构设计开发
框架介绍: 1.基于 ASP.NET MVC4.0 + WebAPI + EasyUI + Knockout 的架构设计开发 2.采用MVC的框架模式,具有耦合性低.重用性高.生命周期成本低.可维护性 ...
- SNF快速开发平台3.0之-界面个性化配置+10种皮肤+7种菜单-Asp.net+MVC4.0+WebAPI+EasyUI+Knockout
一.个性配置-首页:可以进行拖动保存配置,下次登录时就会按配置的进行加载 二.个人配置页面 7种菜单用户可自定义配置,和预览效果 10种皮肤自定义配置,和预览效果 皮肤和菜单可以随意组合-部分截图: ...
随机推荐
- Erlang数据类型的表示和实现(4)——boxed 对象
Boxed 对象 Boxed 对象是比较复杂的对象,在 Erlang 中主标签为 10 的 Eterm 表示一个对 boxed 对象的引用.这个 Eterm 除去标签之后剩下的实际上是一个指针,指向具 ...
- Cocos2d-x解析XML文件,解决中文乱码
身处大天朝,必须学会的一项技能就是解决中文显示问题.这个字符问题还搞了我一天,以下是个人解决乱码问题的实践结果,希望可以给其他人一些帮助 读取xml文件代码: CCDictionary* messag ...
- 烂泥:KVM安装Windows Server 2008 R2使用virtio硬盘
本文首发于烂泥行天下. 在上一篇文章中,我们介绍了使用IDE硬盘来安装Windows Server 2008 R2,这篇文章我们来介绍使用virtio硬盘来安装Windows Server 2008 ...
- 4412开发板Android教程——Android平台简介
本文转自迅为开发板论坛:http://www.topeetboard.com Android和IOS Android的历史 Android公司 2005年Google收购成立22个月的Android公 ...
- MPP 架构数据库
Greenplum是一种基于postgresql的分布式数据库.其采用shared nothing架构(MPP),主机,操作系统,内存,存储都是自我控制的,不存在共享.也就是每个节点都是一个单独的数据 ...
- 用javascript得到客户端IP的新方法
javascript得到客户端IP的新方法 很久以来,我都是经过http://fw.qq.com/ipaddress来得到客户端用户的IP,这个方法简单.快速.实用 . 我们调用它的写法是: < ...
- POJ 3304 Segments --枚举,几何
题意: 给n条线段,问有没有一条直线,是每条线段到这条直线上的投影有一个公共点. 解法: 有公共点说明有一条这条直线的垂线过所有线段,要找一条直线过所有线段,等价于从所有线段中任选两端点形成的直线存在 ...
- android初学问题集
1. Manifest中的Application tag用途? 2. java中的组件设计模型是指什么? 3. java Bean是指什么? 4. Proxy-Stub设计模式又指的是什么? 有要的网 ...
- js 日期2015/12/22/16/45替换2015-12-22 16:45格式
js 日期2015/12/22/16/45替换2015-12-22 16:45格式 利用正则分组: function toChange(date) { var reg = /(\d+)\/(\d+)\ ...
- p点到(a,b)点两所在直线的垂点坐标及p点是否在(a,b)两点所在直线上
/// <summary> /// p点到(a,b)点两所在直线的垂点坐标 /// </summary> /// <p ...