造车轮工程之权限树

写在前面:文章内容为本人的学习过程,路过大神不喜勿喷!

一、具体思路

既然是权限分配,根据RBAC原则,只需要给角色分配菜单权限,给人分配角色即可。
选择使用Python的Pyqt5绘制页面。
1.基础使用的参数变量

ownMenuDf = pd.Dataframe  # 以拥有的权限
haveNotMenuDf = pd.Dataframe  # 未拥有的权限
chooseMenuIds = [] # 选择需要添加的菜单ID列表
removeMenuIds = [] # 选择需要移除的菜单ID列表

2.构造权限的基础数据

def fillLimit(self, groupId):
    self.chooseMenuIds = []
    self.removeMenuIds = []
    self.ownMenuDf = 
    # 此处是获取以拥有的权限列表,数据来源自定
    # 此Dataframe中应包含一些关键列:
    # MENUID   菜单ID
    # MENU_ZN  菜单中文名称
    # PARENTID 父级节点ID
    # MENUNODE 是节点还是菜单的标识(此处0表示为菜单节点,1表示为菜单项)
    # MENUSORT 菜单顺序
    self.initTreeView(self.ownMenuDf, self.ownModel, self.ownLimitTreeView)
	
	# 筛选出已拥有的所有菜单节点
    self.directoryIds = self.ownMenuDf.loc[self.ownMenuDf['MENUNODE'] == '0', 'MENUID'].to_list()
    # 筛选出已拥有的所有菜单项
    pageIds = self.ownMenuDf.loc[self.ownMenuDf['MENUNODE'] == '1', 'MENUID'].to_list()
    # 从全部菜单中过滤掉已拥有的剩下的就是未拥有的菜单项
    self.haveNotMenuDf = self.menuDf[~self.menuDf['MENUID'].isin(pageIds)]
    # 提取出筛选完未拥有的菜单项的菜单节点的ID
    parent_ids = self.haveNotMenuDf.loc[self.haveNotMenuDf['MENUNODE'] == '0', 'MENUID'].to_list()
    # 通过递归方法判断这些未拥有的菜单节点下是否包含子菜单项,筛选出菜单节点下没有子菜单的菜单节点ID
    filterIds = [parent_id for parent_id in parent_ids if self.has_sub_menu(self.haveNotMenuDf, parent_id)]
    # 将这些节点从未拥有的菜单数据集中删除
    self.haveNotMenuDf = self.haveNotMenuDf[~self.haveNotMenuDf['MENUID'].isin(filterIds)]
    self.haveNotMenuDf.reset_index(inplace=True, drop=True)
    self.initTreeView(self.haveNotMenuDf, self.haveNotModel, self.allLimitTreeView)


#递归判断所有目录菜单下是否有子菜单项
def has_sub_menu(self, df, menuid):
    children = df[(df['PARENTID'] == menuid) & (df['MENUNODE'] == '1')]
    if len(children) == 0:
        return True
    for child_menuid in children['MENUID'].tolist():
        if self.has_sub_menu(df, child_menuid):
            return False
    return True

3.构造树形结构的菜单列表并开启复选框

# df为要形成树形结构的数据集
# model 为 QStandardItemModel 对象
# treeView 为 QTreeView 对象
def initTreeView(self, df, model, treeView):
    model.clear()
    # 设置表头信息
    model.setHorizontalHeaderLabels(['菜单列表', 'ID'])
    treeView.setModel(model)
    # 调整第一列的宽度
    treeView.header().resizeSection(0, 160)
    # 设置节点不可编辑
    treeView.setEditTriggers(QAbstractItemView.NoEditTriggers)
    # 设置第二列隐藏
    treeView.setColumnHidden(1, True)
    # 设置成有虚线连接的方式
    treeView.setStyle(QStyleFactory.create('windows'))
    # 开始构建菜单树
    self.build_menu(df, model, treeView)

