ARTICLE DETAIL

资讯详情

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

Excel Smartbi报表设置:模板、列宽自适应与参数校验

Excel Smartbi报表设置:模板、列宽自适应与参数校验

简介:《Smartbi报表设置文档》面向使用Smartbi制作电子表格报表的设计人员与开发人员,围绕报表从模板引用到预览发布的完整设置流程展开,适合需要规范报表样式、补全参数校验逻辑的初、中级使用者参考。资源包共1个docx文件,约1.05MB,以图文步骤与可复用代码片段为主,体量轻便,便于随查随用。内容覆盖引用模板、页面设置与冻结行处理、隔行不同颜色、列宽自适应、参数设置与参数排版等环节,其中列宽全局自适应给出可直接套用的JavaScript宏,结束日期不早于开始日期且间隔不超过90天的参数校验也附有完整宏示例,并提示参数筛选项居左、单行不超过6个等排版细节。无论是首次搭建报表样式,还是为已有报表补充交互校验与版式优化,都能从中找到对应做法。目前已有1304人学习下载,适合作为Smartbi报表设置的操作手册与宏脚本参考。

1. 一份 Excel 里的 Smartbi 页签,撑起整套报表设置流程

多数人做报表的顺序是反的:先在 Excel 里把样式画满,回头补参数、补刷新逻辑,最后发现预览时列宽全炸开,结束日期比开始日期还早也能查出一堆数据。这份 Smartbi 报表设置文档走的是另一条路——先在 Excel 的 Smartbi 页签里引用模板把骨架钉死,再叠页面设置、隔行换色、列宽自适应脚本、参数校验宏,最后才谈参数排版和预览发布。它面向的是每天要在 Excel 里出固定格式报表的人,尤其适合接手别人模板、天天被追问“格式为什么不一致”的岗位。下面按真实操作顺序拆,代码段可以直接抄。

2. 引用模板与页面设置:把报表骨架先钉死

报表出问题的根源,八成不在最后一步预览,而在最初五分钟:模板引错了、冻结行算错了、隔行色填充方式选错了。这三件事一旦定下来,后面所有脚本都是在它们上面打补丁,补丁越多越难维护。所以这一章不讲宏,只讲骨架怎么搭才算稳。

2.1 模板引用:先另存为,再动第一笔

Smartbi 页签下的“模板”是一个共享素材入口,里面通常已经绑好了数据集、表头样式、单元格格式、参数控件占位。正确动作是打开模板之后立刻另存为一份自己的报表,再在副本上改样式。直接改模板的后果不是当场报错,而是下次别人引用同一份模板时,发现表头被你换成了另一套字段名,排查起来毫无线索。

动作操作位置注意点
登录Excel → Smartbi 页签 → 登录服务器地址与账号由管理员分配,切环境后必须重新登录,否则模板列表是空的
打开模板Smartbi 页签 → 模板 → 选分类 → 打开打开即挂载数据集,此时改样式会污染共享素材
另存新报表报表另存为 / 另存到个人目录命名带业务名和日期,例如“销售明细_月度_202401”
校验绑定数据区右键查看数据集字段字段名和类型必须与模板一致,类型被改过会导致数字列变文本

引用完模板,第一件事不是改颜色,是确认三样东西:数据集连接是否指向你自己的库、参数控件是否已经生成占位单元格、表头行里有没有残留的上一个业务字段。常见的做法是把模板里的示例数据先刷一遍,看预览能不能出数,能出数再改样式。如果模板一打开预览就报数据集无权限,先找管理员要权限,别急着在 Excel 里改公式——那不是样式问题。

另存的时候建议保留一份“原始模板副本”不动,只在自己的报表上迭代。我见过太多人把模板改成四不像之后,想回头看看原版长什么样,结果模板本身也被覆盖了,只能去回收站捞。这类后悔药没必要吃。

2.2 页面设置:冻结 1-5 行的行号账

