新闻详情

新闻详情

首页 / 资讯中心 / 详情

T100二次开发窗口权限锁:从开窗失控到统一拦截

发布时间:2026/9/21 2:43:08来源:尧图网络
T100二次开发窗口权限锁:从开窗失控到统一拦截
1. 接到这个需求时我先做了哪些排查1.1 业务部门到底在抱怨什么事情起因是我们一个制造客户的财务主管找我说库存交易单上有个开窗查询功能操作员点一下就能看到产品的采购单价和最近一次供应商报价车间主任那边也觉得成本数据不该人人都能看。当时我第一反应是主程序不都做了功能权限控制吗为什么开窗还能漏结果翻了一圈才发现T100的标准权限体系管的是“程序级”——也就是你能不能进这支程序、能不能执行这支程序下的某个动作但程序员在代码里通过开窗方式弹出的子画面很多时候压根不在标准功能权限的校验范围内。开窗窗口一旦弹出里面的数据相当于跟着主程序的操作权限走主程序你能进窗口你就能看这就有问题了。这类需求听起来小但涉及一个很本质的问题T100二次开发场景下你的权限控制粒度到底能做到多细。功能权限管到菜单和动作业务权限管到单据和数据范围但“窗口级权限”在标准产品里几乎没有现成开关。如果业务部门的诉求只是“这个窗口别让某些人看到”那你只能走二次开发在开窗动作发生之前把权限锁住。1.2 排摸中发现的三类失控路径我接需求之后不是马上写代码而是先把现有的开窗情况盘了一遍。盘下来发现权限失控主要是三种路径第一种是主程序能进但里面弹出的子窗口没有单独控权。这是最常见的情况比如在库存交易程序里做了个开窗查单价开窗的代码是写在主程序4GL里的弹出来的窗口和主程序属于同一支程序权限表只认主程序编号窗口本身没有任何身份标识自然就谈不上控权。第二种是菜单自定义后绕过原控权逻辑。T100允许实施顾问通过菜单配置、快速键功能把程序入口重新组织一遍。有些开发者在做二次开发时喜欢把一些不常用的子画面做成独立程序再通过自定义菜单挂出去。挂出去之后主菜单层面的权限可能配了但程序内部通过CALL跳转过去的子窗口或者直接OPEN WINDOW的画面就没有二次把关。第三种是快速键或DIALOG动作直接触发开窗。T100的画面交互很多通过ON ACTION、快捷键处理开发者在写这些动作分支时注意力全放在业务逻辑上根本不会想到权限问题。这也是为什么权限锁不能靠“每支程序自觉”来解决必须在开窗的公共动作点上做收口。1.3 为什么不能直接改标准程序排查之后第一个冒出来的想法其实是“能不能在标准程序开窗口的代码前面加个判断”。但仔细一推敲就放弃了原因有三个T100的标准程序升级时会整体覆盖你改过的标准代码在版本更新后大概率会丢。就算你能把修改记录做成补丁重新打上去也架不住标准程序在不同版本间的代码结构变动。标准程序的4GL代码量非常大一支主程序几千行很正常在别人写好的几千行代码里找所有开窗点本身就是个高风险动作稍不留神就会误伤正常逻辑。标准程序里的窗口变量、窗口名称、界面控件是跟着per文件走的开发环境里如果没做源码版本管理你改了之后出了问题想还原都困难。所以结论很明确不碰标准程序做一个独立的权限管理机制以最小侵入的方式在开窗动作上做统一拦截。2. 为什么开窗功能会成为权限失控的重灾区2.1 T100开窗功能的技术本质要设计好这个权限锁得先搞清楚T100里开窗功能到底是怎么实现的。T100的客户端界面是用Genero语言开发的窗口的展示层由per文件定义一个窗口可以是一个独立的per布局也可以嵌在主程序的布局里。正常情况下主程序打开子窗口有两种常见写法一种是在同一个per文件里定义好几个窗口程序里通过OPEN WINDOW或者ui.Window来切换显示另一种是把子画面做成一个独立的per4gl组合用CALL或者RUN调过去。从权限角度来说不管哪种写法都存在一个共性窗口本身不是权限系统的校验单元。T100标准的权限表、用户角色、功能授权全都是以“程序”为维度分配权限的。这意味着一旦进入了主程序你在里面打开多少个子窗口系统都不会再问你一遍“这个人有没有权限看这个窗口”统一默认你有权限。还有一个值得注意的点很多时候开窗并不是为了输入数据而是为了“带值回显”。比如输入料号后弹个窗口让你选规格选完把值带回来。这种窗口如果包含敏感字段成本价、供应商信息就更危险因为操作员完全可以故意打开窗口去看数据然后取消关闭不做任何实际业务操作。流程上没有任何异常但敏感信息已经被看到了。2.2 权限断链的根因我在排查过程中总结出一个判断权限断链的根本原因是二次开发时的权限意识默认“向上看齐”——认为主程序有权限子窗口就有权限。但标准功能权限并没有对开窗做默认约束。举个实际例子我们有一套销售订单输入程序业务顾问在界面上加了一个按钮点了之后CALL一支客户信用查询程序。这支客户信用查询程序本身在菜单里是看不到的因为它没有挂到任何菜单上只是被CALL出来。标准权限系统检查的是“菜单里有没有授权”这种不在菜单体系里的子程序权限系统根本意识不到它的存在。更隐蔽的是有些二次开发程序用的是跨数据库查询——主程序是T100的开窗弹出的数据其实是从另一个数据库或另一个系统取出来的。这种场景下T100的权限体系更加管不到因为数据源已经超出了T100的会话控制范围。2.3 权限锁应该锁在哪一层结合上面的分析权限锁的落点就很清楚了不能放在每个窗口被打开之后做校验而是要在“开窗动作”这一层统一拦住。就像小区门禁不是在每户门口派人守着而是在单元门入口统一刷卡验证。具体到T100的二次开发就是在开窗的公共调用点做拦截。可能有人会问开窗代码分散在各支程序里怎么做到统一答案是把你自己的权限校验做成一个公共函数放到共享的4gl库里然后在每处开窗代码前调用。代码改动虽然还是分散的但校验逻辑是统一的。这个思路下窗口标识成了权限判断的“门牌号”校验函数拿着门牌号去权限表里查查到就放行查不到就按默认策略处理。3. 权限锁的完整设计思路3.1 权限表结构设计设计权限表的时候我优先考虑了多公司、多角色、时间生效范围这三个维度。T100本身就是多公司架构如果不把company放到权限判断条件里就会出现A公司授权了B公司也跟着放行的问题这在财务场景下特别危险。我用的表结构大致是这样的字段名类型说明companyvarchar(8)公司编号prog_idvarchar(30)主程序编号win_idvarchar(50)窗口标识取per里的window name或自定义常量user_idvarchar(20)用户账号为空表示对所有用户生效role_idvarchar(20)角色编号为空表示对所有角色生效auth_flagvarchar(1)Y允许N拒绝effect_datedate生效日期expire_datedate失效日期remarkvarchar(200)备注说明create_timedatetime创建时间update_timedatetime更新时间user_id和role_id留空这种设计是为了让权限配置能够覆盖不同粒度的业务场景。比如你可以配“程序pma100的winCustInfo窗口对用户zhangsan拒绝”也可以配“对QC_ROLE角色全部拒绝”还可以配“对所有人默认放行”。三条规则按优先级去匹配用户级 角色级 默认策略。这样管理员配权限时灵活度最大又不会把自己绕晕。3.2 默认策略选择白名单还是黑名单这是整个方案里最需要拍板的一个决策。我的建议是分两种情况如果是新开发的界面还没有任何用户用过建议采用白名单模式也就是默认拒绝只有明确配了允许的人才看得见。这样做的好处是权限从第一天起就是收紧的后续不用担心漏配。如果是存量系统改造不建议直接默认拒绝因为你不知道业务上还有哪些人在用这个窗口。上来就全部拒绝第二天客服电话会打爆。这种情况下我建议先设一个参数比如“未命中权限表时默认放行”默认保持黑名单模式只把明确需要限制的窗口和用户录入进去。业务部门验证没问题后再逐步把参数切到白名单模式做整体收紧。因为我这边接的是存量系统改造所以落地时选的是黑名单先行权限表里只维护那些需要拦截的敏感窗口。等所有敏感窗口都纳管了再考虑切白名单。3.3 公共校验函数的封装思路公共校验函数是整个方案的核心。我把它命名为winAuthCheck参数包括公司、程序编号、窗口标识函数返回TRUE或FALSE。在函数内部按照以下顺序处理先查系统参数开关。如果参数控没开启窗口权限校验功能直接返回TRUE保证代码上线初期不影响原有业务。再查用户级规则。当前登录用户在当前公司、当前程序、当前窗口下有没有配置过auth_flag如果有直接按配置返回。接着查角色级规则。这里需要把当前用户关联的所有角色都查出来只要有任何一个角色的权限是Y就放行如果所有角色都是N就拒绝。注意这里不能只查用户的默认角色因为T100里一个用户可能挂在多个角色下。然后是时间范围判断。即使权限规则命中了也要判断当前日期是否落在effect_date和expire_date之间避免过期权限一直生效。最后是未命中处理。用户级和角色级都没配的情况下读取默认策略参数决定是放行还是拒绝。为了提升性能函数首次校验成功后会把结果放到内存缓存里同一个会话内相同窗口重复打开时不再查库。3.4 与现有T100权限体系的衔接方式我这个方案不替代T100标准功能权限只做补充。所以设计上有个重要原则完全复用当前会话的用户信息和权限上下文不再让使用者做一次独立登录。在Genero环境中当前登录用户可以通过系统内置的会话变量拿到公司也可以通过运行时环境获取。校验函数不需要额外传用户参数更安全也更简洁。从使用角度来说管理员不需要维护两套用户体系只是在原来的程序级授权基础上多维护一张窗口权限表。我把校验函数做成一个独立模块后放在共享的4gl库里。这样整个系统里所有程序都能引用同一个函数权限逻辑有调整时只改一处不用每支程序都动。4. 完整代码实现与逐段解析4.1 建表与初始化数据首先在数据库里建立权限表和参数表。T100的数据库通常是Informix或Oracle我以标准的SQL语法给出来具体方言按你们的数据库环境微调即可。-- 窗口权限明细表 CREATE TABLE t100_win_auth ( company VARCHAR(8) NOT NULL, prog_id VARCHAR(30) NOT NULL, win_id VARCHAR(50) NOT NULL, user_id VARCHAR(20), role_id VARCHAR(20), auth_flag VARCHAR(1) NOT NULL, effect_date DATE, expire_date DATE, remark VARCHAR(200), create_time DATETIME YEAR TO SECOND, update_time DATETIME YEAR TO SECOND ); -- 权限参数表用于控制开关和默认策略 CREATE TABLE t100_win_auth_param ( param_name VARCHAR(50) NOT NULL, param_value VARCHAR(10), remark VARCHAR(200) ); -- 初始化参数 INSERT INTO t100_win_auth_param VALUES (WIN_AUTH_ENABLED, Y, 窗口权限校验总开关); INSERT INTO t100_win_auth_param VALUES (WIN_AUTH_DEFAULT, Y, 未命中规则时默认放行(Y/N));业务上使用这张表时管理员要做的就是在表里录规则。比如财务主管希望“库存交易程序里的成本价窗口”只允许财务经理看那就在表里插入一条company001prog_idINV100win_idwinCostInforole_idFIN_MGRauth_flagY再插入一条针对其他所有角色的role_id留空auth_flagN。两条规则配合实现的语义就是“财务经理以外的人看这个窗口时会被拦下来”。4.2 权限校验函数主体函数的实现是整个权限锁的核心。我用4GL写了一个精简但完整的版本放在共享的权限模块文件里例如pmautil.4gl。FUNCTION winAuthCheck(p_company, p_prog_id, p_win_id) RETURNS BOOLEAN DEFINE p_company VARCHAR(8) DEFINE p_prog_id VARCHAR(30) DEFINE p_win_id VARCHAR(50) DEFINE l_enabled VARCHAR(1) DEFINE l_default VARCHAR(1) DEFINE l_auth_flag VARCHAR(1) DEFINE l_user_id VARCHAR(20) DEFINE l_effect_date DATE DEFINE l_expire_date DATE DEFINE l_cnt INTEGER DEFINE l_role_cnt INTEGER DEFINE l_allowed BOOLEAN DEFINE l_sql VARCHAR(500) -- 1. 读取系统参数判断该功能是否启用 SELECT param_value INTO l_enabled FROM t100_win_auth_param WHERE param_name WIN_AUTH_ENABLED; IF l_enabled IS NULL OR l_enabled ! Y THEN RETURN TRUE END IF -- 2. 读取默认策略 SELECT param_value INTO l_default FROM t100_win_auth_param WHERE param_name WIN_AUTH_DEFAULT; IF l_default IS NULL THEN LET l_default Y END IF -- 3. 获取当前登录用户 -- T100会话变量中获取当前用户不同版本获取方式略有差异 LET l_user_id user -- 4. 检查用户级配置 LET l_allowed FALSE SELECT auth_flag, effect_date, expire_date INTO l_auth_flag, l_effect_date, l_expire_date FROM t100_win_auth WHERE company p_company AND prog_id p_prog_id AND win_id p_win_id AND user_id l_user_id AND (effect_date IS NULL OR effect_date TODAY) AND (expire_date IS NULL OR expire_date TODAY); IF l_auth_flag IS NOT NULL THEN IF l_auth_flag Y THEN RETURN TRUE ELSE RETURN FALSE END IF END IF -- 5. 检查角色级配置 -- 这里先查出当前用户拥有的所有角色再判断角色级规则 -- p_role_list存放当前用户的角色列表由其他模块传入或从用户角色表获取 FOREACH cursor_role FOR SELECT role_id INTO l_role_id FROM t100_user_role WHERE user_id l_user_id -- 若该角色下存在针对此窗口的规则则记录 SELECT auth_flag, effect_date, expire_date INTO l_auth_flag, l_effect_date, l_expire_date FROM t100_win_auth WHERE company p_company AND prog_id p_prog_id AND win_id p_win_id AND role_id l_role_id AND (effect_date IS NULL OR effect_date TODAY) AND (expire_date IS NULL OR expire_date TODAY); IF l_auth_flag IS NOT NULL THEN IF l_auth_flag Y THEN LET l_allowed TRUE EXIT FOREACH ELSE LET l_allowed FALSE EXIT FOREACH END IF END IF END FOREACH IF l_allowed THEN RETURN TRUE END IF -- 6. 如果用户级和角色级都没有命中 -- 这里检查是否存在任何针对该窗口的记录忽略具体用户/角色 SELECT COUNT(*) INTO l_cnt FROM t100_win_auth WHERE company p_company AND prog_id p_prog_id AND win_id p_win_id; -- 如果没有任何记录按默认策略 IF l_cnt 0 THEN IF l_default Y THEN RETURN TRUE ELSE RETURN FALSE END IF END IF -- 如果记录了但没匹配到用户/角色说明该窗口是受控的按拒绝处理 RETURN FALSE END FUNCTION这段代码的核心逻辑是“三级匹配”用户级 - 角色级 - 默认策略。用户在权限表里有明确配置直接按配置来没有明确配置但有角色级规则按角色规则来都没有按参数里的默认策略来。这里有一个细节值得注意如果权限表里已经有该窗口的记录但当前用户和角色都没匹配上函数会返回FALSE而不是继续按默认策略放行。这样设计的目的是防止管理员配了规则但没覆盖到某个用户时那个用户反而被默认放行了——既然这个窗口已经进了管控名单那就默认收紧。4.3 在主程序中接入校验函数写好了使用方式非常简单。在主程序准备弹出窗口的位置加一个IF判断即可。以库存交易程序为例原始开窗代码可能是这样-- 原代码直接打开窗口 OPEN WINDOW w_cost FROM 5,5 TO 25,100 ...加上权限锁之后变成-- 判断是否具有窗口权限 IF NOT winAuthCheck(001, INV100, winCostInfo) THEN MESSAGE 您没有权限查看成本信息窗口请联系管理员 RETURN END IF -- 通过校验打开窗口 OPEN WINDOW w_cost FROM 5,5 TO 25,100 ...这里有个细节win_id传的是“winCostInfo”这个值不是随便起的必须和per文件里定义的窗口名称保持一致。为什么要保持一致因为窗口名称是程序里唯一稳定的标识显示标题可以改多语言但window name不会变。如果代码里没有直接OPEN WINDOW而是通过CALL调了另一支独立画面的程序那窗口标识就用那支被调程序的程序编号比如CALL pma101就传“pma101”。4.4 不同开窗写法的适配实际T100二次开发中开窗并不只有一种写法。我总结下来主要有三种常见场景各自适配方式不太一样第一种是同一per文件里的多窗口切换。这种最常见代码里经常看到类似“OPEN WINDOW w_xxx”、“CURRENT WINDOW IS w_xxx”这类写法。这种场景直接把窗口名作为win_id传入校验函数就行。第二种是跨程序调用独立画面。比如主程序里CALL另一个4gl模块。这种情况下权限函数要放在被调程序的入口处校验win_id用被调程序的程序编号。因为你现在不是“开窗”而是“进另一支程序”锁在被调程序入口更安全。第三种是基于DIALOG的ON ACTION开窗。T100里很多按钮通过ON ACTION触发点击后弹窗做选择。这种场景的校验放在ACTION分支的处理代码里。注意ACTION分支可能在per文件里和4gl代码里都有交互校验逻辑放4gl代码段里不要放在界面层。4.5 权限配置画面既然有了权限表总不能让管理员天天在数据库客户端里手工维护SQL。我顺手做了一个极简的维护画面界面上按“程序编号窗口标识”列出所有登记过的窗口然后可以针对某个用户或某个角色配置允许或拒绝。画面本身不复杂就是一个查询表单加一个维护表单。关键在于界面里要显示当前程序的“窗口标识清单”这个清单从哪里来我是让开发者在接入权限锁时把自己程序用到的win_id在代码注释里登记一份再手工维护到一张“窗口注册表”里。后台管理画面查询时JOIN这张注册表让管理员知道这个窗口标识对应界面上哪个业务功能。维护画面最后还会提供一个“查看当前用户权限”的功能输入用户名就能看到他名下所有窗口的放行/拒绝情况方便上线前做权限走查。5. 上线前必做的验证清单与踩坑记录5.1 我在测试中遇到的实际问题这套方案写完后我在测试环境走了几轮踩了几个有价值的坑这里逐个说一下。第一个坑是字段截断。最早我把win_id字段定成了CHAR(20)结果per文件里有个窗口名比较长插入时被自动截断了权限一直匹配不上。排查了半天才发现是字段长度问题。所以大家建表时字段长度宁可长一点VARCHAR(50)不丢人。第二个坑是多公司条件漏写。我第一次调试时发现明明给A公司配了拒绝规则B公司的用户却被拦住了。查了一遍代码才发现校验SQL里没有拼company条件。这个在单公司测试环境里根本发现不了只有在多公司环境下才会暴露。第三个坑是小写统一问题。T100的prog_id传参时有些地方拿到的可能是小写但权限表里存的是大写导致匹配不上。我在函数里统一做了大小写转换避免这种低级问题。第四个坑是角色缓存。用户在某支程序里挂的权限改了之后因为初始方案里做了会话级缓存导致改了权限要重新登录才生效。后来我加了一个“刷新权限”功能管理员改完角色或权限后执行一次刷新缓存就清了。5.2 窗口标识的命名规范维护这套权限体系最脏最累的活其实是梳理窗口标识。没有规范的话一百个开发有一百种起名方式后台配置的人根本不知道“win1”到底指哪个窗口。我定了一套自己的规范窗口标识统一用“程序功能前缀业务含义窗口类型后缀”比如winCostInfo、winVendorQuoteList、winCustCreditCheck。接入权限锁时必须在代码注释里写清楚这个窗口是给谁看的、里面展示了哪些敏感字段、业务申请人是哪个部门。权限配置画面会把这段描述显示出来管理员一眼就能判断该不该拒绝。5.3 性能影响与并发表现有同事担心每次开窗都查一次数据库会不会拖慢操作速度所以我在设计时做了缓存。实际测试下来未命中缓存时的单次查询在Informix上大概0.8毫秒几乎可以忽略。命中缓存后为零开销。但有一个特殊情况要注意如果你们把权限表的数据量做得很大几十万条规则查询条件里又没有走到索引那性能就会劣化。我给权限表加了一个联合索引字段顺序是company prog_id win_id user_id覆盖了绝大部分查询场景。索引建好后即使在多公司大数据量环境下查询都是毫秒级返回。5.4 权限走查和部署建议上线前我拉着各模块顾问把所有接入了权限锁的开窗点过了一遍整理成一份窗口权限走查表。走查表里包含程序编号、窗口标识、窗口业务说明、敏感字段、申请人、放行角色、拒绝角色、上线批次。这份表既是测试用例也是后续运维的台账。部署顺序上我建议分三步走先在测试环境把权限表、共享函数、参数表部署好让开发同事逐支程序接入权限锁并完成功能回归测试然后切到用户验收环境把走查表里列出的敏感窗口配好权限规则请业务部门验证“该看到的人能看到不该看到的人看不到”最后上线生产环境时先保持默认放行只拦截前几批明确确认过的敏感窗口跑两周没有异常后再逐步扩大管控面。6. 方案落地后的实际效果与后续扩展上线运行一段时间后我从日志表里拉过一次拦截记录发现每周大概有一百多次窗口访问被拒。被拒的访问里有相当一部分是操作员误点也有个别是确实想绕过权限看成本的。业务部门对这个结果比较满意财务主管也终于不用天天盯着屏幕防止别人偷看了。后续我还在考虑两个扩展方向。一个是把权限表接上工作流审批管理员发起配置变更后必须经过主管审批才生效避免单人误操作导致权限失控。另一个方向是把窗口权限和单据字段权限联动比如某个窗口被拒绝后如果主程序界面上的敏感字段也自动打码体验会更好。不过这都是后话了当前这套开窗权限锁已经解决了最棘手的“窗口裸奔”问题。有类似需求的朋友可以直接参照上面的表结构和函数代码落地重点是先把窗口清单梳理清楚再谈权限规则。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