# 递归构造菜单节点列表
def build_menu(self, df, model, treeView):
    # 筛选出所有的菜单节点
    root_nodes = df[df['PARENTID'] == '0'].sort_values("MENUSORT")
    root_nodes.reset_index(drop=True, inplace=True)
    for i, node in root_nodes.iterrows():
        # 创建QStandardItem菜单节点对象并添加到QStandardItemModel对象中
        item = QStandardItem(node['MENU_ZN'])
        item.setCheckable(True) # 开启复选框
        model.appendRow(item)
        model.setItem(i, 1, QStandardItem(node['MENUID']))
        self.add_children(node['MENUID'], df, item)
    # 将QStandardItemModel设置到相对应的QTreeView对象中
    treeView.setModel(model)

# 递归向菜单节点中添加菜单项
def add_children(self, parent_id, df, parent_item):
    # 根据父菜单ID,找出子菜单项
    children = df[df['PARENTID'] == parent_id].sort_values("MENUSORT")
    children.reset_index(drop=True, inplace=True)
    for i, child in children.iterrows():
        # 创建QStandardItem菜单项对象并添加到QStandardItemModel对象中
        child_item = QStandardItem(child['MENU_ZN'])
        child_item.setCheckable(True) # 开启复选框
        parent_item.appendRow(child_item)
        parent_item.setChild(child_item.index().row(), 1, QStandardItem(children.loc[i, 'MENUID']))
        self.add_children(child['MENUID'], df, child_item)

4.绑定属性结构复选框点击事件

self.未拥有的QTreeView.clicked.connect(self.addLimit)
self.已拥有的QTreeView.clicked.connect(self.removeLimit)

5.事件触发方法

# 添加权限
# index 为当前点击的 QModelIndex 对象
def addLimit(self, index):
    if index.column() == 0: # 表示只点击复选框有效
        item = self.haveNotModel.itemFromIndex(index)
        item.setCheckState(Qt.Checked if item.checkState() == Qt.Checked else Qt.Unchecked)
        # 递归处理子节点
        self.recursivelyHandleChildren(item, item.checkState())
        # 处理父节点状态
        self.handleParentStatus(item)
        # 触发视图更新
        self.haveNotModel.dataChanged.emit(index, index)
        # 向上递归收集选中的父菜单ID
        self.filterMenuIdsPre(item, item.checkState(), self.haveNotModel, self.chooseMenuIds, 'add')
        # 向下递归收集选中的子菜单ID
        self.filterMenuIdsSub(item, item.checkState(), self.haveNotModel, self.chooseMenuIds)
        print('chooseMenuIds', self.chooseMenuIds)

# 移除权限
# index 为当前点击的 QModelIndex 对象
def removeLimit(self, index):
    if index.column() == 0: # 表示只点击复选框有效
        item = self.ownModel.itemFromIndex(index)
        item.setCheckState(Qt.Checked if item.checkState() == Qt.Checked else Qt.Unchecked)
        # 递归处理子节点
        self.recursivelyHandleChildren(item, item.checkState())
        # 处理父节点状态
        self.handleParentStatus(item)
        # 触发视图更新
        self.ownModel.dataChanged.emit(index, index)
        # 向上递归收集选中的父菜单ID
        self.filterMenuIdsPre(item, item.checkState(), self.ownModel, self.removeMenuIds, 'del')
        # 向下递归收集选中的子菜单ID
        self.filterMenuIdsSub(item, item.checkState(), self.ownModel, self.removeMenuIds)
        print('removeMenuIds', self.removeMenuIds)

# 递归修改子节点选中状态
def recursivelyHandleChildren(self, item, state):
    if item.hasChildren():
        for i in range(item.rowCount()):
            child = item.child(i)
            child.setCheckState(state)
            self.recursivelyHandleChildren(child, state)

# 递归修改父级节点选中状态
def handleParentStatus(self, item):
    parent = item.parent()
    if parent:
        childStates = [parent.child(i).checkState() for i in range(parent.rowCount())]
        if all(state == Qt.Checked for state in childStates):
            parent.setCheckState(Qt.Checked)
        elif all(state == Qt.Unchecked for state in childStates):
            parent.setCheckState(Qt.Unchecked)
        else:
            parent.setCheckState(Qt.PartiallyChecked)

