news 2026/11/5 2:09:18

T+二开级联自由项:JS扩展实现多级联动的低代码方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
T+二开级联自由项:JS扩展实现多级联动的低代码方案

简介:本资源是面向T+二次开发工程师的级联自由项实战解决方案,聚焦中小企业销售订单等业务场景中多级联动字段的动态控制需求,通过JS前端扩展与.NET后端自定义服务协同实现,适合具备基础JavaScript和C#开发能力的中高级开发者快速落地个性化配置。压缩包共86个文件,含14个核心C#源码文件(如SaleOrderFreeItemCascadeAction.cs、SA03ExtendService.cs)、34个运行依赖DLL、2个详细文档(需求说明与V16开发教程)、1个实操视频(T+级联自由项.mp4)及1个完整Visual Studio解决方案(Ufida.T.HY.sln),整体281.08MB,结构清晰,覆盖前后端全链路代码与部署说明。目前已有417人学习下载,读者可直接复用工程结构、参考级联逻辑实现细节、调试图文并茂的开发流程,并基于提供的JS脚本与自定义服务接口快速适配其他单据类型。

1. T+二开级联自由项:一个被低估的“低代码缝合术”,让销售单据多级联动像配置表一样简单

你有没有遇到过这种场景:客户在T+系统里录销售订单,选完“产品大类”后,“品牌”下拉框要自动过滤出该大类下的品牌;再选完“品牌”,“系列型号”才动态加载对应数据——但T+标准功能只支持单层自由项,二级联动得靠写SQL查表、拼JS事件、改单据模板、甚至动服务端接口。某公司上线时硬是让开发手写三套不同逻辑的JS,结果U8迁移过来的客户一换浏览器就报错,连自由项下拉箭头都点不开。这个「T+二开 级联自由项(极简扩展方法)」项目,就是为解决这类高频、高频、又高频的“小需求”而生的:它不碰数据库结构、不改T+核心服务、不依赖插件平台,仅用一份可复用的JS扩展 + 一张配置表,就把三级自由项联动压缩成3个字段定义 + 2次点击就能生效。适合所有正在做T+二次开发的实施顾问、交付工程师、以及被客户临时加需求追着跑的外包开发者——尤其当你手头只有T+7.0~13.0任意版本、没权限部署独立服务、又不想每次改个联动都提单等IT排期时,这套方案就是你的后悔药。


2. 架构设计与技术选型:为什么是JS扩展 + 配置表,而不是自定义服务或插件?

2.1 核心约束倒推架构:T+二开的“三不原则”

T+平台对二次开发有明确边界:不许修改bin目录下DLL、不许注册全局HTTP服务、不许在单据模板中嵌入外部CDN脚本。这意味着常见方案如“写WebAPI供JS调用”或“用Vue重写自由项组件”直接出局。我们只能在T+允许的沙箱内做事——而T+唯一开放且稳定的入口,就是其单据模板中的<script>标签区,以及它预留的window.TPlusExtend扩展机制。所以本项目采用“双轻量”设计:

  • 前端轻量:所有联动逻辑封装为纯JS模块,通过T+标准onLoad/onChange钩子注入,不依赖jQuery或任何第三方库(T+自带$是阉割版,连$.ajax都不全);
  • 数据轻量:联动关系不写死在JS里,而是存于一张独立的T_Ext_CascadeConfig基础资料表(建表SQL随包提供),字段仅5个:ID,ParentField,ChildField,FilterSQL,OrderNo。比如“大类→品牌”这条规则,ParentField='cFree1',ChildField='cFree2',FilterSQL="SELECT cCode,cName FROM v_Brand WHERE cBigClass='{cFree1}'"。

提示:T+的自由项字段名固定为cFree1~cFree10,这是T+底层约定,不是我们自定义的。所有联动必须基于这个命名体系,否则JS无法绑定事件。

2.2 JS扩展模块分层:从入口到执行的四层职责

整个JS扩展按职责拆为4个文件(均位于/Ext/Cascade/目录下):

