ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Smartbi电子表格报表设置:模板、宏校验与发布验收

Smartbi电子表格报表设置:模板、宏校验与发布验收

简介:《Smartbi报表设置文档》面向使用 Smartbi 制作电子表格报表的 BI 开发与实施人员,聚焦报表样式搭建、参数控制和发布前的细节规范。压缩包仅含 1 个 docx 文件,大小约 1.05MB,按引用模板、页面设置、隔行变色、列宽自适应、参数设置与排版、预览发布等环节组织,便于按步骤查阅。内容除基础知识点外,还给出列宽自适应脚本、结束日期不早于开始日期且跨度不超过 90 天的参数校验宏,以及筛选项每行不超过 6 个等排版提示,可直接对照实践。已有 1305 人学习,适合需要快速上手 Smartbi 报表设置或查漏补缺的初中级用户。

1. 一份 Smartbi 报表设置文档,能省掉多少返工

上周帮人看一张电子表格报表,打开就是一片####——列宽没调,日期列被挤成三个字符;往下拉,隔行底色在合计行突然断掉;切到参数区,六个筛选项挤成两行,第二行孤零零挂着一个「结束日期」。这张表功能没问题、数据也准,但交付前还得再花半天收拾。这份 Smartbi 报表设置文档解决的正是这类「最后一公里」的活:从 Excel 插件里引模板、设冻结行和打印区、配隔行色、挂列宽自适应宏、写参数联动校验、排参数、发布,一条链路按顺序固定下来。它不讲产品架构,是一份可以直接照着操作的素材清单。适合已经会用电子表格画表、但每次交付都要返工的人,也适合想把流程沉淀成团队模板的人。

2. 模板引用与页面设置:把冻结窗格和打印区一次定死

页面设置是整份文档里最容易被跳过的一步,也是最容易返工的一步。样式可以慢慢改,但冻结窗格、顶端标题行、打印区域这三项如果一开始没定,等到数据行涨到几百行、用户滚到第 80 行看不到表头的时候再回头补,改的就不只是报表,而是所有人的使用习惯。这一章把模板引用的边界、页面设置的必设项、以及「第一行空白行」这个细节讲清楚。

2.1 模板和报表的分工:为什么先在模板上画

在 Excel 的 Smartbi 页签里点「模板」,打开的是一个和普通 xlsx 长得一样的编辑界面,但又和报表共用同一套渲染能力。这个设计的价值在于:模板是素材底座,报表是实例。样式、行高列宽、页面设置、部分宏放在模板里,改一次,所有引用它的报表跟着变;反过来,每张报表从零画,改一次样式就要改 N 张。

我一般的做法是先在模板上把骨架搭完:标题行长什么样、表头占几行、合计行在哪、哪些列是数值哪些列是日期。骨架定了再另存为报表往里面填数据集,这样后面调样式的时候不会动到数据绑定。

这里有个边界要说清楚:模板里的宏不一定百分百跟着复制到新报表,插件的版本差异会导致部分内容丢失。所以另存为报表之后,第一件事是打开 Smartbi 页签下的「宏管理」,确认宏还在、函数名还是main、代码没被截断。这一步三十秒,能省掉后面半小时的排查。

还有一点,模板不要放数据。模板一旦绑了数据集,下次别人引用它做新报表,第一件事就是解绑,解绑过程里很容易把格式带乱。模板只放结构和样式,数据绑定留给报表。

2.2 页面设置的四个必设项

页面设置分散在 Excel 的两个地方:一个是「页面布局」选项卡,一个是「视图」选项卡。很多人只记得设打印,忘了设冻结。下面这四项建议一次配齐,配完再往下走。

设置项位置建议值不设的后果
顶端标题行页面布局 → 打印标题$1:$5打印或导出 PDF 时只有第一页有表头
冻结窗格视图 → 冻结窗格冻结至第 5 行数据一多,用户滚动后分不清列含义
打印区域页面布局 → 打印区域手动框选有效区右侧空白列被打印成一片空页
缩放页面设置 → 缩放调整为 1 页宽宽表被切成两三页,列被拦腰截断

