JS Tree

时间:2022-07-03 22:21:41

在需要表示级联、层级的关系中,Tree作为最直观的表达方式常出现在组织架构、权限选择等层级关系中。典型的表现形试类似于:

JS Tree

一颗树的生成常常包括三个部分:1)数据库设计;2)后台程序;3)前端代码。那么,具体是怎么样的呢?

一、数据库设计

数据库设计对于树的表达常会包含这么几个类似意思的字段:

parent_id、id、name。

id:用于描述自己;

parent_id:用于描述自己的上一级;

name:用于描述自己的名称;

例如:总办(id=3,parent_id=0,name=总办),客户服务中心(id=10,parent_id=3,name=客户服务中心) ,客户部(id=12,parent_id=10,name=客户部)。由此建立了三级层级关系。

二、后台程序

对于一个层级,可能会用于描述部门关系,还可能用于描述菜单关系等等,不同的用途有不同的数据库设计字段。但为了程序的通用性,不可能为一了一个表或功能做单独的前端插件,因此就要在后台为前端插件需要使用到的字段做一个规范(或者在数据库设计中做规范)。在此为“树结构”在后台作这样的规范:

JS Tree
 1     /// <summary>
2
3 /// 层级
4
5 /// </summary>
6
7 public class vmHierarchy
8
9 {
10
11 public int id { get; set; }
12
13 public int pid { get; set; }
14
15 public string name { get; set; }
16
17 public object sub { get; set; }
18
19 public int status { get; set; }
20
21 }
JS Tree

Pid:用于描述上级关系;

Sub:用于描述子级关系;

Status:用于描述自身状态或特殊标识;

以做部门的层级关系为例:分为两个部分:

1)  取数据:

JS Tree
        /// <summary>

        /// 取部门层级

        /// </summary>

        /// <returns></returns>

        public List<vmHierarchy> GetDepartmentRelation()

        {

            List<vmHierarchy> vmdrlist = new List<vmHierarchy>();

            using (var ctx = DB.ContextForName(DBConnection.DefaultConnection).UseTransaction(true))

            {

                List<au_Department> adlist = new List<au_Department>();

                adlist = base.GetModelAll();

                vmdrlist = GetDepartmentRelationSub(1, adlist);

            }

            return vmdrlist;

        }
JS Tree

2) 定层级:

JS Tree
       /// <summary>

        /// 取部门层级-子级

        /// </summary>

        /// <param name="parentid"></param>

        /// <param name="adlist"></param>

        /// <returns></returns>

        public List<vmHierarchy> GetDepartmentRelationSub(int parentid, List<au_Department> adlist)

        {

            List<vmHierarchy> vmdrlist = new List<vmHierarchy>();

            List<au_Department> modellist = new List<au_Department>();

            modellist = adlist.Where(s => s.parent_id == parentid).OrderBy(s => s.sequence).ToList<au_Department>();

            foreach (au_Department item in modellist)

            {

                vmHierarchy vmmodel = new vmHierarchy();

                vmmodel.id = item.id;

                vmmodel.pid = item.parent_id;

                vmmodel.name = item.name;

                vmmodel.sub = GetDepartmentRelationSub(item.id, adlist);

                vmdrlist.Add(vmmodel);

            }

            return vmdrlist;

        }
JS Tree

由此在前端可以得到类似这样的关系数据:

JS Tree

三、前端代码

在与前端代码时,关于树的逻辑关系理清是最为主要的。

1)  如何生成当前层级关系和期子级关系,每个节点的子节点都不同。

2)  需要复选框吗?

3)  需要折叠吗?

4)  当点击一个节点:

  A:其下还有一串节点,要全部选中/全部不选中?

  B:当前点击中其它子节点都被选中了,再选中这个节点,如何影响上级的选中与不选中?

总结为:在有复选框的情况下,如何影响它的下级和上级节点关系?

5)  三个事件:

  A:单击选中复选框事件;

  B:单击取消选中复选框事件;

  C:单击行事件;

事件顺序?冒泡?必要事件与用户自定义事件?

有需求的童鞋可以看看下面的jQuery代码:

jQuery:

