首页 > 编程语言 >WxPython跨平台开发框架之动态菜单的管理和功能权限的控制

WxPython跨平台开发框架之动态菜单的管理和功能权限的控制

时间:2025-01-02 11:08:02浏览次数:1  
标签:菜单 self create 跨平台 WxPython 权限 id wx

在一个业务管理系统中,如果我们需要实现权限控制功能,我们需要定义好对应的权限功能点,然后在前端界面中对界面元素的可用性和功能点进行绑定,这样就可以在后台动态分配权限进行动态控制了,一般来说,权限功能点是针对角色进行控制的,也就是简称RBAC(Role Based Access Control)。对于登录系统后的用户,对用户的菜单(工具栏)、界面操作按钮的权限进行动态化的绑定和统一处理的操作过程,这样对于我们界面,只需要约定一些规则即可实现比较弹性化的操作,非常方便。本篇随笔介绍WxPython跨平台开发框架之动态菜单的管理和功能权限的控制。

1、权限管理系统的相关资源

权限管理系统主要的功能包括有:用户管理、组织机构管理、功能管理、角色管理和权限分配管理、菜单管理、系统类型管理、登录日志管理、操作日志管理、系统黑白名单管理等功能模块。对于每新增一个系统,我们只需要在权限管理系统中增加一个系统类型定义,以及相关的功能、菜单数据即可,非常方便管理。下图是一个简化的权限管理系统中涉及到角色相关资源的信息。

菜单资源和权限功能点是基于不同系统前端定义的资源,因此可以一套系统管理多个终端的菜单、功能点,以便实现更好的控制。

权限系统分了两级管理员用户:超级管理员和公司管理员。超级管理员可以管理整个集团或者整个系统的人员和相关信息(包括组织机构、角色、登陆日志、操作日志等信息的分级);公司管理员可以管理分子公司、事业单位处室/局级这样的组织机构的人员和相关信息。
分级管理组织机构、角色、用户等相关数据,能够减少管理员的相关工作,提高工作效率,并能增强权限管理系统对权限的控制和资源分配等管理,提高用户的认同感。

1)功能点定义和管理

权限功能点的管理就是对TB_Function的表的管理操作,这个表是我们定义用于系统控制的功能点。

权限功能点的管理为了展示它的树状结果,包括树列表的管理和明细列表的管理,如下图所示。

我们为了方便,在开始的时候,创建功能点的时候,一般通过批量添加的方式快速添加,如下界面所示。

这样系统会根据主控制标识,为各个操作(增加、删除、修改等)增加对应的操作标识。

 为对应角色添加相关的操作功能点

 

2)菜单资源的定义和管理

一般的业务系统,需要对菜单进行动态配置管理,通过后台菜单的配置和权限的指定,能够实现菜单的动态加载和权限验证。
因此菜单也是权限分配的一部分,为了有效管理菜单资源,我们把菜单放到权限管理系统中进行管理控制,可根据用户权限进行动态控制显示。

菜单的管理界面如下所示。

 菜单信息,包括名称和相关的图标定义等,这些需要再前端界面中构建工具栏显示用到的资源,而窗体类型则是定义我们需要动态展示的菜单对象的模块名和类名称。

 菜单定义好后,为了和实际用户进行关联,那么需要为角色添加相关的访问菜单,从而实现用户菜单的动态关联。

我们在开发初期,模拟定义了静态的菜单资源的信息,如下所示。

class ToolbarUtil:
    """工具栏菜单的创建类"""

    @staticmethod
    def create_tools():
        """创建工具栏菜单嵌套集合"""
        menus = [
            MenuInfo(
                id="01",
                label="用户管理",
                icon="user",
                path="views.frm_user.FrmUser",
            ),
            MenuInfo(
                id="02",
                label="组织机构管理",
                icon="organ",
                path="views.frm_ou.FrmOU",
            ),
            ...............
        ]
        return menus

那么我们有了动态定义的菜单和动态分配的功能后,我们就可以根据用户ID(角色ID)从后端系统接口中获得对应的菜单列表,然后统一展示即可。

    @staticmethod
    def create_tools_dynamic():
        """动态创建工具栏菜单"""

        # 同步获取菜单信息
        menus = api_menu.get_all_nodes_by_user_sync(
            settings.CurrentUser.id, settings.SystemType
        )
        # 定义字段映射
        field_mapping = {"name": "label", "winformtype": "path", "icon": "icon"}
        newmenus = map_with_dynamic_alias(menus, MenuInfo, field_mapping)

        return newmenus