这四项里,冻结窗格和顶端标题行是两套独立机制,很多新手以为设了冻结就等于设了打印标题,实际上一个管屏幕、一个管纸面,互不相干。导出 PDF 的时候只认后者。

再说第一行。原文档特别标注了「第一行是空白行,所以上面报表冻结的是 1-5 行」。这句话的意思是:模板的第 1 行故意留空,可能是给标题、副标题或者单位说明留位置,所以真正需要冻结的表头实际落在第 1 到第 5 行这个区间。如果你的模板没有空行,冻结行数就要相应减一。

2.3 第一行留空:隐藏还是删除

空白行这件事看起来无害,但处理方式不同,后面改动成本差很多。

删除的代价是:所有行引用整体上移一行。你在条件格式里写的$A$6:$H$1000、在宏里写死的行号、在打印标题里设的$1:$5,全部要跟着改。一处没改,隔行变色就从第二行开始错位,表面看是「颜色不对」,实际是引用漂了。

隐藏的代价是:行还在,但看不见,冻结窗格的时候隐藏行仍然占位置,表格顶部会莫名其妙多出一条缝。

我的建议是保留空行,但把行高压到很小,比如 6 到 8 磅。视觉上几乎看不出,行引用一个都不用改。等哪天真要放标题了,把行高调回来就行。这种做法在要反复迭代的报表里特别省事,算是给自己留的后悔药。

2.4 从模板到新报表的落地步骤

把上面几件事串成一条可执行的顺序,照着走一遍就行:

  1. 在 Excel 里打开 Smartbi 页签,登录账号,点「模板」,选择目标模板打开。
  2. 在模板上改样式:行高、列宽、字体、边框、数字格式。这一步只动格式,不动内容。
  3. 切到「页面布局」,设顶端标题行为$1:$5,设打印区域,缩放选「调整为 1 页宽」。
  4. 切到「视图」,光标点到第 6 行第一列,冻结窗格选「冻结拆分窗格」,确认第 1 到 5 行被锁住。
  5. 另存为报表,命名规则建议带上业务域和数据源,别用「新建报表1」这种。
  6. 打开「宏管理」,确认宏还在,函数入口是main,参数名是spreadsheetReport。
  7. 绑定数据集,预览,确认冻结和标题行在预览状态下同样生效。

第 6 步单独拎出来说,是因为它最容易被当成「多余的检查」跳过。我见过不止一次,模板里宏写得好好的,另存之后代码段少了一半,预览时列宽纹丝不动,排查半天以为是 API 不兼容,最后发现是复制没复制全。这种坑没有任何报错提示,纯靠人眼核对,属于典型的黑匣子问题。

另外提一句,页面设置里的缩放和浏览器预览时的缩放不是一回事。前者只影响打印和导出,后者是查看器的显示比例,别在预览里调好了缩放就以为打印也好了,两边得分别验一遍。

3. 隔行变色与列宽自适应:一段 12 行 JS 撑住整张表

数据和样式分离之后,剩下的活基本都是「让表好看且好读」。隔行变色和列宽自适应是两件最影响观感的事,前者靠条件格式,后者必须靠宏。这一段把两种做法的取舍、宏的完整代码、以及几个参数怎么改讲透。

3.1 隔行变色:条件格式和手工底色怎么选

三种做法,适用场景不一样。

手工填充底色:适合行数固定、几乎不改的报表。优点是所见即所得,导出绝对不变形;缺点是数据行一变,颜色就错位,加一行要重刷一遍。属于一次性的活。

条件格式:公式写=MOD(ROW(),2)=0,应用区域选从表头下方第一行到最后一行,比如$A$6:$H$1000。数据增减时颜色自动跟着走,是常规报表的首选。注意公式里的行号要和实际起始行对齐——如果表头占到第 5 行,公式从第 6 行开始判断,MOD(ROW(),2)在第 6 行返回 0,第 6 行就着色,视觉上第一行数据是带底色的。想反过来就把条件改成=1。

报表样式属性:部分版本在电子表格的属性面板里有隔行显示的开关,勾一下就行。能不能用取决于你的版本,有就用,省事;没有就退回条件格式。