冻结这件事看起来最简单,也最容易算错一行。文档里写得很明确:第一行是空白行,所以冻结的是 1-5 行。这意味实际冻结线落在第 6 行的顶端,不是第 5 行。Excel 里选中第 5 行点冻结,冻住的是 1-4 行;选中第 6 行点冻结,才是 1-5 行。差一行,预览的时候表头就会跟着滚走半截。

操作路径就三步:选中 A6 单元格 → 视图 → 冻结窗格 → 冻结拆分窗格。为什么强调选 A6 而不是选第 6 行整行?因为冻结同时会锁列,选中 A6 相当于只锁行不锁列,参数区在左侧时不会被一起冻住;如果参数是竖排在左边一列,那就该选 B6,把第一列也锁上。这个细节文档没展开,但实际排版里躲不掉。

第一行为什么要留空白,值得单独说一句。Smartbi 渲染报表时,参数控件区、工具条提示、行内按钮都要占位,留一行空行相当于给渲染层留了缓冲带。有人图省事把表头直接写在第 1 行,冻结是省了,但参数多的时候控件会压到表头上,看起来像数据错位,实际是布局没留余量。

设置项取值说明
冻结窗格选中 A6 后冻结锁 1-5 行;参数在左列时改选 B6
顶端标题行$1:$5打印时每页重复表头
缩放调整为 1 页宽纵向页数不限,横向压到一页,避免列被截断
页边距窄宽表必须窄边距,否则最后一列总差几毫米

打印标题行和冻结行是两套配置,互不影响。冻结只管屏幕上滚,打印标题行只管纸面上重复。有人以为冻了 1-5 行打印就会自动重复表头,结果打出来第二页直接是数据,回头还得重新设一遍。

2.3 隔行不同颜色:条件格式优先,别手动填色

隔行换色能让长报表的横向阅读不串行,这个需求本身没问题,问题在实现方式。手动隔一行填一次色的做法,在行数固定的静态表上看着没问题,一旦数据行数变化,颜色就和数据错位了:原来第 6 行是浅灰,数据多了一行之后,原本该是浅灰的那行变成白底,整张表的斑马纹从中间断掉。

推荐用条件格式。选中数据区A6:Z2000(上界按你预估的最大行数取,取大一点没坏处),开始 → 条件格式 → 新建规则 → 使用公式确定要设置格式的单元格,公式填:

// 判断当前行是否为偶数行,偶数行填充浅色 =MOD(ROW(),2)=0

格式里把填充色设成一个固定的浅灰,比如 RGB(242,242,242),不要用主题色。原因是主题色在导出 Excel 或者别的客户端打开时,跟随主题变化,斑马纹可能变成蓝的、绿的,甚至和表头色撞上。固定 RGB 值走哪都是同一个颜色。

设置的时候有两个细节容易翻车。第一是起始行的奇偶:条件格式的 ROW() 返回的是绝对行号,如果你的数据从第 6 行开始,第 6 行是偶数,第一行数据就会被染色,视觉上像是“表头下面第一行是灰的”,需要把公式改成=MOD(ROW(),2)=1来错开。第二是应用范围不要从 A1 开始选,选了会把表头一起卷进去,表头本身已经有底色,再叠一层条件格式,结果是表头变成斑马纹的第一段。

方式行数变化时导出表现维护成本
手动填色颜色错位稳定每次改行数都要重填
条件格式(固定 RGB)自动跟随稳定设一次不用管
条件格式(主题色)自动跟随可能变色需要额外验证

数据区如果有合并单元格,条件格式按合并区域左上角单元格的行号判定,整块合并区会用同一个颜色,这时候斑马纹会看起来“一块一块”的。这种表就别硬上隔行色,改成按分组合并加边框更稳。

3. 列宽自适应:一段通用 main 函数怎么改才不出事

列宽是报表里最玄学的一环。同一份报表,在你机器上列宽刚好,到别人机器上第一列就被挤成三个字换一行。这不是 Smartbi 的锅,多半是自适应脚本写得太“全局”——它把所有列都按内容撑到最大,而某个备注字段里恰好有一条三百字的说明。这一章把那段通用 main 函数拆开,说清每一行的含义、哪里该改、边界在哪。