JS Tree
//Tree层级关系 Begin
; (function ($, window, document, undefined) {
var defaults = {
ajaxurl: '',//ajax取数据的url[data==null时有效]
data: null,//数据
erow: null,//点击行时要执行的事件function(){}
checbox: true,//是否有复选框
initunfold: true,//初始展开true 初始折叠false
event: {
selectedrows: null,//单击行时要执行的事件
checked: null,//选中了复选框时要执行的事件
unchecked: null//取消选中复选框时要执行的事件
},//事件
exchangebar: false,//是否有全部展开 全部折叠 按钮
onlyleafcheck: false//是否只有最终子节点才显示checkbox
};
$.fn.etree = function (options) {
var $that = $(this);
var _ops = $.extend(true, {}, defaults, options);
var $con = null, _activehtml;
var _lv = 0;
//初始化数据
function initdata() {
if (_ops.data !== null) {
generateTree(_ops.data);
} else {
$.ajax({
url: _ops.ajaxurl,
dataType: "JSON",
success: function (result) {
_ops.data = result;
generateTree(_ops.data);
}
});
}
};
function generateTree(_data) {
console.log(_data);
$con = $('<div></div>').appendTo($that);
var $ul = $('<ul class="e-tree-ul"></ul>').appendTo($con);
generateSub($con, _data, _lv);
initEvent();
if (_ops.initunfold == false) {
$con.find('.tge-inv').each(function () {
$(this).click();
})
}
};
function generateSub($e, _data, _lv) {
for (var i = 0; i < _data.length; i++) {
var _tdata = _data[i];
var $li = $('<li class="e-tree-li"></li>').appendTo($e);
var $p = $('<p class="e-tree-p" lv=' + _lv + '></p>').appendTo($li);
var $ti = $('<i class="tge-inv"></i>').appendTo($p);
var $tif = $('<i class="tge-invf"></i>').appendTo($p);
var $tc = null;
if (_ops.checbox == true) {
if (_ops.onlyleafcheck == true && _tdata.sub.length == 0) {
$tc = $('<i class="sck" tid="' + _tdata.id + '"></i>').appendTo($p);
} else if (_ops.onlyleafcheck == false) {
$tc = $('<i class="sck" tid="' + _tdata.id + '"></i>').appendTo($p);
}
}
var $ts = $('<span class="e-tree-s"></span>').html(_tdata.name).appendTo($p);
if (_tdata.sub.length > 0) {
$tif.addClass('tge-invfr');
var $ul = $('<ul class="e-tree-ul"></ul>').appendTo($li);
generateSub($ul, _tdata.sub, (_lv + 1));
$ti.addClass('tge-invd');
} else {
$tif.addClass('tge-invfd');
}
if ($tc != null) {
if (_tdata.status == 1) {
$tc.addClass('ck');//选中
} else {
$tc.addClass('nock');//未选中
}
}
}
};
function checksubordinate($e) {
var $slv = $e.parent('p').next('ul');
if ($e.hasClass('ck')) {
$slv.find('.sck').removeClass('nock').addClass('ck');
} else if ($e.hasClass('nock')) {
$slv.find('.sck').removeClass('ck').addClass('nock');
}
};
function checksuperior($e) {
var $plv = $e.parent('p').parent('li').parent('ul');
if ($plv.length > 0) {
var $sib = $plv.children('li');
var $sumckdcount = $sib.children('p').children('.ck').length;
var $scount = $sib.length - $sumckdcount;
var $ppsck = $plv.prev('p').children('.sck');
if ($scount == 0) {
$plv.prev('p').children('.sck').removeClass('nock').addClass('ck');
} else {
$plv.prev('p').children('.sck').removeClass('ck').addClass('nock');
}
if ($ppsck.length > 0) {
checksuperior($ppsck);
}
}
};
function checkselect($e) {
checksuperior($e);
checksubordinate($e);
};
function setAction($e) {
var $ts = $e;
var $te = $ts.parent('p');
var $thisid = parseInt($e.attr('tid'));
$con.find('.e-tree-active').removeClass('e-tree-active');
if ($ts.hasClass('ck')) {
$ts.removeClass('ck').addClass('nock');
checkselect($ts);
if (typeof _ops.event.unchecked == "function") {
_ops.event.unchecked($te, iselectedhtml());//活动项,唯一选中项|null
}
} else if ($ts.hasClass('nock')) {
$ts.removeClass('nock').addClass('ck');
checkselect($ts);
if (_ops.event.checked != null) {
_ops.event.checked($te, iselectedhtml());//活动项,唯一选中项|null
}
}
var $shtml = iselectedhtml();
if ($shtml != null) {
var $ck = $shtml.children('.ck');
var $sid = parseInt($shtml.attr('tid'));
$ck.parent('p').addClass('e-tree-active');
if ($thisid == $sid) {
setAction($ck);
}
}
};
function initEvent() {
$con.find('.tge-inv').bind('click', function (e) {
var $ts = $(this);
var $next = $ts.parent('p').next();
var $tsnext = $ts.next();
if ($ts.hasClass('tge-invd')) {
$ts.removeClass('tge-invd').addClass('tge-invr');
$next.slideUp();
if ($tsnext.hasClass('tge-invfr')) {
$tsnext.removeClass('tge-invfr').addClass('tge-invfd');
}
} else if ($ts.hasClass('tge-invr')) {
$ts.removeClass('tge-invr').addClass('tge-invd');
$next.slideDown();
if ($tsnext.hasClass('tge-invfd')) {
$tsnext.removeClass('tge-invfd').addClass('tge-invfr');
}
}
e.stopPropagation();
});
$con.find('.sck').bind('click', function (e) {
var $ts = $(this);
setAction($ts);
e.stopPropagation();
});
$con.find('.e-tree-p').bind('click', function () {
$(this).children('.sck').click();
});
if (typeof _ops.event.selectedrows == "function") {
$con.find('.e-tree-p').bind('click', function () {
var $te = $(this).context;
_ops.event.selectedrows($($te), iselectedhtml());//活动项,唯一选中项|null
});
}
$con.find('.e-tree-s').bind('click', function () {
return false;
});
};
function iactivehtml() {
_activehtml = $con.find('.e-tree-active').html();
return _activehtml;
};
function iactiveid() {
var $thtml = $con.find('.e-tree-active');
_activeid = parseInt($thtml.find('.sck').attr('tid'));
return _activeid;
};
function iselectedids() {
var _ids = new Array();
$con.find('.ck').each(function () {
_ids.push(parseInt($(this).attr('tid')));
});
return _ids;
};
function iselectedhtml() {
if (iselectedids().length == 1) {
return $con.find('.ck').parent('p');
} else {
return null;
}
};
function iselectedid() {
if (iselectedids().length == 1) {
return parseInt($con.find('.ck').attr('tid'));
} else {
return null;
}
};
initdata();
//活动项html [活动项:当前点击的项]
this.activehtml = function () {
return iactivehtml();
};
//活动项id [活动项:当前点击的项]
this.activeid = function () {
return iactiveid();
};
//获取所有选中的项id
this.selectedids = function () {
return iselectedids();
};
//当前唯一选中项的html 不满足唯一选中时,返回null
this.selectedhtml = function () {
return iselectedhtml();
};
//当前唯一选中项的id 不满足唯一选中时,返回null
this.selectedid = function () {
return iselectedid();
};
return this;
};
})(jQuery, window, document);
//Tree层级关系 End
JS Tree

