菜单不是写死的,是按角色过滤出来的:管理员全看到、前台看业务、保洁只看房态。 菜单过滤只是「看不见」,不是「进不去」——每一层都要再校验一次。 主窗体上「底部显示连接状态」这条很土,但用户自己就能判断是断网还是程序崩了。
18.5 登录成功、拿到了角色,这一节用这个角色决定「主窗体长什么样」。 教材 18.6 分三小节:主窗体模块概述、技术分析、实现过程。 「概述」里那句「主窗体是系统的中枢」是对的——它管着菜单、会话、子窗体的生命周期。 沙箱画不出窗体,所以这一段用表格 + 逐角色打印菜单来表达, 重点是「怎么筛」而不是「怎么摆」。
一、菜单不是写死的,是按角色过滤出来的
| 菜单 | 允许的角色 |
|---|---|
| 客房管理 | 管理员、前台 |
| 入住登记 | 管理员、前台 |
| 退房结账 | 管理员、前台 |
| 房价维护 | 管理员 |
| 报表 | 管理员 |
| 房态看板 | 管理员、前台、保洁 |
三种角色过滤后的结果:
管理员全看到;前台看到客房、入住、退房、房态四项;保洁只看到房态看板一项。
这个设计比「隐藏按钮」可靠——因为菜单级的过滤拦住了「进不去这个页面」。
按钮权限是第二道,不是第一道。
菜单表存哪? 小系统可以直接写在代码里的数组(像这里);
要动态配置就建一张 tb_menu 表(菜单名、父菜单、需要的角色)。
后者更灵活,但要多一个「菜单维护」界面——一期通常不值。
二、主窗体还该显示什么
| 区域 | 内容 | 为什么 |
|---|---|---|
| 顶部 | 系统名、当前操作员、当前时间 | 出问题时知道「谁在用」 |
| 左侧 | 菜单树 | 主导航 |
| 右侧 | 房态概览(空闲几间、已住几间) | 前台一屏就能报数 |
| 底部 | 数据库连接状态 | 连不上时立刻看得出来 |
「底部显示连接状态」这条很土,但很值。 桌面程序最常见的问题是「用了半小时突然报错」—— 如果底部一直显示连接状态,用户自己就能看出是网断了还是程序崩了。 「当前操作员」也一样。 前台交接班时经常两个人用同一台机器, 顶部显示「当前是谁登录的」能避免「用别人的账号干活」——而这种错在出问题时会让你查错方向。 右上角那一块「房态概览」是业务需要: 客人问「还有房吗」,前台不用点进任何页面就能答。 这就是「一屏能报数」的价值。
三、菜单过滤 ≠ 权限控制
| 层 | 校验什么 |
|---|---|
| 菜单层 | 这个角色看不看得到这个入口 |
| 窗体层 | 打开时再确认一次角色(防止被直接调用) |
| SQL 层 | 只给该角色能碰的表/视图的权限(16.3) |
菜单只是「看不见」,不是「进不去」。
用户完全可以直接调用那个窗体的功能(改配置、发请求、写脚本)。
所以三层都要校验,只做第一层叫「界面美化」。
这也是为什么真实系统里「后端校验」永远不能省——前端能做的事,用户都能绕过。
第三层最容易被忽略,但它最硬: 即使程序逻辑被绕过,
数据库权限还在那儿挡着(16.3 的 GRANT/DENY)。
所以「最小权限」不只是代码规范,是最后一道物理防线。
四、主窗体的生命周期
登录成功 → 建主窗体(按角色生成菜单)→ 用户点菜单 → 打开子窗体 → 关闭子窗体 → 退出时释放连接。 最容易漏的是最后一步: 程序退出不释放连接, 连接池里的连接一直挂着——几个人反复开关之后,库就「连接数满了」。 第二个容易漏的是「子窗体重复打开」: 用户连点两下菜单,开了两个一样的窗口,改完互相覆盖。 常规做法是「已经开着就激活它,不再新建」。 第三个是「模态 vs 非模态」: 登录窗体应该是模态(不关就不能操作别的), 而业务窗体通常是非模态(可以同时看两个)。这个选择错了,用户体验会非常别扭。
五、动手 Demo:三种角色,三套菜单
这一段把菜单定义成一个数组(每个菜单标上允许的角色), 然后按三种角色各过滤一次,打印出各自看到的菜单。 逻辑只有一行——但这一行决定了「谁能进哪个页面」。
menus = [
(u"客房管理", u"管理员", u"前台"),
(u"入住登记", u"管理员", u"前台"),
(u"退房结账", u"管理员", u"前台"),
(u"房价维护", u"管理员"),
(u"报表", u"管理员"),
(u"房态看板", u"管理员", u"前台", u"保洁"),
]
for role in (u"管理员", u"前台", u"保洁"):
vis = [m[0] for m in menus if role in m[1:]]
print(role, u"看到:", u"、".join(vis))
实跑输出:
--------------------------------------------------------------------
主窗体:菜单树与角色过滤
--------------------------------------------------------------------
① 菜单不是写死的,是按角色过滤出来的
--------------------------------------------------------------------
菜单表长这样(每个菜单标上「哪些角色能看」):
| 菜单 | 允许的角色 |
|---|---|
| 客房管理 | 管理员、前台 |
| 入住登记 | 管理员、前台 |
| 退房结账 | 管理员、前台 |
| 房价维护 | 管理员 |
| 报表 | 管理员 |
| 房态看板 | 管理员、前台、保洁 |
管理员 看到:客房管理、入住登记、退房结账、房价维护、报表、房态看板
前台 看到:客房管理、入住登记、退房结账、房态看板
保洁 看到:房态看板
三行输出就是「权限菜单」的全部逻辑:同一个窗体,按角色筛一遍菜单项。
保洁只看到「房态看板」,前台看到业务三个,管理员全看到。
这个设计比「隐藏按钮」可靠——因为菜单级的过滤拦住了「进不去这个页面」。
② 主窗体还该显示什么
--------------------------------------------------------------------
| 区域 | 内容 | 为什么 |
|---|---|---|
| 顶部 | 系统名、当前操作员、当前时间 | 出问题时知道「谁在用」 |
| 左侧 | 菜单树 | 主导航 |
| 右侧 | 房态概览(空闲几间、已住几间) | 前台一屏就能报数 |
| 底部 | 数据库连接状态 | 连不上时立刻看得出来 |
「底部显示连接状态」这条很土,但很值。
桌面程序最常见的问题是「用了半小时突然报错」——如果底部一直显示连接状态,
用户自己就能看出是网断了还是程序崩了。
③ 菜单过滤 ≠ 权限控制
--------------------------------------------------------------------
菜单只是「看不见」,不是「进不去」。
用户完全可以直接调用那个窗体的功能(改配置、发请求、写脚本)。
所以每一层都要校验:
| 层 | 校验什么 |
|---|---|
| 菜单层 | 这个角色看不看得到这个入口 |
| 窗体层 | 打开时再确认一次角色(防止被直接调用) |
| SQL 层 | 只给该角色能碰的表/视图的权限(16.3) |
三层都做才叫「权限」,只做第一层叫「界面美化」。这也是为什么真实系统里「后端校验」永远不能省。
④ 主窗体的生命周期
--------------------------------------------------------------------
登录成功 → 建主窗体(按角色生成菜单)→ 用户点菜单 → 打开子窗体 → 关闭子窗体 → 退出时释放连接。
最容易漏的是最后一步: 程序退出不释放连接,连接池里的连接一直挂着——几个人反复开关之后,
库就「连接数满了」。
m[1:] 这一句是「跳过菜单名,取后面的角色列表」。
role in m[1:] 就是过滤条件——一行代码,三种结果。
注意「保洁」只看到一项。 这不是「简化」,
是「她确实只需要这个」——多余的功能对她是干扰,也是风险(点错了可能改到别人的数据)。
这个数组写死在代码里,好处是简单、坏处是要改就得重新编译。
小系统没问题;一旦「菜单要经常变」,就该搬到表里——
判断标准:这个月改过几次?改过两次以上就该搬。
最后再强调一次:这只是一层。
真正要防的是「绕过菜单直接调功能」——
所以在打开每个子窗体时,还得再确认一次角色(18.6 第 ③ 段)。
30 秒实验
- 给菜单数组加一个「报表—员工工资」项,只允许管理员。 前台和保洁的菜单不会变——这就是「加功能不影响别人」。
- 把
menus改成从数据库表里读(建一张tb_menu),再跑一遍。 结果一样,但「改菜单不用改代码」——付出的代价是多一张表和一个维护界面。 - 想一想:保洁需要「房态看板」,但她需不需要「看到客人姓名」? 不需要。 这说明同一张表、 不同角色该看到不同的列——那就是「视图 + 不同授权」的活(16.3)。
常见误区
- 「把菜单都显示出来,点不动的灰掉」。 灰掉的菜单等于告诉别人「这里有东西」,而且一旦前端被绕过就全暴露。 该隐藏就隐藏,该拒绝就拒绝。
- 「菜单过滤做了就等于有权限了」。 菜单只挡「看不见」,挡不住「直接调用」。 每一层都要校验。
- 「主窗体上多放点信息显得专业」。 信息多了就没人看。 一屏只放「操作员、时间、房态、连接状态」就够。
自测三问(点开对答案)
1. 菜单是怎么按角色过滤的? —— **每个菜单标上「允许哪些角色」,登录后按当前角色筛一遍。** **逻辑就一行 `role in m[1:]`。** 2. 为什么「菜单过滤」不等于「权限控制」? —— **因为菜单只是「看不见」,用户可以直接调用那个功能。** **所以窗体层要再校验、SQL 层还要有权限。** 3. 主窗体底部为什么建议显示连接状态? —— **让用户自己判断「是网断了还是程序崩了」。** **桌面程序最常见的故障就是这种,而报错信息往往看不懂。**下一篇 18.7 讲业务模块:开房为什么必须用事务、退房账单怎么算, 以及一条「房态自查 SQL」怎么把「事务没做全」的脏数据抓出来。
- ☐ (本篇完,下一篇 18.7 业务模块设计)