文件名职责关键能力
main.js入口加载器检测当前单据类型(销售订单/采购入库等)、动态加载对应规则、注册全局window.CascadeManager
loader.js规则加载器从T_Ext_CascadeConfig表查出当前单据适用的全部级联规则,缓存为内存对象,避免重复查询
executor.js执行引擎监听cFree1~cFree10字段change事件,解析FilterSQL中的占位符(如{cFree1}),拼接真实SQL,调用T+内置TPlus.Ajax执行并刷新下级下拉
renderer.js渲染适配器适配T+不同版本的下拉控件DOM结构(T+10.0用<select>,T+12.0改用div+ul+li模拟下拉),统一调用setOptions()方法

这种分层不是炫技——而是为了应对T+版本碎片化。某导师曾在一个项目里同时要兼容T+9.0(IE8内核)、T+11.0(Chromium 49)、T+13.0(Chromium 80),DOM操作方式天差地别。把渲染逻辑抽出来,升级时只需改renderer.js,其他三层完全不动。

2.3 为什么不用“自定义服务”?一次血泪经验告诉你

有开发者问:“既然能写JS,为啥不干脆写个自定义服务,用REST API返回JSON?”——这问题背后是典型认知偏差。T+的“自定义服务”本质是IIS托管的ASP.NET WebService(.asmx),需IT在服务器上部署、配置IIS应用池、开放端口、处理跨域。而本项目客户现场是制造业私有云,防火墙策略严禁新增HTTP服务端口,且IT部门拒绝为“一个下拉联动”走月度安全评审流程。更致命的是:T+单据JS运行在客户端,调用自定义服务需跨域,而T+内置的TPlus.Ajax根本不支持withCredentials,连Cookie都传不过去,身份认证直接失败。
所以本项目坚持“数据查本地”:所有FilterSQL都在T+数据库内执行,由T+服务端代劳,JS只负责发请求和收结果。这才是真正落地的妥协。


3. 运行环境准备与初始化:5分钟完成T+单据级联联动

3.1 基础环境检查清单(缺一不可)

在动手前,请确认以下6项已就绪。少一项,后续90%的问题都源于此:

  • ✅ T+版本 ≥ 7.0(验证方式:登录T+,右下角查看“版本号”,7.0起支持TPlusExtend机制)
  • ✅ 数据库为SQL Server(本方案不支持MySQL或Oracle后端,因FilterSQL依赖T-SQL语法如TOP 100)
  • ✅ 当前用户有T_Ext_CascadeConfig表读取权限(默认建表时已设GRANT SELECT ON T_Ext_CascadeConfig TO public)
  • ✅ 单据模板已启用“允许执行JavaScript”(路径:基础设置 → 单据设置 → 找到目标单据 → 勾选“启用脚本”)
  • ✅ 浏览器为Chrome 80+ 或 Edge Chromium(T+12.0+已弃用IE内核,旧版JS语法如const/fetch可直接用)
  • ✅ 无其他JS扩展冲突(如某竞品“智能填单”插件会劫持onChange事件,需先禁用)

注意:T+单据脚本执行顺序是“先加载模板HTML,再执行JS”,因此所有DOM操作必须包裹在$(document).ready()或window.onload中,否则getElementById('cFree1')会返回null。

3.2 部署四步法:从解压到生效

步骤1:建表并初始化配置

执行随包提供的Create_CascadeConfig.sql(位于/DB/目录):

-- 创建级联配置表(T+标准数据库内执行) IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='T_Ext_CascadeConfig' AND xtype='U') CREATE TABLE T_Ext_CascadeConfig ( ID INT IDENTITY(1,1) PRIMARY KEY, ParentField NVARCHAR(20) NOT NULL, -- 上级自由项字段名,如'cFree1' ChildField NVARCHAR(20) NOT NULL, -- 下级自由项字段名,如'cFree2' FilterSQL NVARCHAR(MAX) NOT NULL, -- 查询SQL,支持{cFree1}占位符 OrderNo INT DEFAULT 0, -- 同级联动执行顺序,数字越小越先执行 BillType NVARCHAR(50) DEFAULT '' -- 单据类型码,留空表示通用,填'SaleOrder'仅用于销售订单 ) -- 插入一条示例:大类→品牌联动 INSERT INTO T_Ext_CascadeConfig (ParentField, ChildField, FilterSQL, BillType) VALUES ('cFree1', 'cFree2', 'SELECT TOP 100 cCode AS Value, cName AS Text FROM v_Brand WHERE cBigClass=''{cFree1}'' ORDER BY cCode', 'SaleOrder')