# 递归收集子菜单对应的菜单ID
def filterMenuIdsSub(self, item, state, model, menuIdList):
	# 通过点击的对象寻找在QStandardItemModel中的QModelIndex对象
    index = model.indexFromItem(item)
    # 通过QModelIndex对象获取到第1列的菜单ID
    click_menuId = str(index.sibling(index.row(), 1).data())
    # 如果当前点击是选中状态,则递归将选中的菜单ID收集到选中菜单列表中
    if state == Qt.Checked:
        if click_menuId not in menuIdList:
            menuIdList.append(click_menuId)
        if item.hasChildren():
            for i in range(item.rowCount()):
                self.filterMenuIdsSub(item.child(i), state, model, menuIdList)
    # 如果当前点击时取消选中,则递归将取消勾选的菜单ID从选中菜单列表中删除
    elif state == Qt.Unchecked:
        if click_menuId in menuIdList:
            menuIdList.remove(click_menuId)
        if item.hasChildren():
            for i in range(item.rowCount()):
                self.filterMenuIdsSub(item.child(i), state, model, menuIdList)

# 递归收集父级菜单的菜单ID
def filterMenuIdsPre(self, item, state, model, menuIdList, which):
	# 找到当前点击项的父级
    parent = item.parent()
    if not parent:
        return
    # 从对应的QStandardItemModel中寻找QModelIndex对象
    index = model.indexFromItem(parent)
    # 通过QModelIndex对象获取到第1列的菜单ID
    click_menuId = str(index.sibling(index.row(), 1).data())
    # 收集父级菜单节点下的所有子菜单的选择状态
    childStates = [parent.child(i).checkState() for i in range(parent.rowCount())]
    
    # 添加权限
    # ✅勾选状态
    # 所有子菜单中有选中的状态
    # 当前点击的菜单ID没有存在于添加权限的收集列表中
    # 当前点击的菜单ID没有存在于已拥有权限菜单中
    # 将父级菜单ID添加到添加权限的收集列表中
    if which == 'add' and state == Qt.Checked and (any(qtstate == Qt.Checked for qtstate in childStates) or any(qtstate == Qt.PartiallyChecked for qtstate in childStates)) and click_menuId not in menuIdList and click_menuId not in self.directoryIds:
        menuIdList.append(click_menuId)
    # 添加权限
    # ❎取消勾选
    # 所有子菜单都是未勾选状态
    # 当前点击的菜单ID存在于添加权限的收集列表中
    # 将父级菜单ID从添加权限的收集列表中移除
    elif which == 'add' and state == Qt.Unchecked and all(qtstate == Qt.Unchecked for qtstate in childStates) and click_menuId in menuIdList:
        menuIdList.remove(click_menuId)
    # 移除权限
    # ✅勾选状态
    # 所有子菜单都是勾选状态
    # 当前点击的菜单ID没有存在于删除权限的收集列表中
    # 将父级菜单ID添加到删除权限的收集列表中
    elif which == 'del' and state == Qt.Checked and all(qtstate == Qt.Checked for qtstate in childStates) and click_menuId not in menuIdList:
        menuIdList.append(click_menuId)
    # 移除权限
    # ❎取消勾选
    # 所有子菜单中存在任意一个为选中的状态
    # 将父级菜单ID从删除权限的收集列表中移除
    elif which == 'del' and state == Qt.Unchecked and (any(qtstate == Qt.Checked for qtstate in childStates) or any(qtstate == Qt.PartiallyChecked for qtstate in childStates)) and click_menuId in menuIdList:
        menuIdList.remove(click_menuId)

    self.filterMenuIdsPre(parent, state, model, menuIdList, which)

6.保存记录
根据需要将对应列表已固定形式记录下来,根据实际情报保存到数据库、文件或其他地方。
以便后续的查询和操作。
写在后面:
当前写完这个权限分配使用还没出现异常情况。若有逻辑错误还请大家及时指正。
由于本人使用的是企业内数据,菜单名称及列表包含企业数据,存在敏感数据。
核心思路和一些封装方法就是以上所述,欢迎评论或私信讨论优化方案。

最后的最后:希望文章可以帮到大家,路过大神不喜勿喷,我们共同学习,加油吧!!!!

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值