条件格式有两个必须知道的边界。一是合并单元格会让ROW()的判断错乱,一个跨三行的合并格,取的是左上角那一行的行号,颜色会跳着走;二是分组小计行、合计行往往需要单独强调,如果它们正好落在着色行上,底色会和小计的加粗边框打架,看起来脏。稳妥的做法是给合计行单独加一条规则,优先级放在隔行规则之上,用「停止如果为真」把后面的规则截断。

3.2 列宽自适应宏的完整代码

列宽这块,Excel 自己的「自动调整列宽」在 Smartbi 电子表格里不一定生效,因为最终渲染在浏览器里做,Excel 的列宽是个近似值。所以要靠宏在渲染阶段调 API。

function main(spreadsheetReport) { // 取第一个工作表,多 sheet 时改索引 var sheet = spreadsheetReport.workbook.worksheets.get(0); if (sheet === "" || sheet === null) { return; } // 取当前显示范围的最大列数,注意是"显示范围"不是"数据区" var counts = sheet.cells.maxDisplayRange.columnCount; // 从第 0 列到 counts 列全部自适应 sheet.autoFitColumns(0, counts); // 只对第 4 列(索引 3)自适应时用下面这句 // sheet.autoFitColumn(3); }

这段代码是通用模板,几乎不用改就能跑。入口函数名必须是main,参数必须是spreadsheetReport,这是 Smartbi 电子表格宏的约定,名字写错就是静默不执行,没有任何提示。

3.3 逐行拆解与参数改法

worksheets.get(0)里的 0 是工作表索引,从 0 开始。报表里有多个 sheet 时,想调第二个就写get(1)。如果 sheet 名固定,也可以用名字取,但索引更稳,改名的场景下不会挂。

maxDisplayRange.columnCount取的是「当前显示范围」的最大列数,不是数据集里的字段数。这两个值经常不等:数据集返回 12 个字段,但报表只用了其中 8 列,columnCount就是 8 左右。这意味着如果你在报表右侧留了空白列做视觉缓冲,自适应会把它们也算进去,结果是有效列被压窄。遇到这种情况,把autoFitColumns的结束位置改成固定数字,比如sheet.autoFitColumns(0, 7)。

autoFitColumns(start, end)的边界语义在不同版本上略有差异,原文注释写的是「从 0 开始,到 counts 列结束」。稳妥的调法是先用counts跑一遍看效果,如果最右一列没吃到自适应,就改成counts - 1或者counts + 1试一次。多取一列不会报错,少取一列会漏,所以宁可多取。

autoFitColumn(3)是单列版本,索引从 0 开始,第 4 列就是 3。什么时候用单列?两种情况:一是大部分列宽你已经手工定死了,只有备注、地址这类内容长度不定的列需要自适应;二是全量自适应太慢,先只放开通用的几列。

3.4 执行时机与性能:别让它每次都跑

main函数在报表加载和刷新时都会进。数据量小的时候无所谓,几万行的表上跑一次全量自适应,肉眼可见地卡一下。用户每点一次查询就卡一下,体验很差。

常见的处理是加个标志位,只在首次加载时自适应:

function main(spreadsheetReport) { // 只在第一次进入时做自适应,后续刷新跳过 if (spreadsheetReport._fitDone) { return; } var sheet = spreadsheetReport.workbook.worksheets.get(0); if (sheet === "" || sheet === null) { return; } var counts = sheet.cells.maxDisplayRange.columnCount; sheet.autoFitColumns(0, counts); spreadsheetReport._fitDone = true; }

_fitDone是挂在报表对象上的自定义属性,生命周期跟着当前这次会话走,刷新页面会重置,这个粒度刚好——同一次浏览里不重复算,重新打开又能按新数据算一遍。

代价是:如果后续查询返回的字段长度变化很大,列宽会停在第一次的宽度上。取舍看场景,参数化查询里字段结构基本固定,用这个方案没问题;如果报表支持动态选字段,就别加标志位,宁可慢一点。

真正的血泪经验是:不要在没有测量过数据量的情况下,给一张几十列宽表挂全量自适应。先手工把明显过宽的列定死宽度,只把少数几列交给宏,性能问题基本就消失了。

4. 参数联动校验:劫持 doRefresh 把日期区间管在 90 天内

参数区是用户和报表交互的第一现场。两个日期参数,用户完全可能先选结束、再选开始,或者把区间拉到半年。这类校验如果只靠口头约定,迟早翻车。原文档给的做法是用宏覆盖刷新方法,在真正查询之前把参数拦下来。这一章把这段代码讲透,包括几个不写就会出问题的细节。

4.1 为什么要覆盖 doRefresh,而不是写个按钮事件

最直觉的做法是给「查询」按钮绑一个校验函数,校验通过再调用查询。问题是报表的刷新入口不止一个:按钮能触发,参数变化能触发,定时刷新也能触发。只堵一个口子,等于没堵。

doRefresh是刷新动作的统一入口,把它换掉,所有触发路径都会先过一遍你的校验。原文档的思路就是这套:先把原始的doRefresh存到一个自定义属性上,再用一个新函数替换它,新函数里先校验、校验通过再调原始方法。

有个细节值得单独说:存原始方法的动作套了一层if (!spreadsheetReport._jhy_doRefresh)判断。这层判断不是为了好看,是为了防止二次覆盖。如果main被重复执行,没有这层判断的话,_jhy_doRefresh会被赋成「已经被覆盖过的那个函数」,再调用就变成自己调自己,直接爆栈。加上判断,第二次进来发现已经有了,就跳过保存,只重新挂一遍外层函数,原始引用始终指向最开始的实现。

4.2 完整宏代码与逐段说明

function main(spreadsheetReport) { // 保存原始刷新方法,只保存一次,防止重复覆盖导致递归 if (!spreadsheetReport._jhy_doRefresh) { spreadsheetReport._jhy_doRefresh = spreadsheetReport.doRefresh; } spreadsheetReport.doRefresh = function(fromButton, delayMask) { // 按参数名取值,名称必须和参数面板里完全一致 var endtime = new Date( spreadsheetReport.getParameterValue("结束日期 2").replace(/\-/g, "/") ); var starttime = new Date( spreadsheetReport.getParameterValue("开始日期 2").replace(/\-/g, "/") ); // 规则一:结束日期不能早于开始日期 if (endtime < starttime) { setTimeout(function() { alert("结束时间不能早于起始时间"); }, 100); return; // 直接返回,不调用原始刷新,等于拦下这次查询 } // 规则二:区间跨度不超过 90 天 if (endtime.getTime() - starttime.getTime() > 1000 * 3600 * 24 * 90) { setTimeout(function() { alert("两个参数的差不能超过 90 天"); }, 100); return; } // 校验通过,走原始刷新 this._jhy_doRefresh(fromButton, delayMask); }; }

逻辑顺序是「取值 → 校验 → 拦截或放行」。两个return各自对应一条规则,命中就退出,不进原始刷新。最后一行this._jhy_doRefresh(fromButton, delayMask)是放行通道,两个参数fromButton和delayMask要原样透传,少传一个,按钮触发的刷新和遮罩行为就会跟原生不一致。

4.3 参数名、日期解析、延时弹窗三处细节

参数名。getParameterValue("结束日期 2")里的字符串,必须和参数面板上显示的名字逐字符一致,包括中间的空格和后面的序号。这类名字通常是复制粘贴来的,肉眼看着一样,实际可能一个是半角空格一个是全角,或者序号是中文「二」不是数字「2」。排查时最快的办法是在宏里先console.log打出取到的值,值为undefined就说明名字对不上。这个坑非常隐蔽,表现是「校验完全没反应」,容易被误判成宏没生效。

日期解析。replace(/\-/g, "/")这行不能省。new Date("2024-01-01")在部分内核里解析成 UTC 时间,new Date("2024/01/01")解析成本地时间,两者在做减法的时候可能差出几个小时,边界日期上就会出现「明明没超 90 天却提示超了」的玄学现象。统一换成斜杠格式,两边都按本地时间解析,比较才可靠。

延时弹窗。为什么不是直接alert,而是套一层setTimeout(..., 100)?因为刷新动作本身会盖一层遮罩,同步弹窗可能在遮罩还在的时候弹出来,用户看到的是一个被灰层压住的框,甚至点了确定遮罩还没散。延后 100 毫秒,等 DOM 稳定了再弹,视觉上干净很多。100 这个数字不用改,太小遮罩没散,太大用户会觉得点了没反应。

4.4 空值和空串:两条规则之外还得补这一手

原代码没有处理参数为空的情况。实际用起来,用户很可能先清空日期再点查询,这时getParameterValue返回空串,new Date("")得到的是Invalid Date,任何比较都返回false。后果是两条规则同时失效,查询照常放行,后端拿到空参数可能直接报错,或者更糟,查出全量数据。

补法很简单,在取值之后、比较之前加一段:

var startStr = spreadsheetReport.getParameterValue("开始日期 2"); var endStr = spreadsheetReport.getParameterValue("结束日期 2"); // 任一为空则跳过校验,交给后端或参数默认值处理 if (!startStr || !endStr) { this._jhy_doRefresh(fromButton, delayMask); return; }

这里选择「放行」而不是「拦截」,理由是空参数在多数报表里代表「不限」,属于合法输入。如果你的业务规则要求两个日期必填,把放行改成弹提示再return就行,逻辑结构一样。

顺带说一个命名上的注意点。自定义属性用了_jhy_doRefresh这种带前缀的名字,是个好习惯。同一张报表里如果还有别的宏也要劫持doRefresh,前缀能避免互相覆盖。我见过两个宏都用_originalRefresh这个名字,后加载的把前面的覆盖掉,表现是「刷新走了两次,第二次没校验」,查起来相当费劲。

5. 避坑与排查:五个让报表返工的细节

前面几章讲的是「怎么配」,这一章讲「配完了不对怎么办」。下面五条都是实际交付里反复出现的,按「现象 → 原因 → 解决」写,遇到问题时可以直接对号入座。

5.1 渲染层面的三个坑

坑一:预览里列宽正常,导出 PDF 又变回####。

现象:浏览器预览时每一列都撑开了,导出 PDF 打开一看,日期列还是被压成井号。

原因:宏里的autoFitColumns作用在浏览器渲染阶段,导出走的是另一条渲染链路,宏不一定在里面执行;即使执行了,导出用的是打印布局,列宽还要受页面设置里「缩放」的二次压缩。

解决:两条路一起走。一是把明显会溢出的列(日期、金额、长文本备注)手工定死一个够用的宽度,不依赖自适应;二是在页面设置里把缩放从「调整为 1 页宽」改成固定百分比,比如 85%,给列宽留余量。定死宽度这件事看着笨,但在导出场景下最可靠。

坑二:日期校验的弹窗弹了两次,或者弹完还能查。

现象:选了不合法的日期区间,点查询,弹窗出现两次;或者弹窗点掉之后,报表还是刷新了。

原因:弹两次通常是main被重复执行,doRefresh被覆盖了两层,每次刷新走两层校验各弹一次;弹完还能查,是某个分支漏写了return,校验提示完继续往下走到了this._jhy_doRefresh。

解决:先检查保存原始方法的那段有没有if (!spreadsheetReport._jhy_doRefresh)判断,没有就补上;再逐行核对每个alert后面是不是紧跟return。这两个问题都是纯代码层面的,把宏全文贴出来数一遍return的个数就能定位,比在浏览器里打断点快。

坑三:隔行变色把合计行也染了,看起来脏。

现象:数据区隔行变色正常,最下面的合计行也被涂了底色,和小计的加粗上边框撞在一起。

原因:条件格式的应用区域一直拉到了最后一行,MOD(ROW(),2)不区分数据行和合计行,轮到谁染谁。

解决:给合计行单独加一条规则,条件用行内容判断(比如该行第一列是「合计」),格式设为无填充或者固定的强调色,并在规则管理里勾上「如果为真则停止」。顺序上这条要排在隔行规则之前,否则不生效。规则顺序在条件格式管理器里用上下箭头调,调完记得保存。

5.2 配置与操作层面的两个坑

坑四:参数面板换行后,第二行只剩一个参数。

现象:六个筛选项排成两行,第一行五个,第二行孤零零一个「结束日期」,右边空出一大片,看着别扭。

原因:参数排版没有约束,插件按容器宽度自动折行,宽度刚好卡在第五个后面。

解决:按原文档的规则来——一行不超过 6 个;如果确定要分两行,第二行至少保留 2 个。具体操作是把参数按业务含义分组,开始日期和结束日期必须挨着,同组的筛选条件不要被拆到两行。分组之后如果还是排不满,把个别参数改成下拉或者日期选择器,控件宽度变了,折行位置自然会跟着变。这条没有技术难度,纯粹是排版耐心。

坑五:宏改完保存了,预览还是老样子。

现象:代码里明明加了alert,预览时一次都不弹;或者改了列宽逻辑,效果和改之前一模一样。

原因:三种可能。一是宏没保存成功,插件里点的是「另存」不是「保存」,改动在临时文件里;二是宏挂错了对象,写在了模板上但预览的是报表;三是浏览器缓存了旧版本的报表资源。

解决:按顺序排查。先关掉预览,回 Excel 打开宏管理,把代码全文看一遍,确认改动在里面;再看当前编辑的是模板还是报表,标题栏一般有区分;最后强制刷新预览页面,或者在地址后面加个随机参数绕开缓存。三步走完基本能定位。这一步的经验是:改宏之后永远先做一次「看得见的变化」验证,比如临时加一行console.log,确认执行到了再加业务逻辑,不然改十次都不知道哪次生效了。

6. 发布前的验收:参数排版三条线和一个复核习惯

发布这一步最容易敷衍。点一下「发布」,选个目录,权限一配,就算完事。但报表交付之后是要给别人用的,用的人不会告诉你哪里别扭,只会默默不用。所以发布前我固定走一遍验收,重点看参数排版和三个容易被忽略的角落。

参数排版的三条线,是原文档里最朴素也最实用的规则:筛选项居左对齐;每行不超过 6 个;如果排成两行,第二行至少保留 2 个。第一条保证视觉起点一致,用户扫视的时候不用来回找;第二条防止一行塞太满,窄屏下直接折行折得乱七八糟;第三条是为了视觉平衡,第二行只剩一个控件的时候,整块区域会显得头重脚轻。这三条没有技术门槛,但坚持做和不做,交付出去的东西是两个档次。

三个角落,是我每次都要单独点一遍的:

检查项怎么验不合格的表现
冻结行预览里滚到第 50 行表头跟着滚走了
参数联动故意选一个结束早于开始的区间没有提示,直接查出空数据
导出导一次 PDF,翻到第 2 页第 2 页没有表头,或者列被截断

参数联动那一项,一定要用「非法输入」去验,不要用正常值点两下了事。校验逻辑的价值只在异常路径上体现,正常路径本来就不会触发。我一般会准备三组数据:结束早于开始、跨度刚好 90 天、跨度 91 天。三组都符合预期,这段宏才算过关。

导出这一项也值得单独说。很多人验报表只在预览里看,觉得浏览器里对就行了。但实际使用中,PDF 导出和打印是高频操作,而导出走的是另一套渲染,前面第 5 章讲过的列宽问题就出在这里。翻到第 2 页看表头,翻到最右边看有没有被截断的列,两分钟的事。

最后说一个习惯。这套流程里的宏,我现在一律先写在一个临时报表上跑通,确认没有语法错、能取到参数值、弹窗能正常出现,再往正式模板里搬。原因很直接:正式模板往往已经被别人引用,宏写坏了影响的不止一张表,回滚还得挨个通知。临时报表跑通了再搬,多花五分钟,省掉的是整条链路的返工。这个习惯是被一次线上事故逼出来的——当时一段没测过的校验宏直接写进了公用模板,导致所有引用它的报表在选日期时都卡住,排查了两个小时才发现是一个参数名多了个空格。

希望这些细节能帮到你,少走几段弯路。这份报表设置文档的价值不在某一段代码有多巧,而在它把「模板 → 页面设置 → 样式 → 宏 → 参数 → 发布」的顺序固定下来了,照着走一遍,出来的东西至少是能交付的。

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

返回列表