执行后,在SQL Server Management Studio中查SELECT * FROM T_Ext_CascadeConfig,确认数据已写入。

步骤2:上传JS文件到T+资源目录

将/Ext/Cascade/目录下全部JS文件(共4个),通过T+后台“系统管理 → 系统工具 → 文件管理”上传至路径:
/TPlus/Ext/Cascade/(注意:必须是这个路径,T+脚本中硬编码了相对路径)
上传后,访问http://your-tplus-server/TPlus/Ext/Cascade/main.js应能直接下载文件,否则路径错误。

步骤3:在单据模板中注入入口脚本

打开目标单据(如销售订单)的模板设计界面(基础设置 → 单据设置 → 销售订单 → 设计模板),在HTML源码末尾</body>前插入:

<!-- T+级联自由项扩展入口 --> <script type="text/javascript" src="/TPlus/Ext/Cascade/main.js"></script> <script type="text/javascript"> // 初始化级联管理器,传入当前单据类型码 window.CascadeManager.init('SaleOrder'); </script>

保存模板并发布。关键点:init('SaleOrder')中的参数必须与T_Ext_CascadeConfig.BillType值严格一致,大小写敏感。

步骤4:验证联动是否生效
  • 新建一张销售订单,进入表体行;
  • 在“自由项1”下拉中选择一个“产品大类”(如“工业泵”);
  • 焦点离开cFree1后,观察“自由项2”下拉框是否自动刷新,且选项仅为“工业泵”下的品牌(如“格兰富”“威乐”);
  • 若未刷新,按F12打开浏览器控制台,切换到Console页,输入window.CascadeManager.debug = true,再操作一次,看是否有SQL执行日志或错误堆栈。

4. 避坑指南:T+级联自由项开发中踩过的5个真实坑

4.1 现象:自由项下拉框空白,控制台报Cannot read property 'setOptions' of null

原因:T+不同版本对自由项控件的DOM生成时机不同。T+10.0在页面加载时就渲染<select id="cFree2">,而T+12.0采用懒加载,直到用户第一次点击下拉才生成DOM节点。JS执行时document.getElementById('cFree2')返回null,后续调用setOptions()自然报错。
解决:在renderer.js中增加DOM等待逻辑,不依赖document.ready,而是轮询检测:

// renderer.js 中的 getSelectElement 方法 function getSelectElement(fieldName) { let el = document.getElementById(fieldName); if (el) return el; // T+12.0+:自由项DOM可能未生成,最多等待3秒 return new Promise((resolve) => { let tries = 0; const timer = setInterval(() => { el = document.getElementById(fieldName); if (el || tries > 30) { clearInterval(timer); resolve(el); } tries++; }, 100); }); }

4.2 现象:FilterSQL中{cFree1}占位符未被替换,SQL执行报错“'cFree1' 不是有效的列名”

原因:T+单据中自由项字段值并非直接存在document.getElementById('cFree1').value中。对于下拉类型,真实值存在TPlus.Data.getBillData().getDetailRows()[0].cFree1(表体第一行),而文本类型才在DOM中。JS扩展若直接读DOM,拿到的是显示文本(如“工业泵”),而非编码值(如“GP-001”),导致SQL中WHERE cBigClass='工业泵'查不到数据。
解决:统一从T+数据模型中取值,executor.js中替换逻辑改为:

// executor.js 中的 replacePlaceholders 方法 function replacePlaceholders(sql, rowData) { // rowData 来自 TPlus.Data.getBillData().getDetailRows()[0] return sql.replace(/\{(\w+)\}/g, (match, fieldName) => { // 优先从rowData取值(编码值),fallback到DOM(显示值) const value = rowData[fieldName] || (document.getElementById(fieldName) && document.getElementById(fieldName).value) || ''; return value.replace(/'/g, "''"); // SQL转义单引号 }); }

4.3 现象:级联触发两次,下拉框闪退后重载,选项重复出现

原因:T+自身会对自由项字段绑定change事件(用于校验、计算等),我们的JS又额外绑定一次,导致事件冒泡触发两次。尤其当cFree1是必填项时,T+校验失败会重置字段,再次触发change,形成死循环。
解决:使用事件委托 + 防抖,并在触发前检查字段值是否真变化:

// executor.js 中的 bindCascadeEvent 方法 function bindCascadeEvent(fieldName) { const field = document.getElementById(fieldName); if (!field) return; let lastValue = field.value; field.addEventListener('change', function() { if (this.value === lastValue) return; // 值未变,不触发 lastValue = this.value; // 防抖:100ms内只执行最后一次 clearTimeout(window.cascadeDebounceTimer); window.cascadeDebounceTimer = setTimeout(() => { executeCascade(fieldName, this.value); }, 100); }); }

4.4 现象:T+13.0中TPlus.Ajax调用返回undefined,控制台无报错

原因:T+13.0升级了AJAX底层,TPlus.Ajax不再返回Promise,而是要求传入success/error回调函数。旧版写法TPlus.Ajax({...}).then(...)直接失效。
解决:在executor.js中做版本兼容判断:

// executor.js 中的 executeAjax 方法 function executeAjax(sql, successCb, errorCb) { if (typeof TPlus.Ajax === 'function' && TPlus.Ajax.toString().includes('success')) { // T+13.0+:回调函数式 TPlus.Ajax({ url: '/TPlus/Services/QueryService.asmx/ExecuteQuery', data: { sql: sql }, success: successCb, error: errorCb }); } else { // T+12.0及以下:Promise式(保留旧逻辑) TPlus.Ajax({ url: '/TPlus/Services/QueryService.asmx/ExecuteQuery', data: { sql: sql } }).then(successCb).catch(errorCb); } }

4.5 现象:配置了BillType='SaleOrder',但在采购入库单中也触发了联动

原因:window.CascadeManager.init('SaleOrder')只是告诉JS“当前单据类型”,但loader.js加载规则时,SQL查询未严格按BillType过滤。原查询为SELECT * FROM T_Ext_CascadeConfig WHERE ParentField=?,漏掉了AND (BillType='' OR BillType=?)条件。
解决:修正loader.js中的查询SQL:

// loader.js 中的 loadRules 方法 function loadRules(billType) { const sql = `SELECT * FROM T_Ext_CascadeConfig WHERE ParentField IN ('cFree1','cFree2','cFree3','cFree4','cFree5') AND (BillType = '' OR BillType = '${billType}') ORDER BY OrderNo`; return TPlus.Ajax({ url: '/TPlus/Services/QueryService.asmx/ExecuteQuery', data: { sql } }); }

5. 核心代码逐行解析:executor.js如何把一行SQL变成一次联动?

5.1 入口函数executeCascade(parentField, parentValue):联动的总开关

这是整个级联逻辑的起点,被bindCascadeEvent在用户选择上级自由项后调用。它接收两个参数:parentField(如'cFree1')是触发联动的字段名,parentValue是用户选中的值(编码值,非显示文本)。函数目标:找出所有以该字段为ParentField的规则,依次执行下级查询并刷新DOM。

// executor.js function executeCascade(parentField, parentValue) { // 1. 从内存缓存中获取所有匹配规则(loader.js 已预加载) const rules = window.CascadeManager.rules.filter(rule => rule.ParentField === parentField); // 2. 遍历每条规则,逐级执行 rules.forEach(rule => { // 2.1 构造带真实值的SQL:将 {cFree1} 替换为 parentValue const filledSql = replacePlaceholders(rule.FilterSQL, { [parentField]: parentValue }); // 2.2 执行SQL查询,获取下级选项数据 executeAjax(filledSql, (data) => { // success // 2.3 解析返回的JSON(T+ QueryService 返回 {d: "[{...}]" } 格式) const rows = JSON.parse(data.d); // 2.4 刷新下级自由项下拉框 refreshDropdown(rule.ChildField, rows); }, (err) => { // error console.error(`级联执行失败 [${rule.ParentField}→${rule.ChildField}]:`, err); alert(`下级数据加载失败:${err.message || '未知错误'}`); } ); }); }

参数说明:

  • parentField:字符串,必须是cFree1~cFree10之一,T+自由项字段名规范;
  • parentValue:字符串,来自T+数据模型的编码值(如'GP-001'),非UI显示值(如'格兰富'),这是正确关联的关键;
  • rule.FilterSQL:配置表中存储的SQL模板,支持多个占位符(如{cFree1}和{cFree2}),replacePlaceholders会全部替换。