这样我们在主窗体界面中,构建菜单的函数如下所示。

    def _create_toolbar(self, d="H"):
        """创建工具栏"""
        agwStyle = aui.AUI_TB_TEXT | aui.AUI_TB_OVERFLOW

        if d.upper() in ["V", "VERTICAL"]:
            agwStyle = aui.AUI_TB_TEXT | aui.AUI_TB_VERTICAL

        tb = aui.AuiToolBar(
            self, -1, wx.DefaultPosition, wx.DefaultSize, agwStyle=agwStyle
        )
        tb.SetToolBitmapSize(wx.Size(16, 16))

        # 动态创建工具栏
        toolbars = ToolbarUtil.create_tools_dynamic()

        for item in toolbars:
            tool_id = wx.NewIdRef()
            bitmap = get_bitmap(item.icon)
            tb.AddSimpleTool(
                tool_id=tool_id,
                label=item.label,
                bitmap=bitmap,
                short_help_string=item.tips if item.tips else item.label,
            )

            # 绑定事件
            self.Bind(
                wx.EVT_TOOL,
                partial(self.on_tool_event, item),  # 这里传递菜单信息
                id=tool_id,
            )

        # 增加系统常用按钮
        tb.AddSeparator()
        tb.AddSimpleTool(
            self.id_close_all, "关闭所有", images.delete_all.Bitmap, "关闭所有页面"
        )
        tb.AddSimpleTool(self.id_quit, "退出", images.close.Bitmap, "退出程序")

        self.Bind(
            wx.EVT_TOOL, lambda event: EventPub.show_about_dialog(), id=self.id_about
        )
        self.Bind(
            wx.EVT_TOOL, lambda event: EventPub.close_all_page(), id=self.id_close_all
        )
        tb.Realize()
        return tb

动态构建部分菜单后,并加上一些额外的操作功能项目即可。

要动态构建视图的实例,我们需要用到importlib库来导入模块,然后在模块中获得对应类型进行构造处理即可。

import importlib
import wx

"""要自动导入一个给定的类(比如 "views.testaui_panel.DocumentPanel")并在 Python 中使用它,
可以利用 importlib 库来动态地导入模块和类。importlib 允许你在运行时加载模块,
而不需要在代码中显式地使用 import 语句。"""

def dynamic_import(class_path: str):
    # 拆分字符串为模块路径和类名
    # print(f"Dynamic import: {class_path}")
    module_path, class_name = class_path.rsplit(".", 1)

    # 动态导入模块
    module = importlib.import_module(module_path)

    # 获取类对象
    class_obj = getattr(module, class_name)

    # 返回类对象
    return class_obj

例如可以通过下面类似的代码实现对话框展示或者窗口的展示。

def show_window(class_path: str, parent=None, **kwargs):
    """根据路径和基类对象,显示窗口"""
    window_class = dynamic_import(class_path)
    window = window_class(parent, **kwargs)
    # 判断传入的窗口是 wx.Dialog 还是 wx.Panel
    if isinstance(window, wx.Dialog):
        # 如果是 wx.Dialog,调用 ShowModal
        result = window.ShowModal()
        print(f"Dialog closed with result: {result}")
        window.Destroy()  # 在对话框关闭后销毁
    elif isinstance(window, wx.Panel):
        # 如果是 wx.Panel,调用 Show
        window.Show()
        print("Panel shown")
    else:
        print("Unknown window type")

 

2、功能点和按钮的控制处理

在上面的菜单资源中,直接和角色关联,在用户登录系统后,自动构建对应的菜单(工具栏)显示,而对于功能点和按钮的关联控制处理,我们也是可以采用类似的方式处理的。

首先我们在当前终端的全局设置对象里面定义好拥有的功能点对象,如下代码所示。

我们在用户成功登录系统(认证用户通过)后,获取用户的详细信息,以及相关联的资源(包括菜单、功能点、角色列表)等信息进行全局存储,方便在用到的地方进行调用判断,如下代码所示。

    async def SetLoginInfo(self):
        """设置登录信息"""
        userid = settings.AccessTokenResult.userid
        res = await api_user.Get(userid)
        if res.success:
            settings.CurrentUser = res.result
            await self.GetSystemType()
            await self.GetFunctionsByUser(userid)
            await self.GetRolesByUser(userid)

            EventPub.user_info_loaded()  # 发布用户信息加载完成事件

我们前面随笔介绍过,一般列表窗体是继承一个统一的基类的。

在我的WxPython跨平台开发框架中,我们对于常规窗体列表界面做了抽象处理,一般绝大多数的逻辑封装在基类上,基类提供一些可重写的函数给子类实现弹性化的处理。

如下是基类窗体和其他窗体之间的集成关系。

由于基类是通过泛型类型的定义的,因此可以对子类的相关逻辑进行统一抽象处理,以便实现常规功能的控制(包括新增、编辑、删除、批量添加、打印、导入、导出等)。