3.1 autoFitColumns 的调用链与 counts 的取值

文档给的通用代码如下:

function main(spreadsheetReport) { var sheet = spreadsheetReport.workbook.worksheets.get(0); if (sheet === "" || sheet === null) { return; } var counts = sheet.cells.maxDisplayRange.columnCount; // 获取电子表格列数 sheet.autoFitColumns(0, counts); // 从 0 开始,到 counts 列结束 // sheet.autoFitColumn(3); // 只对某列进行自适应,第一列为 0 }

逐行说。spreadsheetReport.workbook是当前报表挂载的工作簿对象,后面所有取 sheet、取单元格的动作都从这里出发。worksheets.get(0)取第一张工作表,索引从 0 开始,所以第一个 sheet 是 0 不是 1。maxDisplayRange是当前有内容的最大显示区域,columnCount是这个区域的列数——注意它是“数”,不是“最大索引”,10 列内容的 columnCount 是 10,最大索引是 9。

autoFitColumns(0, counts)的意思是从第 0 列开始,一直自适应到第 counts 列。这里传 counts 而不是 counts-1,多算一列一般无害,因为超出内容范围的列自适应后宽度也不会异常;但如果你的表右边紧挨着另一个区域,多这一列可能把它也一起撑开。

第一段判空sheet === "" || sheet === null是老写法,实际取不到 sheet 时返回的是undefined或者空对象,跟空字符串比永远不相等。稳妥点改成:

function main(spreadsheetReport) { var sheet = spreadsheetReport.workbook.worksheets.get(0); if (!sheet) { // 取不到 sheet 直接退出,避免后续报 null 引用 return; } var counts = sheet.cells.maxDisplayRange.columnCount; if (!counts || counts <= 0) { // 空表没有可自适应的列,直接返回 return; } sheet.autoFitColumns(0, counts); }

参数说明:counts来自maxDisplayRange,如果报表刚打开还没渲染完,这个值可能是 0 或者很小的数,自适应会失效,表现为“第一次打开列宽不对,点一次刷新就正常了”。遇到这种时序问题,别去改脚本逻辑,先确认这段宏挂在哪个事件上——挂在报表加载完成后触发的宏里,比挂在渲染前稳得多。

3.2 局部列自适应:调试期先单列试

全局自适应写起来省事,调起来痛苦:你不知道是哪一列把整张表撑爆的。调试阶段建议把全局那行注释掉,改用单列自适应,一列一列试:

function main(spreadsheetReport) { var sheet = spreadsheetReport.workbook.worksheets.get(0); if (!sheet) { return; } // 调试期只对第 4 列(索引 3)自适应,其余列保持手工设定的宽度 sheet.autoFitColumn(3); }

autoFitColumn(3)里的 3 是列索引,第一列是 0,所以第 4 列对应 3。这个数字和 Excel 里的列号差 1,A 列是 0,B 列是 1,很容易看错。调的时候建议在 Excel 里先看一眼要调的列是第几列(A=1),减 1 再填进去。

调的顺序我一般是这样:先把关键列(金额、数量、日期)单独自适应,看它们撑到多宽;再放开全局自适应,对比差异,差异特别大的那几列就是罪魁祸首,单独给它们设固定宽度或者加截断。这个套路比盯着整张表瞎猜快得多。

3.3 边界:合并单元格、超长文本和性能

自适应不是万能的,它的判定依据是单元格里的文本长度和字体,遇到下面三种情况就会失灵。

合并单元格。自适应只认单个单元格的内容,跨列合并的区域它按最左列的宽度算,结果就是合并区看起来被压扁或者被拉得特别宽。表格里如果有大段说明放在合并单元格里,最好把这列从自适应范围里排除掉,手动定宽。

超长文本。一个备注字段里塞了三百字,自适应会把这列撑到几百像素,整张表横向要滚动才能看完。解决办法不是不给它自适应,而是自适应之后做一次收口:

function main(spreadsheetReport) { var sheet = spreadsheetReport.workbook.worksheets.get(0); if (!sheet) { return; } var counts = sheet.cells.maxDisplayRange.columnCount; var limit = Math.min(counts, 30); // 只处理前 30 列,后面的列不动 sheet.autoFitColumns(0, limit); for (var i = 0; i < limit; i++) { var w = sheet.getColumnWidth(i); if (w > 320) { // 320 像素是经验上限,超过就压回来 sheet.setColumnWidth(i, 320); } } }

limit用Math.min兜一下,防止列数特别多的时候逐列遍历拖慢渲染;320这个上限按你的字号和屏幕宽度调,一般正文 11 号字、320 像素能放下二十来个汉字,够用。收口之后超长文本会显示不全,配合单元格的自动换行(不是自适应宽度,是 wrap text)一起用,行高会自动撑开,比横向滚动友好。

性能。列数上百、数据行几千的表,全局自适应会明显卡顿,因为它是逐列测量。常见的优化是只对表头行和数据前若干行做采样,或者干脆关掉自适应、改成按字段类型预设宽度(金额列 90、日期列 100、文本列 180),一次配好不再动。后者牺牲一点灵活性,换来的是每次打开都快。

4. 参数联动校验:90 天窗口的宏怎么写才不翻车

参数这块的需求通常很朴素:结束日期不能早于开始日期,且两者相差不超过 90 天。听起来两行 if 就完了,实际写起来会撞上三个问题——怎么拦住刷新、日期字符串怎么解析、报错提示为什么用户看不见。这一章按这三个问题拆。

4.1 重写 doRefresh:先存原函数,再做守卫

校验必须挂在“刷新”这个动作上,而不是参数控件的值变化事件上。原因是用户可能先改开始日期、再改结束日期,中间那一次值变化是合法但无意义的组合,在这个时机拦他属于误伤。点刷新才是一次完整的查询意图,这时候校验最准。

doRefresh是刷新入口,重写它的标准写法是先保存原函数引用:

function main(spreadsheetReport) { // 只在第一次进入时保存原始刷新函数,避免重复包装导致递归 if (!spreadsheetReport._jhy_doRefresh) { spreadsheetReport._jhy_doRefresh = spreadsheetReport.doRefresh; } spreadsheetReport.doRefresh = function (fromButton, delayMask) { // 校验逻辑放在这里,不通过就直接 return,不再往下走 // ... this._jhy_doRefresh(fromButton, delayMask); // 校验通过,调原始函数真正刷新 }; }

_jhy_doRefresh这个属性名是自定义的,换个名字也行,作用是暂存原函数。如果没有if (!...)这层守卫,宏每执行一次就把当前(已经被包装过的)doRefresh再存一遍,包装层越套越多,点一次刷新弹好几次提示,甚至栈溢出。这个坑我在别的脚本里踩过,现象是“第一次点刷新正常,第二次点弹两个框,第三次弹三个”,排查半天才反应过来是重复包装。

fromButton和delayMask是两个入参,前者标识是否由按钮触发,后者控制刷新时的遮罩动画,原样透传给原函数就行,不要自己造值。有人为了“跳过遮罩加快刷新”硬传false,结果按钮状态和加载动画对不上,用户以为没点上,连点三次。

4.2 日期解析与 90 天差值:几个必须写对的细节

文档给的校验代码如下:

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); }; }

第一个细节:replace(/\-/g, "/")。new Date("2024-01-01")在某些解析器里按 UTC 处理,new Date("2024/01/01")按本地时区处理,两者差 8 小时。日期比较本身不受影响,但如果你后面要做“相差天数”的取整计算,8 小时会让结果差一天。统一换成斜杠是最省事的兼容写法。

第二个细节:参数名带空格。"结束日期 2"中间有个空格,这个空格必须和参数面板里的名称逐字符一致,包括全角半角。手打很容易漏,正确做法是去参数定义里复制名称粘贴过来。名称对不上时getParameterValue返回null,null.replace直接抛异常,整个刷新被中断,用户看到的是“点了刷新没反应”。

第三个细节:setTimeout(..., 100)。刷新过程中页面上有一层加载遮罩,alert如果立刻弹,会被遮罩盖住或者被后续的渲染流程吞掉,用户只看到页面闪了一下。延后 100 毫秒,等遮罩层稳定再弹,提示才能被看见。这个数字不用精确,80 到 200 之间都行,太小容易看不见,太大用户会觉得卡。

第四个细节:差值用getTime()拿毫秒再比,别拿 Date 对象直接相减。1000 * 3600 * 24 * 90是 90 天的毫秒数,写成7776000000也行,但拆开写更好读,改天数的时候只动最后一个数字。注意“不超过 90 天”和“小于 90 天”是两回事,需求说“不大于”,那判断就该是>才拦截,等于 90 天要放行。

参数示例值类型注意点
开始日期 22024-01-01字符串名称含空格,从参数定义复制
结束日期 22024-03-31字符串空值时先判空,再解析
90 天常量1000×3600×24×90数字改窗口天数只动最后一位

补一句判空,实际项目里参数允许为空的情况很常见。getParameterValue取到空串或null时,new Date得到的是 Invalid Date,任何比较都返回 false,校验形同虚设。稳妥写法是在解析前加一层:

var rawEnd = spreadsheetReport.getParameterValue("结束日期 2"); var rawStart = spreadsheetReport.getParameterValue("开始日期 2"); if (!rawEnd || !rawStart) { return; // 参数没填完整,不拦刷新,交给后端或必填校验处理 }

4.3 参数排版:一行 6 个上限与第二行保底

参数排版的规则文档写得很直白:筛选项居左,每行平均分配,一行不超过 6 个,如果是 2 行,第二行至少保留 2 个。这条规则背后是视觉平衡,不是随便定的。一行 7 个以上的时候,单个控件的宽度会被压到只能显示三四个字,日期控件的日历图标直接被裁掉;两行排版时如果第二行只剩 1 个,视觉上会像“掉队”,看起来像排版出错。

参数个数建议每行行数说明
1-6全部一行1居左排列,控件之间留均等间距
7-84 + 42第二行 4 个,视觉平衡
9-105 + 52每行 5 个,控件宽度够用
11-126 + 5 或 6 + 62第二行不少于 5 个
13 以上拆成两行以上3+每行不超 6 个,末行不少于 2 个

实际操作里还有一个隐含约束:日期区间类的两个参数(开始日期、结束日期)必须挨着放,中间不要插别的控件。用户看参数是扫视的,两个日期被一个下拉框隔开,每次都要愣一下。排版的时候把成对的参数先拎出来当一组,再往行里填。

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

这一章全是翻车现场。每条按“现象→原因→解决”写,都是我在不同报表上真实遇到过的,症状描述尽量按你能看到的表象来写,方便对号入座。

5.1 宏不生效与刷新异常

现象一:宏写完了,保存也提示成功,但点刷新完全没反应,既不拦也不提示。原因通常是宏的作用范围挂错了——Smartbi 里客户端宏和服务端宏是两套执行环境,spreadsheetReport这个对象只在客户端宏里可用,写到服务端宏里就是一堆未定义变量,还不报错。解决:在宏的第一行加一句alert("宏已加载")打点,能弹出来说明宏本身被执行了,问题在后面的逻辑;弹不出来就是挂载位置或者触发事件选错了,回去检查宏的类型和绑定对象。

现象二:第一次点刷新正常,第二次点弹两个提示框,第三次弹三个。原因是重写doRefresh的时候没加守卫,每执行一次宏就多包一层。解决:用_jhy_doRefresh这种自定义属性暂存原函数,外面套一层if (!spreadsheetReport._jhy_doRefresh),保证只包装一次。改完之后要清一次浏览器缓存或者重新打开报表,否则旧的多层包装还在内存里。

现象三:日期比较永远不生效,结束日期比开始日期早也能查。原因有两个,一是参数名对不上,getParameterValue返回null,new Date(null)得到 1970 年,跟谁都比他小;二是参数值里带时分秒,比如2024-01-01 00:00:00,斜杠替换之后依然解析失败。解决:取值之后先判空再解析,alert把取到的原始值打出来看一眼,格式不对就让上游把参数格式统一成yyyy-MM-dd。

5.2 样式与排版的坑

现象四:屏幕预览列宽正好,导出 Excel 或者打印出来某一列被挤成两行。原因是列宽自适应在浏览器端按像素算,导出时按字符宽度换算,字体不一致就会差一截。解决:对关键列(金额、编号、日期)不要依赖自适应,直接设固定宽度,自适应只用在文本描述类列上;导出前用打印预览过一遍,别只看屏幕。

现象五:隔行换色在预览里正常,导出之后斑马纹消失或者变色。原因是用的是主题色,导出时主题映射丢失。解决:条件格式里的填充色改成固定 RGB,别用“浅蓝,个性色 1,淡色 60%”这类主题色;同时确认条件格式的应用范围没有超出实际数据区太多,范围过大时部分导出引擎会截断。

现象六:参数排两行,第二行只剩一个控件,看着像排版崩了。原因是最初按 7 个参数排的,后来业务加了一个,变成 8 个,谁也没回头调布局。解决:参数个数是动态的,排版也要定期回看,按“一行不超 6 个、两行时第二行不少于 2 个”的规则重排;参数增删之后把整块参数区重新拖一遍,别只往末尾追加。

提示:排查宏相关问题,最快的手段是在关键节点alert打点,把变量值直接弹出来看。日志看不到的时候,弹窗就是唯一的黑匣子。

6. 发布前的验收与一个提速习惯

预览发布这一步,很多人点一下“预览”看到有数就发布了,结果第二天业务方打开说“怎么跟昨天不一样”。发布前我固定走一遍清单,五件事,每件都对应一类线上问题。

验收项检查方法不通过的表现
参数边界手动把结束日期设成早于开始日期,点刷新没弹提示,直接出数
90 天窗口把区间拉到 91 天,点刷新提示没弹,或者 90 天整被误拦
空参数清空开始日期直接刷新页面卡住或报脚本错误
列宽换一台分辨率不同的机器打开关键列被压成两行
打印打印预览看第 2 页表头没重复,或者最后一列被截断

这张表不用每次全过,但参数和打印这两项我一次都没省过。参数校验的 bug 上线后才发现的代价最大,业务方按错误区间跑出来的数据会直接进汇报材料。

提速的习惯只有一个:把调试用的宏和正式宏分开。日常改报表的时候,我在宏的第一行留一个开关变量,var DEBUG = true;,后面所有alert打点都包在if (DEBUG)里。调试完把DEBUG改成false,打点全部静默,不用一行行删注释,也不用担心漏删一个弹窗把用户吓一跳。这个开关还有一个副作用好处:下次这张报表再出问题,把DEBUG改回true就能直接进入排查状态,不用重新想当初在哪打的点。

另一个值得固化的动作是列宽预设。凡是出过“列宽在别人机器上炸开”这种问题的报表,我都会在自适应之后补一段固定宽度覆盖,把金额列、日期列、编号列钉死,只放描述类列去自适应。自适应是给不确定内容用的,确定内容的东西不该交给它猜。

说个我自己的教训:早几年做一张月度汇总表,参数校验写完只测了“结束早于开始”这一种情况,90 天窗口那一段代码因为平时用不到就跳过了,结果上线第一周业务方把区间拉到半年,脚本没拦住,后端跑了个全表扫描,报表直接超时。从那以后我每次写完参数校验,都强制把两个分支各触发一遍,哪怕需求里写着“可选”。希望帮到你。

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

返回列表