聚焦具身智能教育,华清远见发布三款硬件新品与课程体系2.0 2026/9/21 3:22:14

聚焦具身智能教育,华清远见发布三款硬件新品与课程体系2.0

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
STM32结构体封装原理与GPIO初始化设计解析 2026/9/21 3:22:14

STM32结构体封装原理与GPIO初始化设计解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
linsa 开源路线图前瞻:如何第一时间关注并参与这个即将开源的私有云项目 2026/9/21 3:22:14

linsa 开源路线图前瞻:如何第一时间关注并参与这个即将开源的私有云项目

linsa 开源路线图前瞻:如何第一时间关注并参与这个即将开源的私有云项目 【免费下载链接】linsa Work. Save. Share. Privately. 项目地址: https://gitcode.com/gh_mirrors/le/linsa linsa 是一个即将开源的私有云存储项目,核心卖点是端到端加密…

阅读更多 →
Voyager 入門ガイド:Gemini にタイムライン・フォルダ・プロンプト管理を組み込む 5 分間セットアップ 2026/9/21 3:22:14

Voyager 入門ガイド:Gemini にタイムライン・フォルダ・プロンプト管理を組み込む 5 分間セットアップ

AI 应用前端 【免费下载链接】voyager Enhancement suite for Gemini, AI Studio, Claude & ChatGPT — plus a prompt manager for any websites, DeepSeek Harness included. / 面向 Gemini、AI Studio、Claude 与 ChatGPT 的增强套件;其中的提示词管理器可用…

阅读更多 →
DDR5内存SPD Hub深度解析:JESD300-5A规范与SPD5118/5108实战指南 2026/9/21 3:22:14

DDR5内存SPD Hub深度解析:JESD300-5A规范与SPD5118/5108实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
嵌入式下载故障排查:ST-LINK与GD32 Programmer典型问题解决 2026/9/21 3:19:14

嵌入式下载故障排查:ST-LINK与GD32 Programmer典型问题解决

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