简介:这份资源是面向SAP ABAP开发者的BSP报表开发实例,适合已掌握ABAP基础、希望入门SAP Web应用与动态报表的初中级开发者。内容围绕SE80中创建BSP应用、页面与CSS引用,重点演示ABAP脚本与HTML混合编写,包括表单提交、事件处理及内部表循环绑定表格数据,帮助读者理解BSP如何将数据库数据与前端页面结合,解决传统报表交互性不足的问题。资源包为1个doc文档,大小约433KB,以图文步骤与代码片段呈现完整开发流程,便于对照练习。目前已有378人学习下载,可作为BSP报表开发的入门参考,快速掌握页面布局、样式引用与ABAP逻辑交互的核心思路。
1. 从一张订单报表说起:BSP 到底能解决什么
手里有一套 SAP 系统,业务方要一张按客户编号和订单日期查询的销售订单报表,字段横跨 VBAK、VBAP、KNA1、TVGRT、PA0001 好几张表,还要在网页上直接输入条件、点按钮出结果。这种需求放在今天,很多人第一反应是丢给 Fiori 或者 OData 服务,但在一些老系统里,BSP 仍然是成本最低、落地最快的方案。BSP 全称 Business Server Pages,是 SAP 早期提供的 Web 开发技术,页面里用<% %>嵌 ABAP 代码,语法结构跟 JSP 几乎一样,区别只是尖括号里跑的是 ABAP 而不是 Java。这份 BSP 报表开发实例(HTML)就是一份典型的“从零搭一张可交互报表”的完整记录,覆盖了 SE80 建应用、建页面、引 CSS、写 Layout、写 Event Handler、定义 Page Attribute 和 Type Definition 的全流程。适合谁看?一是维护老 SAP 系统、需要快速交付 Web 报表的 ABAP 开发者;二是想理解 BSP 页面生命周期和事件模型、为后续迁移或重构做技术储备的工程师。下面我按实际动手顺序,把这份实例拆开讲透。
2. SE80 建应用与页面:从空壳到可运行骨架
2.1 创建 BSP Application 的入口与命名约束
事务代码 SE80 进去之后,左侧对象导航里选 BSP Application,输入程序名回车,系统会提示不存在并给出创建选项。这里有个容易忽略的点:BSP 应用名在同一个系统里必须唯一,而且一旦激活后改名成本很高,因为后续页面引用、导航跳转都会绑死这个名字。常见做法是先用Z或Y开头,后面跟模块缩写,比如ZREPORT_ORDER这种风格,既符合命名规范,也方便在 SE80 里按前缀过滤。
创建时系统会让你选描述和包(Package),如果是临时验证,可以先放$TMP,但正式交付一定要挂到传输请求对应的包里,否则后面跨系统传输会漏掉。这一步没有代码,但选错包是后期“报表在开发机好好的、到测试机打不开”的经典原因之一。
2.2 页面类型选择与 Layout 页签的职责
在 BSP 应用下新建页面,需要填网页名字、描述、页面类型。页面类型一般选 HTML,因为这份实例就是纯 HTML 报表,不需要走其他渲染模式。建完之后你会看到几个页签:Layout、Event Handler、Page Attributes、Type Definition 等。Layout 页签负责页面静态结构和动态输出的混合,也就是 HTML 骨架加<%= %>表达式;Event Handler 负责处理用户交互事件,比如按钮点击;Page Attributes 定义页面级变量;Type Definition 定义内表类型和结构。
这个分工要理解清楚,否则很容易把取数逻辑写到 Layout 里,导致页面渲染和业务逻辑搅在一起,后面改一个字段要翻半天。我一般的原则是:Layout 只做展示和循环输出,所有 SELECT、校验、导航都放 Event Handler。
2.3 引用 CSS 文件的正确姿势
实例里用<link rel=stylesheet href="./style.css" type="text/css">引入样式。这里的关键是路径。BSP 应用里的静态文件(CSS、JS、图片)通常放在 MIME 对象下,./style.css是相对当前页面路径去找。如果你把 CSS 放在 BSP 应用的 MIME 目录里,这个写法能生效;如果放在别的路径,就要改成对应的相对路径或绝对路径。
另一个坑是:BSP 页面在 Layout 里写<link>标签时,位置要放在<HEAD>里,不要放到<form>内部。实例代码里<link>出现在<html>之后、<HEAD>之前,浏览器大多能容错解析,但严格来说不规范,迁移到某些前端框架或做 HTML 校验时会报问题。建议统一写成:
<head> <link rel="stylesheet" href="./style.css" type="text/css"> <title>订单报表</title> </head>这样结构清晰,也方便后续加多个样式文件。
3. Layout 与 Event Handler 联动:把输入框、按钮和取数逻辑串起来
3.1 Layout 里的表单结构与 ABAP 表达式
Layout 页签的核心是把 HTML 表单和 ABAP 变量绑定。实例里有两个输入框,分别对应客户编号st_custno和订单日期st_ord,值通过<%=gt_custno%>和<%=gt_dat%>回填。提交按钮的 name 是onInputProcessing(sub),这个命名很关键——它告诉 BSP 运行时,点击按钮后去 Event Handler 里找when 'sub'分支。
下面这段是简化后的 Layout 结构,保留了核心绑定逻辑:
<%@page language="abap" %> <h3><%= errormsg %></h3> <html> <link rel="stylesheet" href="./style.css" type="text/css"> <head><title>订单报表</title></head> <form name="PageForm" id="PageForm" method="post"> <table align="center" border="0" width="80%"> <tr> <td>客户编号: <input class="InputBox" type="text" name="st_custno" size="10" value="<%=gt_custno%>" maxlength="10"> </td> </tr> <tr> <td>订单日期: <input class="InputBox" type="text" name="st_ord" size="10" value="<%=gt_dat%>" maxlength="10"> </td> </tr> <tr> <td colspan="2" align="right"> <input name="onInputProcessing(sub)" class="form1" type="submit" value="查询"> </td> </tr> </table> <!-- 下方是数据表格,循环输出内表 --> <table border="0" width="100%" class="table1" cellspacing="1"> <tr class="th"> <td>凭证日期</td><td>销售凭证号</td><td>采购订单编号</td> <td>员工姓名</td><td>销售组描述</td><td>交货日期</td> <td>累计订购量</td><td>订单金额</td><td>已出货金额</td><td>产品组描述</td> </tr> <% DATA: wa_YSHDDCX LIKE LINE OF ZSDFB_YSHDDCX. LOOP AT ZSDFB_YSHDDCX INTO wa_YSHDDCX. %> <tr> <td nowrap align="center"><%=wa_YSHDDCX-AUDAT%></td> <td nowrap align="center"><%=wa_YSHDDCX-VBELN%></td> <td nowrap align="center"><%=wa_YSHDDCX-BSTNK%></td> <td nowrap align="center"><%=wa_YSHDDCX-SMNAM%></td> <td nowrap align="center"><%=wa_YSHDDCX-BEZEI20%></td> <td nowrap align="center"><%=wa_YSHDDCX-VDATU%></td> <td nowrap align="right"><%=wa_YSHDDCX-ZJSL%></td> <td nowrap align="right"><%=wa_YSHDDCX-KZWI2%></td> <td nowrap align="right"><%=wa_YSHDDCX-YCHJE%></td> <td nowrap align="center"><%=wa_YSHDDCX-VTEXT%></td> </tr> <% ENDLOOP. %> </table> </form> </html>逻辑说明:<%@page language="abap" %>声明页面脚本语言;<%= %>是输出表达式,把 ABAP 变量值写到 HTML 流里;<% %>是代码块,里面可以写 DATA、LOOP、SELECT 等语句。参数方面,name="st_custno"和name="st_ord"是表单字段名,Event Handler 里通过navigation->get_parameter按这个名字取值。maxlength="10"限制输入长度,和后面 ABAP 变量gt_custno TYPE VBAK-KUNNR的长度对齐,避免截断问题。
3.2 Event Handler 里的取值、校验与导航
Event Handler 是 BSP 页面的事件入口。实例里case event_id. when 'sub'.对应按钮点击。核心步骤是:先navigation->set_parameter把表单字段注册进来,再navigation->get_parameter取值赋给页面属性,然后做非空校验,最后执行 SELECT 取数并填充内表。
* Event handler for checking and processing user input case event_id. when 'sub'. * 取得输入值 navigation->set_parameter( 'st_custno' ). gt_custno = navigation->get_parameter( name = 'st_custno' ). navigation->set_parameter( 'st_ord' ). gt_dat = navigation->get_parameter( name = 'st_ord' ). * 输入判断 clear errormsg. if gt_custno is not initial. unpack gt_custno to gt_custno. else. errormsg = '请输入客户编号'. exit. endif. * 取值区 DATA: wa_YSHDDCX LIKE LINE OF ZSDFB_YSHDDCX. select KUNNR VBELN BSTNK AUDAT VDATU SPART VKGRP VTWEG into corresponding fields of table ZSDFB_YSHDDCX from VBAK where KUNNR eq gt_custno and AUDAT eq gt_dat. loop at ZSDFB_YSHDDCX into wa_YSHDDCX. * 取得客户描述 select single NAME1 from KNA1 into wa_YSHDDCX-NAME1 where KUNNR = wa_YSHDDCX-KUNNR. * 取销售组代码对应的描述 select single BEZEI from TVGRT into wa_YSHDDCX-BEZEI20 where VKGRP = wa_YSHDDCX-VKGRP. * 取销售雇员代码 select single T~PERNR from VBPA as T into wa_YSHDDCX-BUPARTNR where T~PARVW = 'VE' and T~VBELN = wa_YSHDDCX-VBELN. * 取销售雇员名称 select single PA0001~SNAME from PA0001 into wa_YSHDDCX-SMNAM where PA0001~PERNR = wa_YSHDDCX-BUPARTNR. * 取订单金额 select sum( KZWI2 ) from VBAP into wa_YSHDDCX-KZWI2 where VBELN = wa_YSHDDCX-VBELN. * 取得产品组描述 select single VTEXT from TSPAT into corresponding fields of wa_YSHDDCX where SPART = wa_YSHDDCX-SPART. * 取得分销渠道描述 select single VTEXT from TVTWT into wa_YSHDDCX-VTEXT where VTWEG = wa_YSHDDCX-VTWEG and SPRAS = '1'. modify ZSDFB_YSHDDCX from wa_YSHDDCX. endloop. when others. endcase.逻辑说明:navigation->set_parameter把表单字段名注册到导航上下文,get_parameter按名字取值。unpack用于去掉前导空格,因为网页输入框传过来的值可能带空格。select ... into corresponding fields of table一次性把 VBAK 的字段填到内表,字段名要跟内表结构里的字段名一致。循环里对每一行做多次select single补全描述字段,最后modify更新内表。参数方面,SPRAS = '1'是语言键,代表中文,如果系统语言是 EN 就要改成'E',否则描述取不到。
3.3 Page Attribute 与 Type Definition 的配合
Page Attributes 里定义页面级变量:errormsg TYPE STRING、gt_custno TYPE VBAK-KUNNR、gt_dat TYPE D、ZSDFB_YSHDDCX TYPE TT_YSHDDCX。Type Definition 里定义TYPES: tt_YSHDDCX TYPE STANDARD TABLE OF ZSDFB_YSHDDCX INITIAL SIZE 0.。这里ZSDFB_YSHDDCX是自定义结构,字段包括 KUNNR、NAME1、VBELN、BSTNK、AUDAT、KZWI2、VDATU、SPART、ZJSL、YCHJE、VTEXT、VKGRP、BEZEI20、BUPARTNR、SMNAM、VTWEG 等。
关键点是:Page Attribute 里声明的内表类型必须和 Type Definition 里定义的类型名一致,否则激活会报“类型未定义”。另外INITIAL SIZE 0表示初始不分配内存,对报表这种数据量不大的场景够用;如果数据量预期很大,可以改成INITIAL SIZE 100减少扩容开销。
4. 避坑与排查:BSP 报表开发里最容易翻车的五个点
4.1 页面激活报错“类型 TT_YSHDDCX 未定义”
现象:Layout 或 Event Handler 里用了TT_YSHDDCX,激活时提示类型不存在。原因:Type Definition 页签里没有定义这个类型,或者定义在了错误的页面里。解决:确认 Type Definition 里写了TYPES: tt_YSHDDCX TYPE STANDARD TABLE OF ZSDFB_YSHDDCX INITIAL SIZE 0.,并且ZSDFB_YSHDDCX这个结构已经在数据字典里创建并激活。如果结构是新建的,记得先激活结构再激活 BSP 页面。
4.2 按钮点击后页面刷新但数据不显示
现象:输入客户编号点查询,页面重新加载了,但表格是空的,也没有错误提示。原因:Event Handler 里when 'sub'没有匹配上,或者navigation->get_parameter取到的值是空的。解决:先检查按钮 name 是否严格写成onInputProcessing(sub),括号和大小写都要一致;再在 Event Handler 里加errormsg = gt_custno.临时输出,确认取值是否成功。如果取值为空,检查set_parameter是否在get_parameter之前调用。
4.3 SELECT 取数结果和内表字段对不上
现象:内表里有数据,但某些字段显示为空或乱码。原因:select ... into corresponding fields of table要求数据库字段名和内表字段名完全一致,如果内表里字段名是BEZEI20而数据库里是BEZEI,就对不上。解决:要么在 SELECT 里用别名,要么在内表结构里把字段名改成和数据库一致。实例里BEZEI20是自定义字段名,所以用了单独的select single ... into wa_YSHDDCX-BEZEI20来赋值,没有走corresponding fields。
4.4 中文描述取不到,显示为空白
现象:客户名称、销售组描述这些中文字段在报表里是空的。原因:SPRAS参数设成了'1',但系统语言键可能不是'1',或者文本表里没有对应语言的条目。解决:先用 SE16 查 TVTWT 表,确认SPRAS字段的实际值,中文通常是'1',英文是'E'。如果查不到,说明该描述在文本表里没有维护,需要先补数据。
4.5 内表循环里 MODIFY 不生效
现象:循环里对wa_YSHDDCX赋值后modify,但 Layout 里输出的还是旧值。原因:modify ZSDFB_YSHDDCX from wa_YSHDDCX.没有带索引或条件,默认按表头内表的主键更新,如果内表没有定义主键,可能更新不到正确行。解决:在loop at ... into wa_YSHDDCX之后,用modify ZSDFB_YSHDDCX from wa_YSHDDCX.是可行的,因为into会带出当前行索引;但如果用的是into corresponding fields或assigning,行为会不同。稳妥做法是用loop at ... into wa_YSHDDCX. ... modify ZSDFB_YSHDDCX from wa_YSHDDCX. endloop.这种标准写法,不要混用field-symbols。
5. 进阶技巧:用导航参数和页面跳转做多页报表
5.1 用 navigation->next_page 做页面间传参
实例里注释掉了navigation->next_page( '要关联的值' )和navigation->goto_page('custord_list.htm')。这两个是 BSP 页面跳转的两种方式:next_page按属性里定义的目标页面跳转,goto_page直接指定页面文件名。如果要做“查询结果页”和“明细页”分离,可以在查询页的 Event Handler 里把客户编号和日期通过set_parameter传到导航上下文,然后在明细页的 Layout 里用navigation->get_parameter取出来。
* 在查询页 Event Handler 里 navigation->set_parameter( name = 'st_custno' value = gt_custno ). navigation->set_parameter( name = 'st_ord' value = gt_dat ). navigation->next_page( 'ORDER_DETAIL' ). * 在明细页 Event Handler 里 DATA: lv_custno TYPE VBAK-KUNNR. navigation->set_parameter( 'st_custno' ). lv_custno = navigation->get_parameter( name = 'st_custno' ).参数说明:set_parameter的name是参数名,value是参数值;get_parameter按name取值。注意set_parameter在跳转前调用,get_parameter在目标页面的 Event Handler 里调用。如果目标页面是静态 HTML 没有 Event Handler,可以在 Layout 里用navigation->get_parameter直接输出。
5.2 用 CSS 类控制表格样式和输入框对齐
实例里用了class="InputBox"、class="form1"、class="table1"、class="th"这些样式类。CSS 文件里可以这样定义:
.InputBox { border: 1px solid #999; height: 20px; line-height: 20px; padding: 0 4px; } .form1 { background-color: #e0e0e0; border: 1px solid #666; height: 24px; padding: 0 12px; cursor: pointer; } .table1 { border-collapse: collapse; font-size: 12px; } .table1 .th { background-color: #d9e5f2; font-weight: bold; } .table1 td { border: 1px solid #ccc; padding: 2px 6px; }这样表格有表头底色、单元格边框,输入框和按钮也有统一风格。注意border-collapse: collapse要加在 table 上,否则cellspacing="1"和 CSS 边框会叠加出双线效果。
5.3 验证方法:从 SE80 测试到浏览器调试
保存激活后,在 SE80 里右键 BSP 应用选“测试”,系统会生成一个 URL 在浏览器打开。如果页面报 500 错误,先看 SAP 系统里的 ST22 短转储,里面会给出 ABAP 运行时错误的行号和原因。如果页面能打开但样式不对,按 F12 打开浏览器开发者工具,看 Network 里style.css是否 404。如果 404,说明 CSS 文件没有放到 MIME 目录或者路径写错了。
我一般会按这个顺序排查:先确认 BSP 应用和页面都激活了,再确认 MIME 对象存在且路径正确,然后看 Event Handler 里的when分支是否命中,最后查 SELECT 语句在 SE16 里能否查到数据。这套流程走下来,大部分问题都能定位到具体行。
从那以后我每次建 BSP 页面,都强制先把 Page Attribute 和 Type Definition 定义完再写 Layout 和 Event Handler,避免激活时来回跳页签找错误。希望帮到你。
本文还有配套的精品资源,点击获取