☰
Smartbi电子表格报表设置全流程:冻结窗格、隔行变色与参数校验
2026/10/31 7:29:26 网站建设 项目流程

简介:《Smartbi报表设置文档》面向正在使用 Smartbi 制作 Excel 风格报表的报表开发与数据分析人员,聚焦模板引用、页面设置、隔行配色、列宽自适应、参数校验与参数排版、预览发布等常见配置环节,适合需要快速上手或查漏补缺的初中级使用者。压缩包内共 1 个 docx 文件,约 1.05MB,以图文步骤和配置要点组织内容,并附有列宽自适应、起止日期校验等宏脚本示例,便于对照实际报表工程理解设置逻辑。目前已有 1304 人学习下载,说明该主题具有一定的实践参考需求。读者可从中获得从模板设计到发布预览的完整设置脉络,理解条件格式、AutoFit、参数排版及宏管理在 Smartbi 报表中的落点,并借助代码片段排查列宽、参数联动和日期范围限制等问题,提升报表搭建效率与规范性。

1. 一份把 Smartbi 报表设置讲透的操作清单

上个月帮一个财务口径的月报做返工,问题不在 SQL,也不在数据集,全卡在展示层:冻结窗格偏了一行、隔行变色在扩展后错位、用户把结束日期选到一年后,一刷新直接把明细表拉出六位数行。这类事单看都不难,难在每次都要重调一遍,没人把步骤沉淀下来。这份 Smartbi 报表设置文档做的事很朴素——把引用模板、页面设置、隔行变色、列宽自适应、参数校验、参数排版、预览发布这七步固化成可照着敲的流程,配套两段可直接复用的电子表格宏。它适合刚接手电子表格类报表的人,也适合已经会做但每次都要翻旧报表抄样式的人。下面按我实际落地的顺序拆,重点放在参数、代码和那些调试时才冒出来的坑。

2. 引用模板与页面设置:冻结 1-5 行背后的三个数

2.1 从「模板」入口打开,而不是新建空白

Smartbi 的电子表格设计器是挂在 Excel 里的插件,登录之后顶部会多出一个页签。多数人第一次用会直接新建空白工作簿,结果数据集要重新绑、参数要重新建、连单元格的样式素材都得从零调。正确入口是页签里的「模板」,从资源目录里挑一份结构接近的已有报表打开,在它的基础上改样式。

这样做的好处很实际:数据集绑定、参数定义、已挂的宏、单元格的字体边框、甚至打印设置都会一起继承下来。我做季度报表时就是拿上一季度的模板改,只动表头文案和取数条件,二十分钟能出一版。

代价是旧宏也会被继承。有一回打开模板后新写的校验宏不生效,排查半天发现模板里本来就挂着一个同名的main,后建的宏排在后面,执行时被前者挡住。所以打开模板后的第一件事,是去宏管理里把现有宏列一遍,确认没有同名冲突再往下做。

操作顺序记一下:

  1. Excel 里点 Smartbi 页签,登录账号。
  2. 点「模板」,在资源树里定位到目标报表,双击打开。
  3. 另存为新报表,改名字、改存放目录,避免直接改坏原模板。
  4. 打开宏管理,清点已有宏的名称和触发时机。

第 3 步容易被跳过。直接改模板文件,下次别人引用这份模板时,你的临时改动就跟着扩散出去了,这种污染比写错一个公式更难查。

2.2 页面设置:首行空白行与冻结窗格的账

原始文档里有一句注:「第一行是空白行,所以上面报表冻结的是 1-5 行」。这句话第一次看容易误解,以为冻结 5 行就够了。实际账是这样的:Excel 的行号从 1 开始,第 1 行留白是为了给参数面板或者标题区留位置,真正要固定的表头在第 2 到第 5 行。冻结窗格的操作落在第 6 行的「冻结拆分窗格」上,效果是 1 到 5 行在浏览器端滚动时始终悬在顶部。

为什么强调这个数?因为电子表格发布到浏览器后,滚动容器和 Excel 的视图不是一回事。冻结行数多一行,底下内容区就少一行;少一行,表头滚走了用户就得往上翻。财务类报表一屏显示二十来行,这一行的差别肉眼可见。