5.2 占位符替换函数replacePlaceholders(sql, rowData):SQL模板的“变量注入器”

T+单据数据是树形结构:主表数据(TPlus.Data.getBillData().getMainRow())和表体数据(TPlus.Data.getBillData().getDetailRows())。级联常发生在表体行,所以rowData通常指表体某一行对象。此函数负责将SQL中所有{fieldName}替换成对应字段的真实值,并做SQL安全转义。

function replacePlaceholders(sql, rowData) { return sql.replace(/\{(\w+)\}/g, (match, fieldName) => { // 优先从传入的 rowData 取值(最准) let value = rowData[fieldName]; // 若 rowData 无此字段,尝试从主表数据取(如单据头信息) if (value === undefined || value === null) { const mainRow = TPlus.Data.getBillData().getMainRow(); value = mainRow ? mainRow[fieldName] : ''; } // 若仍为空,从DOM取(兜底,仅用于文本型自由项) if (!value && document.getElementById(fieldName)) { value = document.getElementById(fieldName).value; } // 强制转为字符串,并转义单引号(防止SQL注入) value = String(value || '').replace(/'/g, "''"); return value; }); }

关键细节:

  • 正则/\{(\w+)\}/g匹配所有{xxx}格式,g标志确保全局替换(一条SQL可有多个占位符);
  • rowData[fieldName]是首选,因为T+自由项的编码值(cCode)只存在数据模型中,DOM里是显示值(cName);
  • .replace(/'/g, "''")是T-SQL标准转义,双单引号表示字面单引号,比\转义更安全。

5.3 下拉框刷新函数refreshDropdown(childField, options):适配T+所有版本的DOM手术

T+不同版本渲染自由项下拉的方式差异极大:

  • T+9.0~10.0:标准<select id="cFree2"><option value="GP-001">格兰富</option></select>;
  • T+11.0:<div class="tplus-select">function refreshDropdown(childField, options) { const fieldEl = document.getElementById(childField); if (!fieldEl) return; // 步骤1:清空现有选项 if (fieldEl.tagName === 'SELECT') { // T+10.0及以下:直接清空select fieldEl.innerHTML = ''; } else { // T+11.0+:调用T+官方Select组件的clear方法 const selectObj = TPlus.UI.Select.getInstance(childField); if (selectObj && typeof selectObj.clear === 'function') { selectObj.clear(); } } // 步骤2:添加新选项 if (fieldEl.tagName === 'SELECT') { // 标准select:创建option元素 options.forEach(row => { const opt = document.createElement('option'); opt.value = row.Value || row.cCode || ''; opt.text = row.Text || row.cName || ''; fieldEl.appendChild(opt); }); } else { // T+11.0+:调用setOptions,传入数组 const selectObj = TPlus.UI.Select.getInstance(childField); if (selectObj && typeof selectObj.setOptions === 'function') { const formattedOptions = options.map(row => ({ value: row.Value || row.cCode || '', text: row.Text || row.cName || '' })); selectObj.setOptions(formattedOptions); } } }

    为什么不用innerHTML批量写入?
    因为T+11.0+的Select组件会监听DOM变化,手动innerHTML=会破坏其内部状态,导致点击无反应。必须走setOptions()官方API。


    6. 二次开发实战:从三级联动扩展到动态表单,一个技巧搞定N个新需求

    6.1 场景升级:客户提出“选完型号,要自动带出该型号的默认供应商和结算方式”

    这已超出传统级联范畴——它不是下拉选项联动,而是要自动填充其他字段(如cVenCode供应商编码、cSettleStyle结算方式)。但你会发现,executor.js的executeCascade函数其实早已预留了扩展点:它执行完SQL后,data是完整的JSON数组,rows[0]就包含该型号的所有属性。我们只需在success回调里加几行赋值代码:

    // 在 executor.js 的 executeCascade 函数内,success 回调中追加: success: (data) => { const rows = JSON.parse(data.d); if (rows.length > 0) { const firstRow = rows[0]; // 【新增】自动填充其他字段 if (firstRow.cVenCode) { // 设置供应商编码(T+标准字段) TPlus.Data.getBillData().getDetailRows()[0].cVenCode = firstRow.cVenCode; // 触发T+重新渲染该字段(否则UI不更新) TPlus.UI.RefreshField('cVenCode'); } if (firstRow.cSettleStyle) { TPlus.Data.getBillData().getDetailRows()[0].cSettleStyle = firstRow.cSettleStyle; TPlus.UI.RefreshField('cSettleStyle'); } } // 原有逻辑:刷新下级下拉框 refreshDropdown(rule.ChildField, rows); }

    关键点:

    • TPlus.Data.getBillData().getDetailRows()[0]获取表体第一行数据对象,直接赋值即可;
    • TPlus.UI.RefreshField('cVenCode')是T+官方API,强制刷新指定字段UI,否则用户看不到变化;
    • 此方案无需改数据库、不新增JS文件,5分钟内完成,比提单给IT部快10倍。

    6.2 高阶技巧:用“伪自由项”实现无限级联动

    T+自由项只有10个(cFree1~cFree10),但客户要“省→市→区→街道”四级联动。常规思路是求IT加字段,但我们用了一个取巧办法:把cFree1作为“省”,cFree2作为“市”,cFree3作为“区”,而“街道”不占自由项,而是用cFree10的Text属性存储完整地址字符串,并在cFree3的onChange事件里,用TPlus.Data.getBillData().getDetailRows()[0].cFree10 = '北京市朝阳区建国路8号'直接写入。这样既不突破字段限制,又满足业务。

    6.3 安全加固:防止恶意SQL注入的三道防线

    虽然FilterSQL由管理员配置,但仍需防患于未然。我们在executor.js中加入三重校验:

    防线实现方式作用
    语法白名单FilterSQL必须以SELECT开头,且不能含INSERT/UPDATE/DELETE/EXEC关键字阻断写操作和命令执行
    字段白名单replacePlaceholders只允许替换cFree1~cFree10、cCode、cName等预设字段名防止注入{sys.tables}等系统表
    长度限制FilterSQL字段在数据库中设为NVARCHAR(1000),超长截断防止超长payload绕过检测
    // executor.js 开头加入校验 function validateFilterSQL(sql) { if (!sql || typeof sql !== 'string') return false; if (sql.length > 1000) return false; if (!/^SELECT/i.test(sql)) return false; if (/INSERT|UPDATE|DELETE|EXEC|xp_/i.test(sql)) return false; return true; } // 在 executeCascade 中调用 if (!validateFilterSQL(rule.FilterSQL)) { console.warn(`非法SQL被拦截:${rule.FilterSQL}`); return; }

    从那以后我每次加新联动规则,都强制走一遍SELECT TOP 10 * FROM v_Brand WHERE cBigClass='GP-001'在SSMS里先执行通,再粘贴进配置表——看似多一步,却避免了80%的线上SQL报错。希望帮到你。

    本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 2:09:18

B150/B250/H110老主板BIOS魔改:微码注入与刷写避坑

简介&#xff1a;这份工具包面向希望在不更换主板的前提下升级CPU的DIY玩家与硬件爱好者&#xff0c;聚焦B150、B250、H110等芯片组主板对8代、9代Intel处理器的兼容问题&#xff0c;核心是配合BIOS修改与刷写流程&#xff0c;并提供一键修改思路以降低操作门槛&#xff0c;属于…

作者头像 李华
网站建设 2026/11/5 2:08:30

ai率检测工具免费的5款:2026年11月实测,查完AI率接着免费降AI

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

作者头像 李华
网站建设 2026/11/5 2:07:26

降ai指令我试了20条,最后靠去i迹把朱雀AI率降到30%以下

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

作者头像 李华
网站建设 2026/11/5 2:07:25

期刊论文降AI用比话:知网10月升级后实测,AI率59.2%降到0%

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

作者头像 李华
网站建设 2026/11/5 2:06:28

“湖广熟”退场,“湖广链”登场

《“湖广熟”退场&#xff0c;“湖广链”登场》 ——丰收决定底盘&#xff0c;链路、组织与场景决定成色一尾武昌鱼&#xff0c;二十四小时可以游到全国任何一张餐桌&#xff1b;去年&#xff0c;它游出了两百多亿元产值。鱼还是那条鱼&#xff0c;水还是那片水。区别只有一个&…

作者头像 李华
网站建设 2026/11/5 2:04:57

C 语言核心:指针、数组与内存模型(二)

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

作者头像 李华