使用:

JS Tree
@{
Layout = null;
}
@using UCMS_Commons;
@using UCMS_Model;
@using UCMS_Model.ViewModel; <!DOCTYPE html> <html>
<head>
<meta name="viewport" content="width=device-width" />
<title>DepatmentManage</title>
<link href="~/Content/themes/black/Css/eui.css" rel="stylesheet" />
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="~/Scripts/extendjs/jquery.cookie.js"></script>
<script src="~/Content/themes/black/Script/jquery.eui.js"></script>
<style type="text/css">
#dp-content {
width: 920px;
margin: 0 auto;
} #de-cont {
width: 400px;
border: 1px solid #E4E4E4;
padding: 20px;
display: inline-block;
vertical-align: top;
} #oper-cont {
width: 400px;
border: 1px solid #E4E4E4;
padding: 20px;
display: inline-block;
vertical-align: top;
margin-left: 28px;
} fieldset {
border: 1px solid #ddd;
} legend {
color: #9b9b9b;
}
</style>
<script type="text/javascript">
$(function () {
var actionUrl = {
'GetDpInfo': '/SystemCenter/GetDpInfo',
'adddp': '/SystemCenter/AddDp',
'deletedp': '/SystemCenter/DeleteDp',
'updatedp': '/SystemCenter/UpdateDp'
}; var t = JSON.parse('@Html.Raw(JSONNet.Serialize(Model))');
var $opername = $('#oper-name');
var $opersub = $('#oper-cont-sub');
var $operinfoo = $('#oper-info-o');
var $operinfoname = $('#oper-info-name');
var $errormsg = $('#error-msg');
var $deletedp = $('#deletedp');
var $dpname = $('#dpname');
var _status = -1, _ttname = ''; $('input[name="opertype"]').bind('click', function () {
var _index = parseInt($(this).attr('tp'));
$operinfoo.show();
switch (_index) {
case 0: { $opername.attr('readonly', 'readonly'); $opersub.show(); $opername.val(_ttname); $('#oper-info-o').show(); }; break;
case 1: { $opername.removeAttr('readonly'); $opersub.hide(); $opername.focus().val(''); _status = 1; }; break;
case 2: { $opername.removeAttr('readonly'); $opersub.hide(); $opername.focus().val(''); _status = 2; }; break;
case 3: { $opername.removeAttr('readonly'); $opersub.hide(); $opername.focus(); _status = 3; }; break;
}
});
$('#savedp').bind('click', function () {
switch (_status) {
case 1: { adddp(1); }; break;
case 2: { adddp(2); }; break;
case 3: { updatedp(); }; break;
}
});
$('#deletedp').bind('click', function () {
deletedp();
});
function adddp(_type) {
var _id = _dptree.activeid();
var _dpname = $opername.val();
if (_dpname.length == 0) {
$errormsg.html('请填写 名称');
}
else {
$.ajax({
url: actionUrl.adddp,
data: { "thisid": _id, "addtype": _type == 1 ? 0 : 1, "addname": _dpname },
success: function (result) {
var _result = $.eui.checkresult(result);
if (_result) {
window.location.reload();
}
}
});
}
};
function deletedp() {
var _ids = _dptree.selectedids();
var _idarray = JSON.stringify(_ids);
$.ajax({
url: actionUrl.deletedp,
data: { "ids": _idarray },
success: function (result) {
var _result = $.eui.checkresult(result);
if (_result) {
window.location.reload();
}
}
});
};
function updatedp() {
var _id = _dptree.activeid();
var _name = $opername.val();
if (_name.length == 0) {
$errormsg.html('请填写 名称');
}
else {
$.ajax({
url: actionUrl.updatedp,
data: { "thisid": _id, "newname": _name },
success: function (result) {
var _result = $.eui.checkresult(result);
if (_result) {
window.location.reload();
}
}
});
}
};
function getdpinfo(_id) {
$.ajax({
url: actionUrl.GetDpInfo,
data: { "id": _id },
success: function (result) {
var $os = $('#oper-sub').html('');
for (var i = 0; i < result.length; i++) {
$('<span style="padding: 0 10px;"></span>').html(result[i].name + '(' + result[i].percount + '人)').appendTo($os);
}
}
});
};
function schecked(el, sl) {
$('input[name="opertype"]').eq(0).click();
if (sl != null) { var _name = sl.find('.e-tree-s').html();
$dpname.html(_name);
$opername.val(_name);
_ttname = _name;
getdpinfo(_dptree.selectedid());
$operinfoo.show();
} else {
$dpname.html('已选中个数:' + _dptree.selectedids().length);
$operinfoo.hide();
}
};
var _dptree = $('#de-cont-d').etree({
data: t,
checbox: true,
onlyleafcheck: true,
event: {
selectedrows: function (el, sl) { },
checked: function (el, sl) {
var _tname = el.find('.e-tree-s').html();
$deletedp.show();
$errormsg.html('');
schecked(el, sl);
},
unchecked: function (el, sl) {
$operinfoo.hide();
schecked(el, sl);
}
}
});
});
</script>
</head>
<body>
<div id="dp-content">
<fieldset id="de-cont">
<legend>部门</legend>
<div id="de-cont-d"> </div>
</fieldset>
<fieldset id="oper-cont">
<legend>信息</legend>
<div id="oper-info-name" style="line-height: 32px;">
名称:<span id="dpname"></span>
<a href="javascript:void(0);" class="eui-btns" style="float:right;display:none;" id="deletedp">删除?</a>
</div>
<div id="oper-info-o" style="display:none;">
<hr class="hrgrey" />
<p id="oper-cont-d" class="eui-p50">
操作类别:
<label><input name="opertype" type="radio" value="" tp="0" checked="checked" />查看信息</label>
<label><input name="opertype" type="radio" value="" tp="1" />添加同级</label>
<label><input name="opertype" type="radio" value="" tp="2" />添加子级</label>
<label><input name="opertype" type="radio" value="" tp="3" />修改自身</label>
</p>
<p class="eui-p50">
名称:
<input type="text" name="name" value="" id="oper-name" class="eui-input" readonly="readonly" />
</p>
<p class="eui-p50" id="oper-cont-sub">
下辖职位:<span id="oper-sub"></span>
</p>
<label id="error-msg" class="error-msg"></label>
<a href="javascript:void(0);" class="eui-btns" style="float:right;bottom:0;" id="savedp">保存</a>
</div>
</fieldset>
</div>
</body>
</html>
JS Tree