页面设置里我一般会固定这么几项:

设置项建议值说明
纸张方向横向列数超过 8 列基本都得横过来
缩放调整为 1 页宽避免打印时右侧列被截断
页边距上下 1.5cm,左右 1cm留出装订和页码位置
打印标题行$1:$5与冻结行保持一致
页脚第 &P 页 / 共 &N 页多页报表必须有
网格线打印时不显示靠单元格边框出线

「打印标题行」这一项和冻结窗格是两套机制,一个管屏幕,一个管纸张,很多人只设了前者,导出 PDF 时第二页开始就没表头了。

2.3 行列基准线:先定骨架再填内容

样式调整最忌讳上来就调字体颜色。我的习惯是先定三条基准线,再谈美观:

第一条是行高。表头行固定 28 到 32 像素,数据行 22 到 24 像素,参数区单独一行 30 像素左右。行高不统一,冻结区和非冻结区在滚动时会有明显的跳变感。

第二条是列宽。文本类列(客户名、科目名)给到能放下 12 个汉字;金额列统一宽度,右对齐,千分位;日期列固定 10 个字符宽。列宽先手工定一个基准,后面再用第 3 章的宏做自适应兜底,顺序不能反——先自适应再手调,等于白调。

第三条是边框。外框一条粗线,表头下缘一条中线,数据区内部只留横线不留竖线。竖线一多,密集数字看着就晕。

这三条定完,再去处理标题合并、字体字号、数字格式。数字格式里最容易翻车的是金额列用了「常规」,导出后小数位数乱七八糟,统一设成#,##0.00最稳。

3. 隔行变色与列宽自适应:条件格式与 autoFitColumns 宏

3.1 条件格式做隔行变色:公式写在哪一格

隔行不同颜色的实现路径有两条:一条是 Excel 的「套用表格格式」,一条是条件格式配公式。电子表格场景我坚决用后者。套用表格格式会顺带改掉筛选按钮、边框和字体,和模板里已有的样式打架,而且它绑定的是一个区域对象,单元格扩展之后区域边界不会自动跟着走。

条件格式的做法是在「开始 → 条件格式 → 新建规则 → 使用公式确定要设置格式的单元格」里写:

=MOD(ROW()-2,2)=0

这个公式的意思是:当前行号减 2 之后能被 2 整除的行,套用设定的填充色。为什么减 2 而不是直接MOD(ROW(),2)?因为第 1 行是空白行,第 2 行通常是表头,真正的数据从第 3 行开始。用ROW()-2,第 3 行算出 1,奇数行不染色;第 4 行算出 2,偶数行染色。条纹就从数据首行开始,不会出现「表头被染、第一条数据没染」的错位。

应用范围要写大一点,比如$A$3:$N$2000,覆盖到扩展后的最大可能行数。这里有个细节:电子表格的扩展单元格在渲染前,条件格式只认设计态的区域,扩展出来的行能不能继承格式,取决于应用范围是否覆盖到了那些行号。范围给小了,翻到第二页条纹就断了。

填充色别选太扎眼。我一般用#F5F8FC这种接近白的浅蓝,或者#FAFAFA浅灰,打印出来几乎看不见,屏幕上又能分清行。深色条纹在投影仪上会糊成一片。

还有一条经验:条件格式只设填充色,不要在里面同时设边框和字体。多个规则叠加时优先级不好控,边框经常被覆盖掉,最后还得手工补。

3.2 列宽自适应宏:全局跑和单列调的两种写法

手工拖列宽的问题是每次数据结构变一次就得重拖。原始文档给了一段通用宏,我把它整理成带注释的版本:

function main(spreadsheetReport) { // 取第 0 个工作表;多 sheet 报表建议改成按名称取,见下方说明 var sheet = spreadsheetReport.workbook.worksheets.get(0); if (sheet === "" || sheet === null) { return; } // maxDisplayRange 是渲染后的最大显示区域,扩展单元格的行列也算在内 var counts = sheet.cells.maxDisplayRange.columnCount; // 从第 0 列开始,到 counts 列结束,整体自适应 sheet.autoFitColumns(0, counts); // 只想调某一列时用单列版本,索引从 0 开始,第 4 列对应索引 3 // sheet.autoFitColumn(3); }

逐段说一下逻辑。workbook.worksheets.get(0)拿的是工作簿里的第一张表,索引从 0 起。单 sheet 报表这么写没问题,多 sheet 的报表就有坑了——用户切到第二张表,宏还在对第一张表做自适应,看起来像没生效。稳妥的写法是先取getActiveSheet()或者按名称get("明细")定位,再执行。

maxDisplayRange.columnCount取的是当前渲染出来的最大列数。这里的「最大」是含扩展的,也就是说数据集返回多少列,它就报多少列,不会只报设计态画好的那几列。这也解释了为什么宏要放在渲染之后触发:渲染前取到的列数是设计态的,会偏小,自适应只覆盖了前几列,后面的列还是原始宽度。

autoFitColumns(0, counts)是区间版本,起点 0、终点 counts。按原始文档的说法是「从 0 开始,到 counts 列结束」,终点给的是列数而不是最大索引,多算一列通常无害,空列自适应后还是原来的宽度。真要抠这个边界,写成counts - 1也行,效果上没有肉眼差别。

autoFitColumn(3)是单列版本,调试阶段特别有用。整表自适应一次要遍历所有列测量文本宽度,列数上到三四十列、行数上万时,页面会有明显的卡顿感。我的做法是先用单列版本把几个长文本列(比如客户名称、备注)单独调宽,剩下的列手工定宽,最后再决定要不要整体跑一遍。

参数上还有两个可调的地方。一是自适应之后的最大宽度限制,某些版本支持传入上限,避免备注列被撑到半屏;二是最小宽度,防止全是数字的列被压成一条缝。这两个值没有统一标准,按你们的字段实际情况试两三次就摸出来了。

3.3 宏挂在哪、什么时候触发

宏写完了不等于生效。电子表格的宏分客户端宏和服务器端宏,列宽自适应这种依赖渲染结果的操作,必须挂在客户端、在报表渲染完成之后执行。入口在 Excel 的 Smartbi 页签 → 宏管理 → 新建宏,类型选客户端宏,事件选渲染后或者首次加载后。

触发时机的选择直接影响结果:

  • 挂在「加载前」,maxDisplayRange还是设计态的值,自适应范围不全。
  • 挂在「渲染后」,列数准确,但如果报表有异步取数,可能取数还没回来就执行了。
  • 挂在「刷新后」,每次用户点查询都会重跑一次,列宽跟着数据变,观感更稳,代价是每次刷新多一点耗时。

我一般挂「渲染后 + 刷新后」两个时机,前者保证首屏,后者保证换参数之后列宽不塌。列数超过 40 列的报表只挂刷新后,首屏用手工列宽顶着,避免打开就卡。

4. 参数联动校验:重写 doRefresh 卡住 90 天边界

4.1 为什么改 doRefresh 而不是监听参数事件

参数校验有好几种做法:在数据集 SQL 里加 where 条件、在参数面板上配校验规则、写宏拦截。前两种的边界能力有限——SQL 拦截会让用户看到一个数据库报错,体验很差;参数面板的规则配置对「两个日期之间的差值」这种跨参数计算支持不足。

宏拦截的思路是:报表每次刷新都会走spreadsheetReport.doRefresh,把这个方法包一层,在真正刷新之前做校验,不通过就直接 return,刷新根本不会发生。原始文档用的就是这个套路,先把原函数存到_jhy_doRefresh,再用一个新函数覆盖doRefresh,校验通过后调用原函数。

这个套路的妙处在于「无侵入」:不用改数据集,不用动参数定义,一段宏搞定,而且对所有触发刷新的入口都生效——点查询按钮、改参数回车、切页签,全走同一条路。

4.2 完整宏代码与逐行说明

原始版本只做了两条校验,我在实际项目里补了空值和解析失败的兜底,否则用户清空日期直接点查询,new Date("")得到 Invalid Date,比较结果恒为 false,校验形同虚设:

function main(spreadsheetReport) { // 保存原始 doRefresh,避免覆盖后无法刷新 if (!spreadsheetReport._jhy_doRefresh) { spreadsheetReport._jhy_doRefresh = spreadsheetReport.doRefresh; } spreadsheetReport.doRefresh = function (fromButton, delayMask) { // 参数名必须和参数面板上的完全一致,含空格和数字后缀 var endRaw = spreadsheetReport.getParameterValue("结束日期 2"); var startRaw = spreadsheetReport.getParameterValue("开始日期 2"); // 任一为空时放行,交给数据集或后端兜底,避免空值把校验卡死 if (!endRaw || !startRaw) { this._jhy_doRefresh(fromButton, delayMask); return; } // 把 - 换成 /,兼容部分浏览器对 yyyy-MM-dd 的解析差异 var endtime = new Date(String(endRaw).replace(/\-/g, "/")); var starttime = new Date(String(startRaw).replace(/\-/g, "/")); // 解析失败不要静默放行,直接提示,否则用户以为查询成功了 if (isNaN(endtime.getTime()) || isNaN(starttime.getTime())) { setTimeout(function () { alert("日期格式无法识别,请重新选择"); }, 100); return; } // 结束日期不早于开始日期 if (endtime < starttime) { setTimeout(function () { alert("结束时间不能早于起始时间"); }, 100); return; } // 跨度不超过 90 天,等于 90 天放行,超过才拦 if (endtime.getTime() - starttime.getTime() > 1000 * 3600 * 24 * 90) { setTimeout(function () { alert("两个参数的差不能超过 90 天"); }, 100); return; } // 校验全部通过,走原始刷新逻辑 this._jhy_doRefresh(fromButton, delayMask); }; }

逻辑上有三个关键点。

第一是_jhy_doRefresh这个暂存。doRefresh是实例方法,直接覆盖会把原实现弄丢,之后再也刷不出来。先用一个带前缀的私有属性存一份,判断if (!spreadsheetReport._jhy_doRefresh)是为了防止宏被重复执行时把已经包装过的函数又存一遍,套娃下去会栈溢出。

第二是setTimeout(..., 100)包住 alert。看起来多此一举,实际是因为 alert 是阻塞式弹窗,在刷新流程里同步弹出会打断上层调用链,某些版本下表现为「弹了框但按钮一直转圈」或者「弹完框整个表格白了」。放到定时器里,让刷新流程先退出,弹窗在下一个事件循环里出来,就不会卡死。100 毫秒是个经验值,太短可能赶在流程结束前,太长用户会觉得点了没反应。

第三是 90 天的算法。endtime.getTime() - starttime.getTime()得到毫秒差,除以1000 * 3600 * 24就是天数。这里没有做「日期归一化」,也就是说 1 月 1 日 00:00 到 4 月 1 日 00:00 是整 90 天,放行;到 4 月 1 日 00:00 零 1 秒就超了。如果参数控件只到日期粒度,这个精度足够;如果带时分秒,得先把两端截断到当天零点再比,否则用户会看到一个莫名其妙的「差一天」。国内没有夏令时,不用担心某一天只有 23 小时的问题,但跨时区部署的服务器要留意,前端new Date解析的是本地时区。

4.3 参数名、空值与日期格式的三个细节

参数名这一条值得单独说。原始文档里的参数名是「结束日期 2」和「开始日期 2」,中间有空格,后面有数字后缀。这个后缀是参数面板复制粘贴时自动加的,很多人写宏的时候凭记忆敲成「结束日期」,getParameterValue返回 undefined,后面new Date(undefined)直接炸,但因为是异步流程,控制台报错一闪而过,表面看起来就是「宏没反应」。

排查方法很简单:在宏的第一行加一句console.log(spreadsheetReport.getParameterValue("结束日期 2")),打开浏览器开发者工具看输出。返回null或undefined就是名字不对,返回日期字符串就是名字对了。这个习惯我保持了挺久,比猜快得多。

空值处理没有出现在原始文档里,但真实场景一定会遇到。用户把日期清空,参数传上来是空字符串,replace不会报错,new Date("")得到 Invalid Date,Invalid Date < 某日期恒为 false,于是两条校验全部放行,查询照跑,数据全量返回。所以空值必须显式判断,要么放行要么拦截,不能留给运气。

日期格式方面,参数面板传上来的通常是yyyy-MM-dd字符串。用replace(/\-/g, "/")转成yyyy/MM/dd再交给new Date,是为了绕开部分浏览器把带横线的日期当 UTC 解析的差异——同一个日期字符串,一处按 UTC 算一处按本地算,差出八小时,跨月边界上就会误判。换成斜杠之后,主流实现都按本地时间解析,行为一致。

5. 避坑与排查:六条踩过的记录

5.1 宏保存了但不生效,刷新毫无反应

现象:宏管理里能看到新写的宏,保存也成功了,但点查询按钮走的是原始刷新逻辑,alert 一次都没弹。

原因通常有三个:宏的类型选错,写成了服务器端宏,而校验需要在前端刷新链路上拦截;宏的事件没勾,创建时只填了名字和代码,事件下拉框留空;模板本身已经挂了一个同名的main,两个宏冲突,执行顺序靠前的那个赢。

解决:先确认宏类型是客户端宏、事件至少勾一个「刷新前」或「加载后」;再去宏管理里把同名宏找出来,要么删掉旧的,要么给新的换个名字并在旧宏里显式调用;最后在代码首行加日志,打开开发者工具确认宏真的被执行了,再往下查逻辑。

5.2 列宽自适应后合并单元格被压扁

现象:跑完自适应宏,表头那几列合并的大标题变成了窄窄一条,文字挤成两三行,或者干脆被截断。

原因:autoFitColumns的测量对象是单元格里的文本,合并单元格只按左上角那个单元格的内容算宽度,合并区域的其他列拿不到内容,被当成空列压到最小宽度。整表自适应时这种情况特别常见。

解决:把合并单元格所在的列排除在自适应范围之外。常见做法是先跑autoFitColumns,再手工把合并区的列宽设回固定值;或者反过来,只对数据列区间做自适应,比如从第 3 列开始,表头列全部手工定宽。我的习惯是后者,代码里把起点从 0 改成实际的第一个数据列索引。

5.3 冻结窗格在浏览器里偏了一行

现象:Excel 里预览时表头固定得好好的,发布到浏览器后,冻结区把第一条数据也冻住了,滚动的时候第一条数据永远悬在表头下面。

原因:冻结位置是按「当前选中单元格上方」计算的。在 Excel 里点第 6 行冻结,冻的是 1 到 5 行;如果点的是第 7 行,冻的就是 1 到 6 行。设计过程中插入或删除过行,选中位置跟着漂移,冻结行数就悄悄变了。首行空白行让这个偏移更隐蔽。

解决:发布前重新点一次目标行,确认冻结线在哪;Excel 的「视图 → 冻结窗格」菜单里能看到当前状态;发布后在浏览器里滚动到底部再滚回来,观察表头和第一条数据是否分离。这个检查我一般放在发布清单的第一项,三十秒能做完。

5.4 隔行变色到第二页就断了

现象:首屏条纹正常,往下翻几页之后条纹消失,或者变成整片同色,再往下又出现。

原因:条件格式的应用范围只覆盖了设计态的固定行数,比如设到$A$3:$N$50,第 51 行开始就不在规则里了。扩展单元格渲染出来的行号超出了这个范围,格式不继承。

解决:把应用范围放大到远超过预期数据量,比如$A$3:$N$5000;同时确认条件格式的公式用的是相对行号ROW()而不是绝对引用ROW($A$3),后者会让所有行都按第 3 行的结果染色,条纹直接消失。范围放大对性能影响很小,条件格式是渲染时按行判断的,不预计算。

5.5 日期校验对某些用户永远放行

现象:同一个报表,A 用户选超长区间会被拦,B 用户选同样的区间直接查出来了。

原因:new Date("2024-01-01")在不同浏览器内核下的解析行为不一致,有的按 UTC,有的按本地时间,极少数返回 Invalid Date。一旦解析失败,比较结果恒为 false,两条校验全部失效。另外,如果参数控件允许手工输入,用户敲了「2024年1月1日」这种格式,replace之后仍然解析不了。

解决:统一用yyyy/MM/dd格式解析,解析后立刻用isNaN(date.getTime())判一次,失败就弹提示并阻断刷新,绝不放行。参数控件尽量设成日期选择器,禁用自由文本输入。如果业务上必须支持多格式,就写一个小的解析函数,按yyyy/MM/dd、yyyy-MM-dd、yyyyMMdd依次尝试,全失败再提示。

5.6 参数区一行塞了七个,换行后第二行只剩一个

现象:参数面板在宽屏上看着还行,一到笔记本分辨率或者浏览器缩放 125%,筛选项自动折行,第二行孤零零挂着一个「查询」按钮,视觉上很难看。

原因:参数排版的容器宽度是自适应的,设计时按大屏摆的六个一行,实际渲染宽度不够就折行,折行位置不受控。

解决:控制每行参数个数不超过 6 个,并且按「标签 + 控件」算一个整体,不要只数控件。真正折成两行时,第二行至少保留 2 个,避免孤项。更稳的做法是把筛选项整体左对齐、宽度用百分比而不是固定像素,让它在 1280 到 1920 之间都能均匀分布。

6. 参数排版与发布前验证:上线前跑完这张表

6.1 参数排版的分配规则

参数区的排版原则原始文档写得很克制:筛选项居左,每行不超过 6 个,如果排成两行,第二行至少保留 2 个。这三条背后是视觉平衡,不是随便定的数。

每行 6 个是上限,超过之后单个控件的可用宽度会被压缩,日期选择器这种带图标的控件最先变形,日历图标被挤到框外。第二行至少 2 个,是为了避免「7 个参数排成 6+1」这种孤项布局,孤零零一个按钮挂在左边,用户视线会被拽过去,反而找不到主查询按钮。

我一般按重要性排序,把最常用的两三个参数放第一行左侧,日期区间放第一行右侧,次要的维度筛选放第二行。参数多到超过 12 个,就该考虑折叠面板或者默认值隐藏了,硬塞进去只会让首屏全是控件、看不到数据。

6.2 发布前跑一遍的验证清单

预览发布不是点一下按钮就完事。下面这张表是我每次上线前必跑的,顺序从样式到逻辑再到端上表现:

检查项操作通过标准
冻结窗格浏览器里滚到底再滚回顶表头 1-5 行始终可见,第一条数据能滚走
隔行变色翻到数据末页条纹连续,表头不被染色
列宽换一组返回列数不同的参数列宽随内容调整,合并单元格不变形
参数校验结束日期早于开始日期弹提示,不刷新,表格保持原数据
参数校验区间正好 90 天正常刷新,不拦截
参数校验区间 91 天弹提示,不刷新
参数校验清空任一日期后查询按约定放行或拦截,不出现全量拉取
参数排版浏览器缩放到 100% 和 125% 各看一次无孤项,控件不变形
导出导出 Excel 和 PDF 各一份表头重复、页码正确、金额小数位一致
移动端手机浏览器打开参数区可横向滚动,表格不溢出

90 天这个边界最容易漏测。开发时随手选两个日期,差值不是 3 天就是 300 天,正好卡在 90 天的那一次往往没人试,结果上线第一天就有用户反馈「差一天也被拦了」或者「91 天居然放行了」。我现在的习惯是边界值单独列一行,等于、小于、大于各跑一次,跑完再点发布。

还有个小技巧:这段校验逻辑不止一个报表要用,与其每份报表复制一遍宏,不如把日期解析和区间判断抽成一个公共函数,放在同一个宏文件里,各报表的main只负责取参数和调函数。这样 90 天改成 60 天时,只改一处,不用满资源目录找副本。宏管理支持多个宏共存,公共函数那个宏不挂事件、只提供函数定义就行。

吃过一次亏之后,我每次点发布前都会强制走一遍上面那张表,哪怕这次只改了一个表头文字。听起来像形式主义,但真正救过我的一次,是改文案时不小心把冻结行往下拖了一行,表格里完全看不出来,列表里滚一下才发现表头少固定了一行。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询