• 一个权限树的设计与实现


    如图,实现如下功能:

    1,加载自动读出当前权限,自动展开(基本功能)

    2,有一个选择/取消全部的功能(之前设计成独立的按钮,最后改成一个根目录的形式,如上图)

    3,任何父级与子级的全选关系动态关联,具体如下:

      3.1,选中父级,则子级全部选中,取消父级,则子级全部取消选择;

      3.2,选中父级的情况下,取消一个子级,或更多,父集的勾去动取消,(全选同样,只要有一个项没有选,全选自动取消)

      3.3,在子级未全部选中的情况下,一个个勾选,一旦子级全选,则父级自动变成选中

    因为这是一个限制用户能使用哪些菜单的设计,所以我们设计的就是菜单表:

    权限用逗号分隔的字符串表示,如3,4,5,17,19,22,对应了以这些值为id的菜单项

    而菜单表的设计为:

    主键ID,Int, 不自增

    菜单名,varchar

    菜单说明,varchar

    父级id,int

    是否最终目录,leaf,int----即只有树叶,才是不可能有下级枝、干的。

    权限表只存leaf值为1的选项。

    窗体加载的时候,把权限读成一个list,List生成后,开始递归循环每一级菜单;

    一旦该菜单的Id在权限的list里面存在(用contains方法),则把其node的状态设为选中;

    核心逻辑:所有的动态判断勾选状态的事件,全部关联到节点的AfterCheck事件,在这个事件中,我们智能判断其下级所有目录的勾选情况,其上级所有目录的勾选情况,

    核心递归逻辑走完,原则上,这个设计就完成了,所有的逻辑都放在这个逻辑里,比如全选,只要使第一级菜单全部选中/取消选中,这个“选中”的动作,则会自动触发aftercheck事件,自然就进入递归流程,不要显式写代码递归。

    所以核心方法有下面几个

    1,加载treeview后,写一个判断权限的方法(getCheckStatus),递归完成,每一个有权限的菜单,设一

    次node的checked为true,

      因为初始化的时候是从根目录往叶目录走,所以此次递归没有“设子菜单勾选情况”的逻辑,因为子菜单的状态还有待同数据库比对。

      因此每一个有权限的node被check后,只需判断上级目录是否应该勾选。

      正因为向上搜索和向下搜索要分开,并且有时候只执行一个方向,因此设计了两把锁:nodeParentLock, nodeChildLock,来控制是否向上和向下搜索。

        注:加载treeview之前,先生成一个“选择全部”的节点,然后所有的节点由它发散。

     2,得到父菜单状态的方法(getParentCheckStatus),

        传入当前node,判断是否有父节点,如果没有,则什么也不做。

      有父节点,则判断该父节点下勾选的个数和其子节点个数是否相等,如相等,则触发勾选事件,此时自动触发递归;

      如果不相等,并且之前的状态是勾选,则仍然触发check事件,由勾选变为不勾选,同样进入递归,判断父级的父级。

      而如果之前本来就没有勾选,说明从这个菜单往上,父级都是不可能勾选的,因此不处理。

        注:在设node.checked=true/false之前,要把向下搜索功能锁住,以免死循环,这点非常重要。在check事件结束之后,才把锁打开。

    3,得到子菜单状态的方法(getChildCheckStatus),

      传入当前node,判断是否叶节点,或者虽然不是叶节点,但该菜单下暂时没有子目录。

      如果不是,则还有子目录,就循环子目录,把状态不同的更新为相同的,此时又会进入递归(只要有check事件,一定会进入递归),同样,check之前要把向上的锁锁住,避免死循环,事件完成后,务必记得解锁。

      如果是叶节点或空节点,说明不需要继续得到子菜单,此时直接把向上搜索的开关打开,相当于,这个叶菜单在执行完“向下搜索”的任务后,因为向上的开关开了,就会立即从这个位置一直返到根目录,判断每一级是否该勾选。

      于是有人问了,为什么向上搜索的时候到了根目录,不要也这么往叶目录走一遍呢?

      我当初就是这么设计的,实验的时候才发现,你一旦主目录的值一变,你再往回走的话,就会把所有子菜单都变成主菜单的勾选状态(相当于执行了一次全选!),所以这是向上和向下唯一不同的地方,就是向上到顶后,就停在那,向下到底后,在最后一个菜单处,继续往回再跑一圈。这是个细节,怎么把握“到了最后一个目录,代码还没执行完,此时把向上的锁打开”,你仔细看看代码吧

    4,勾选事件发生,这里只有一个要注意的地方,就是上面说了,冒泡到顶后,向下的开关没打开,那么你再点任何一级目录的时候,那不是不会往下搜索了么?

        为了解决这个问题,我就这么判断,既然被我锁住了,就是不应该操作的,什么时候才要强制向下搜呢

    ?对了,鼠标点击!只有鼠标再次点一下checkbox,才会产生把下级都勾上的要求,这就明晰了,用

      if (e.Action == TreeViewAction.ByMouse),可以直接把鼠标事件筛选出来,然后再把向下的开关打开!这样,在你进行了自定义的根据勾选来增删权限制后,只要依次调用上述两个方法就行了。

    注意,顺序不能错,先向下搜索,再向上搜索,在向下搜索的最后一个事件里,打开向上的开关,这样就不会在每次调用到aftercheck事件的时候,都把最后才要执行的向上搜索执行一次了。

    说得比较混乱,看代码吧,是项目里的原生文件,懒得把不相关的摘掉的,很晚了。

    代码
    1 using System;
    2  using System.Collections.Generic;
    3  using System.ComponentModel;
    4  using System.Data;
    5  using System.Drawing;
    6  using System.Linq;
    7  using System.Text;
    8  using System.Windows.Forms;
    9
    10  namespace Wgi.MDSIN.WinApp.Forms
    11 {
    12 public partial class frmMenuAuth : Form
    13 {
    14
    15
    16 //业务操作类变量
    17   private IBLL.ISystemManage bll = BLLFactory.CreateSystemManageClass();
    18 //所有的供应商分类数据
    19   private List<Wgi.EntRole.Model.wgi_system_module> allcate;
    20 //员工id
    21   private int empid;
    22 private List<string> menuright;
    23 private bool nodeParentLock = true;//此锁为false,表示不需要更新父级状态
    24 private bool nodeChildLock = false;//表示不需要更新子级状态
    25
    26 public frmMenuAuth(int empid)
    27 {
    28 InitializeComponent();
    29 initData(empid);
    30 }
    31
    32 private void initData(int empid)
    33 {
    34 this.empid = empid;
    35 menuright = new Helper.rights().menuRights;
    36
    37 //加载菜单
    38 LoadAllCategory();
    39 //确定权限
    40 getCheckStatus(trvType.Nodes);
    41
    42 }
    43
    44 private void LoadAllCategory()
    45 {
    46 //清除所有的节点
    47 trvType.Nodes.Clear();
    48 TreeNode root = new TreeNode("选择全部");
    49 root.Tag = new Wgi.EntRole.Model.wgi_system_module();
    50 trvType.Nodes.Add(root);
    51
    52 //搜索用model
    53 Wgi.EntRole.Model.wgi_system_module m = new Wgi.EntRole.Model.wgi_system_module();
    54
    55 //取出所有的分类数据
    56 allcate = bll.MenuGetModelList(null);
    57 //取出父级分类数据
    58 var mods = from p in allcate where p.parent_id.Value == 0 select p;
    59
    60 TreeNode node = null;
    61 foreach (Wgi.EntRole.Model.wgi_system_module mod in mods)
    62 {
    63 node = new TreeNode(mod.mod_name);
    64 node.Tag = mod;
    65 node.ToolTipText = mod.remark;
    66
    67 root.Nodes.Add(node);
    68
    69 LoadSubCategory(mod.mod_id, node);
    70 }
    71 trvType.ExpandAll();
    72 }
    73
    74 //加载子菜单
    75 private void LoadSubCategory(int parent_id, TreeNode node)
    76 {
    77 TreeNode subnode = null;
    78 var mods = from p in allcate where p.parent_id.Value == parent_id select p;
    79 foreach (Wgi.EntRole.Model.wgi_system_module mod in mods)
    80 {
    81 subnode = new TreeNode(mod.mod_name);
    82 subnode.Tag = mod;
    83 subnode.ToolTipText = mod.remark;
    84 node.Nodes.Add(subnode);
    85 //递归调用
    86 LoadSubCategory(mod.mod_id, subnode);
    87 }
    88 }
    89
    90
    91 //渲染权限
    92 private void getCheckStatus(TreeNodeCollection tnc)
    93 {
    94 if (tnc.Count == 0)
    95 {
    96 return;
    97 }
    98 Wgi.EntRole.Model.wgi_system_module m = new Wgi.EntRole.Model.wgi_system_module();
    99 //判断权限
    100 foreach (TreeNode n in tnc)
    101 {
    102 m = n.Tag as Wgi.EntRole.Model.wgi_system_module;
    103 nodeChildLock = false;//首次加载,不需要向下更新,故每次循环前锁掉
    104 if (m.leaf.HasValue && m.leaf.Equals(1))//叶节点
    105 {
    106 if (menuright.Contains(m.mod_id.ToString()))
    107 {
    108 n.Checked = true;
    109 }
    110 }
    111 else//父节点,进入递归
    112 {
    113 getCheckStatus(n.Nodes);//非叶页点不代表一定有子节点,所以递归进入的时候要判断
    114 }
    115 }
    116 }
    117
    118 //更新父节点
    119 private void getParentCheckStatus(TreeNode n)
    120 {
    121 Wgi.EntRole.Model.wgi_system_module m = n.Tag as Wgi.EntRole.Model.wgi_system_module;
    122 if (n.Parent != null)
    123 {
    124 TreeNode pn = n.Parent;
    125 int i = 0;
    126 foreach (TreeNode item in pn.Nodes)
    127 {
    128 if (item.Checked)
    129 {
    130 i++;
    131 }
    132 }
    133 if (i == pn.Nodes.Count)
    134 {
    135 nodeChildLock = false;//向上找的时候不需要向下更新
    136 pn.Checked = true;
    137 nodeChildLock = true;//即时解锁
    138 }
    139 else
    140 {
    141 if (pn.Checked)//之前已经是未选择的状态,则不变
    142 {
    143 nodeChildLock = false;
    144 pn.Checked = false;
    145 nodeChildLock = true;
    146 }
    147 }
    148 }
    149 }
    150
    151 //更新子节点
    152 private void getChildNodeCheckStatus(TreeNode node)
    153 {
    154 if(node.Nodes.Count == 0)//叶节点或无子节点的树节点
    155 {
    156 //if (node.Parent != null)
    157 //{
    158 // if (node.Index == node.Parent.Nodes.Count - 1)
    159 // {
    160 // nodeParentLock = true;//解除父节点更新的锁
    161 // }
    162 //}
    163 //else
    164 //{
    165 nodeParentLock = true;//只有一个节点
    166 //}
    167 return;
    168 }
    169 bool check = node.Checked;
    170 foreach (TreeNode n in node.Nodes)
    171 {
    172 nodeParentLock = false;//向下找的时候不需要向上更新
    173 if (n.Checked != check)//状态不同才更新
    174 {
    175 n.Checked = check;
    176 }//没必要像更新父节点一样即时解锁,因为只有在最后一个子节点检查过后,才向上搜寻,所以在上面单独判断了
    177 nodeParentLock = true;
    178 }
    179 }
    180
    181
    182 //check事件
    183 private void trvType_AfterCheck(object sender, TreeViewEventArgs e)
    184 {
    185 if (e.Action == TreeViewAction.ByMouse)
    186 {
    187 nodeChildLock = true;//因为向上到根目录后不解锁,所以判断是鼠标点下的时候解锁
    188 }
    189 Wgi.EntRole.Model.wgi_system_module m = new Wgi.EntRole.Model.wgi_system_module();
    190 TreeNode n = e.Node;
    191 m = n.Tag as Wgi.EntRole.Model.wgi_system_module;
    192
    193 if (m.leaf.HasValue && m.leaf.Equals(1))//叶节点才更新权限
    194 {
    195 if (n.Checked)
    196 {
    197 if (!menuright.Contains(m.mod_id.ToString()))
    198 {
    199 menuright.Add(m.mod_id.ToString());
    200 }
    201 }
    202 else
    203 {
    204 if (menuright.Contains(m.mod_id.ToString()))
    205 {
    206 menuright.Remove(m.mod_id.ToString());
    207 }
    208 }
    209 }
    210
    211 //判断子节点的状态
    212 if (nodeChildLock)
    213 {
    214 getChildNodeCheckStatus(n);
    215 }
    216 //判断父节点的状态
    217 if (nodeParentLock)//更新状态被锁,则无需更新父节点
    218 {
    219 getParentCheckStatus(n);
    220 }
    221 }
    222
    223 //提交
    224 private void btnQuery_Click(object sender, EventArgs e)
    225 {
    226 Wgi.EntRole.Model.wgi_employee model = bll.EmployeeGetModel(empid);
    227 string rights = "";
    228 if (menuright.Count > 0)
    229 {
    230 rights = string.Join(",", menuright.ToArray());
    231 }
    232 model.menu_right = rights;
    233 bll.EmployeeEdit(model);
    234
    235 MessageBox.Show("权限更新成功!");
    236 this.DialogResult = DialogResult.OK;
    237 this.Close();
    238 }
    239
    240 }
    241 }
    242
  • 相关阅读:
    扩展中国剩余定理
    bzoj 3816&&uoj #41. [清华集训2014]矩阵变换
    THUSC2017
    bzoj 4521: [Cqoi2016]手机号码
    bzoj 4871: [Shoi2017]摧毁“树状图”
    bzoj 2300 : [HAOI2011]防线修建
    bzoj 3853 : GCD Array
    HEOI 2017 游记
    bzoj3926: [Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机模板
    bzoj 4310 跳蚤
  • 原文地址:https://www.cnblogs.com/walkerwang/p/1778517.html
Copyright © 2020-2023  润新知