如对于客户信息的列表窗体,我们的视图类如下所示((通过代码生成工具Database2Sharp生成即可,之前随笔《在自家的代码生成工具中,增加对跨平台WxPython项目的前端代码生成,简直方便的不得了》介绍过)。

 我们可以看到,其中model是由子类传入的一个对象类型,那么我们在父类也可以统一进行获取它的名称进行处理即可。

在BaseListFrame类里面,我们定义一个判断是否有对应功能点的函数,如下所示。

    def HasAction(self, action_id: str, entity_name: str = None) -> bool:
        """判断权限缓存是否存在指定操作功能id

        :param action_id: 操作功能id, 如Add,Edit,Delete, Import,Export等
        :param entity_name: 实体名称, 默认根据self.model的名称获取并替换后缀Dto,如Customer,Product等
        :return: 存在返回True,否则返回False
        """
        if not action_id:
            return False

        if not entity_name:
            # 根据self.model的名称获取并替换后缀Dto
            entity_name = self.model.__qualname__.replace("Dto", "")

        # 判断权限缓存是否存在类似"Customer:Add"格式的字符串
        result = f"{entity_name}:{action_id}" in settings.FunctionDict
        return result

这样我们约定了模块和功能点的名称前缀后,就可以通用的处理判断了。

由于我们常规的新增、编辑、删除、导出等操作由父类统一生成标准的按钮,那么我们就可以根据是否有某些功能的标识进行构建了,如下代码所示。

    def _CreateCommonButtons(self, pane: wx.Window):
        """父类窗口统一创建通用按钮"""
        # 增加按钮
        btns_sizer = wx.BoxSizer(wx.HORIZONTAL)

        button_list = []
        # 设置图标和位置
        if EventFlags.EVT_Search & self.EVT_FLAGS:
            btn_search = ControlUtil.create_button(
                pane,
                "查询",
                "search",
                handler=self._on_first_page,
                is_async=True,
                id=wx.ID_FIND,
            )
            button_list.append(btn_search)
        if self.has_add:
            btn_add = ControlUtil.create_button(
                pane, "新增", "add", handler=self.OnAdd, is_async=True
            )
            button_list.append(btn_add)
        if self.has_edit:
            btn_edit = ControlUtil.create_button(
                pane, "编辑", "edit", handler=self.OnEdit, is_async=True
            )
            button_list.append(btn_edit)
        if self.has_delete:
            btn_delete = ControlUtil.create_button(
                pane, "批量删除", "delete", handler=self.OnDelete, is_async=True
            )
            button_list.append(btn_delete)
        if self.has_export:
            btn_export = ControlUtil.create_button(
                pane, "导出Excel", "xls", handler=self.OnExport, is_async=True
            )
            button_list.append(btn_export)

        # 将按钮添加到按钮组中
        for btn in button_list:
            btns_sizer.Add(btn, 0, wx.ALL, 3)

        return btns_sizer

我们这里使用了辅助类创建按钮 ControlUtil.create_button 方便控制相关的内容。类似的右键菜单我们也可以如此操作,判断权限是否拥有再构建即可。

    def _on_showmenu(self, event: wx.grid.GridEvent) -> None:
        """父类窗体的右键菜单处理"""

        # 创建右键菜单对象
        menu: wx.Menu = wx.Menu()

        if self.has_add:
            ControlUtil.create_menu(
                self, menu, "新增", icon_name="add", handler=self._OnMenuAdd
            )
        if self.has_edit:
            ControlUtil.create_menu(
                self, menu, "编辑", icon_name="edit", handler=self._OnMenuEdit
            )
        if self.has_delete:
            ControlUtil.create_menu(
                self, menu, "删除选中行", icon_name="delete", handler=self._OnMenuDelete
            )

        if self.has_export:
            ControlUtil.create_menu(
                self, menu, "导出Excel", icon_name="xls", handler=self._OnMenuExport
            )

          ............

这些都是父类窗体,对通用操作的权限判断和创建处理,如果对于子类窗体,我们也可以使用这些判断标识来增加一些额外的操作按钮或者菜单的。

如对于字典模块列表界面中,判读它是否有批量添加的操作权限,并添加相关的功能入口。如下所示。

    def CreateCustomMenus(self, parent_menu: wx.Menu) -> None:
        """子类重写该方法,创建自定义菜单"""
        # 父类已创建默认菜单,这里添加自定义菜单
        if self.has_batch_add:
            ControlUtil.create_menu(
                self,
                parent_menu,
                "批量添加字典",
                "batch_add",
                handler=self.OnMenuBatchAdd,
            )

对于查看/编辑/新增的窗体,它们也是有一个通用的编辑对话框基类的,因此也可以和列表的方式实现同样的功能控制,这里不在赘述。

以上就是对于登录系统后的用户,对用户的菜单(工具栏)、界面操作按钮的权限进行动态化的绑定和统一处理的操作过程,这样对于我们界面,只需要约定一些规则即可实现比较弹性化的操作,非常方便。

 

标签:菜单,self,create,跨平台,WxPython,权限,id,wx
From: https://www.cnblogs.com/wuhuacong/p/18643810

相关文章

  • 支持多种阅读格式!一个跨平台的电子书阅读器!
    大家好,我是Java陈序员。随着智能设备的流行,传统的纸质阅读渐渐被电子书阅读取代,不知道大家平时是怎么看电子书的呢?今天,给大家介绍一个跨平台的开源电子书阅读器!关注微信公众号:【Java陈序员】,获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。项目介绍koodo-re......
  • WxPython跨平台开发框架之前后端结合实现附件信息的上传及管理
    在使用wxPython开发跨平台应用时,结合后端实现附件信息的上传和管理是一种常见需求。WxPython跨平台开发框架是前后端分离的框架,前端采用的是WxPython+aiohttp 来构建跨平台的界面展示和处理,后端使用FastAPI,SQLAlchemy,Pydantic,Redis等技术构建的项目。后端数据库访问......
  • WxPython跨平台开发框架之图标选择界面
    在使用 wxPython开发跨平台桌面应用程序时,创建一个图标选择界面通常用于让用户从图标资源库中选择图标,我们可以把图标分为自定义的图标资源和系统的图标资源两大类,最终我们把它们整合一起使用,在框架的界面中使用,包括工具栏、右键菜单、按钮、图片等所需的地方显示,实现图文并茂的......
  • WPF使用TreeView和TabControl控件jian实现菜单的选择和切换
    一、页面添加TreeView和TabControl控件1.在MainWindow.xaml页面上添加TreeView控件,设置ItemsSource属性为ViewModel中的TreeList属性,添加<TreeView.ItemTemplate>,在该节点下添加<HierarchicalDataTemplate>,绑定ViewModel中的TreeList下子项中的Children属性,菜单名称绑定Header......
  • 基于HarmonyOS 5.0(NEXT)与SpringCloud架构的跨平台应用开发与服务集成研究【实战】
    HarmonyOSNEXT百川奔腾入海,千帆共进有为,鸿蒙与你共未来!鸿蒙生态千帆起HarmonyOSNEXT!全新HarmonyOSNEXT开发者预览版将在今年第一季度面向所有开发者开放。华为宣布将于2024年1月18日举行“鸿蒙生态千帆启航仪式”,揭秘鸿蒙生态和HarmonyOSNEXT进阶新篇章。2023年......
  • 使用canvas画一个树形菜单节点图
    在前端开发中,使用Canvas来绘制一个树形菜单节点图需要一些步骤。以下是一个简单的示例,展示如何使用HTML5的CanvasAPI来绘制一个简单的树形结构。首先,你需要在HTML中创建一个canvas元素:<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>TreeDia......
  • WxPython跨平台开发框架之列表数据的通用打印处理
    在WxPython跨平台开发框架中,我们大多数情况下,数据记录通过wx.Grid的数据表格进行展示,其中表格的数据记录的显示和相关处理,通过在基类窗体 BaseListFrame进行统一的处理,因此对于常规的数据记录打印,我们也可以在其中集成相关的打印处理,本篇随笔介绍如何利用WxPython内置的打印数据......
  • 使用CSS3制作一个扇形的菜单
    要制作一个扇形的菜单,你可以使用CSS3的transform属性和一些基本的HTML结构。以下是一个简单的示例,展示了如何创建一个扇形菜单:HTML结构:<divclass="fan-menu"><divclass="menu-item"id="item1">Item1</div><divclass="menu-item"id="ite......
  • WxPython跨平台开发框架之复杂界面内容的分拆和重组处理
    复杂界面内容的分拆和重组处理是现代软件开发中常见的做法,尤其在开发大型应用程序时,可以大幅提升开发效率、可维护性和用户体验。通过将复杂的界面内容分拆成更小的模块,每个模块都专注于单一功能或组件,代码更容易理解和维护。模块化的界面组件可以在多个地方复用,减少了重复开发的......
  • basicContext.js - 强大的纯Js上下文菜单插件
    basicContext.js是一款功能强大的纯Js上下文菜单插件。该js上下文菜单插件可以制作左键菜单,右键菜单,可以自定义菜单的位置。它还可以和jquery结合使用,非常方便。 在线预览  下载 使用方法在页面中引入样式文件basicContext.min.css,主题样式文件default.min.css和js文件b......