简介这份资源是面向SAP ABAP开发者的BSP报表开发实例适合已掌握ABAP基础、希望入门SAP Web应用与动态报表的初中级开发者。内容围绕SE80中创建BSP应用与页面、引用CSS样式、在HTML中嵌入ABAP脚本、通过表单与事件处理实现用户交互以及用loop循环将内部表数据绑定到HTML表格等核心环节展开帮助读者理解BSP如何把数据库数据与前端页面结合构建可交互的Web报表。资源包为1个doc文档约433KB以图文步骤与代码片段为主便于对照练习。目前已有378人学习下载。通过该实例读者可掌握BSP应用创建流程、页面布局与样式引用、ABAP表达式动态输出、表单提交与onInputProcessing事件处理等关键技能并积累将ABAP逻辑与HTML界面整合的实战经验为后续开发更复杂的SAP Web报表打下基础。1. BSP 报表开发实例从 SE80 到可交互 HTML 报表的完整落地手里有一份 2007 年的 BSP 报表开发实例作者把 SE80 建应用、页面创建、CSS 引用、Layout 与 Event Handler 代码、Page Attribute 变量定义、Type Definition 类型定义、测试效果和注意事项全写进去了。这份东西放到今天看技术栈确实老但它的价值不在“新”而在“完整”——它是一条从零到可运行 BSP 报表的最短路径没有省略任何一步。如果你正在维护一套跑在 SAP 里的 BSP 应用或者需要快速给业务部门出一个能输入客户编号和订单日期、点提交就出表格的查询页面这份实例能直接抄作业。它适合两类人一是刚接触 ABAP Web 开发、需要理解 BSP 页面生命周期和事件处理机制的新手二是手头有老 BSP 系统、需要快速改一个报表或排查问题的熟手。下面我按“资源是什么、怎么用、坑在哪”的顺序把这份实例拆开讲透。2. BSP 应用创建与页面结构SE80 里的五个关键动作2.1 为什么选 BSP 而不是 Web Dynpro这份实例用的是 BSP不是 Web Dynpro。BSP 的核心思路是“HTML 里嵌 ABAP”页面结构接近传统 JSPLayout 负责展示、Event Handler 负责逻辑、Page Attribute 负责变量、Type Definition 负责类型。对于报表类需求——输入条件、查数据库、循环输出表格——BSP 的代码量比 Web Dynpro 少得多页面结构也更直观。Web Dynpro 适合复杂交互和组件复用但如果你只是要一个“输入框加提交按钮加结果表格”的查询页BSP 的 Layout 里直接写 HTML 表格、用% %绑定变量开发速度更快。这份实例选 BSP 是合理的因为它的目标就是一个简单报表不需要复杂的页面导航和组件模型。2.2 SE80 创建 BSP 应用的完整步骤打开事务代码 SE80在左侧对象导航里选择 BSP Application输入你规划好的 BSP 程序名回车后系统会提示是否创建确认即可。创建完成后在 BSP 应用节点下右键新建 Page输入网页名称和描述页面类型选 HTML确认保存。这一步的关键是页面名称要和后续 Event Handler 里goto_page或next_page引用的名称一致否则运行时会报页面找不到。常见做法是页面名用xxx.htm格式比如custord_list.htm这样在代码里引用时不容易写错。创建页面后你会看到 Layout、Event Handler、Page Attribute、Type Definition 等页签。Layout 是 HTML 和 ABAP 脚本混合的地方Event Handler 是纯 ABAP 逻辑Page Attribute 定义页面级变量Type Definition 定义类型。这四个页签的代码是相互配合的缺一个页面就跑不起来。2.3 Layout 页签的代码结构与参数说明Layout 页签的代码是这份实例的核心。开头%page languageabap %声明页面使用 ABAP 脚本语言这是 BSP 的固定写法。接着h3 % errormsg % /h3把 Page Attribute 里定义的errormsg变量输出到页面用于显示错误提示。link relstylesheet href./style.css typetext/css引用 CSS 文件路径是相对路径CSS 文件需要放在 BSP 应用的 MIME 资源里或者通过其他方式关联。表单部分用form namePageForm idPageForm methodpost定义里面有两个输入框st_custno绑定gt_custnost_ord绑定gt_dat。注意value%gt_custno%这种写法它把 ABAP 变量的值输出到 HTML 的 value 属性里页面刷新后输入框会保留上次输入的值。提交按钮的name属性是onInputProcessing(sub)这个写法是 BSP 的事件绑定语法意思是点击按钮时触发 Event Handler 里case event_id. when sub.分支。表格部分用%DATA: wa_YSHDDCX LIKE LINE OF ZSDFB_YSHDDCX.loop at ZSDFB_YSHDDCX into wa_YSHDDCX.%开始循环然后在tr里用%wa_YSHDDCX-AUDAT%这样的表达式逐列输出数据。循环结束用%endloop.%。这里要注意ZSDFB_YSHDDCX是一个内表在 Page Attribute 里定义为TYPE TT_YSHDDCX而TT_YSHDDCX在 Type Definition 里定义为STANDARD TABLE OF ZSDFB_YSHDDCX。这个链条不能断Type Definition 定义类型Page Attribute 定义变量Layout 里循环输出。2.4 Event Handler 与 Page Attribute 的配合逻辑Event Handler 里的代码负责处理用户输入和查询数据。case event_id. when sub.匹配提交按钮的事件。首先用navigation-set_parameter(st_custno).和navigation-get_parameter( name st_custno ).取得输入值存到gt_custno。然后做输入校验如果gt_custno为空设置errormsg 请输入客户编号并exit页面会重新显示并带上错误信息。校验通过后用select ... 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逐条取客户名称、销售组描述、销售雇员名称、订单金额、产品组描述、分销渠道描述然后modify ZSDFB_YSHDDCX from wa_YSHDDCX.更新内表。这个“循环内 select single 再 modify”的模式是 BSP 报表里常见的写法但也是性能隐患后面避坑章节会细说。Page Attribute 里定义的变量errormsg TYPE STRING、gt_custno TYPE VBAK-KUNNR、gt_dat TYPE DZSDFB_YSHDDCX、ZSDFB_YSHDDCX TYPE TT_YSHDDCX。注意gt_dat的类型写的是DZSDFB_YSHDDCX这可能是笔误或者自定义类型实际应该是日期类型。ZSDFB_YSHDDCX是内表变量在 Layout 里直接循环输出。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、VTXTK 等。这些字段覆盖了报表需要展示的所有列。3. 从输入到输出BSP 报表的完整数据流与代码实现3.1 输入校验与参数传递的代码细节Event Handler 里的输入校验逻辑是报表的第一道关卡。代码先clear errormsg.清空错误信息然后判断if gt_custno is not initial.如果不为空执行unpack gt_custno to gt_custno.。unpack的作用是去掉输入值的前导空格因为 HTML 输入框提交的值可能带空格。如果为空设置errormsg 请输入客户编号并exit页面重新渲染时h3 % errormsg % /h3就会显示错误提示。订单日期的校验在实例里被注释掉了但逻辑是一样的判断gt_dat是否为空为空则设置错误信息并退出。实际开发中建议把日期校验打开因为select语句里AUDAT eq gt_dat如果gt_dat为空查询条件会变成AUDAT eq 00000000查不到任何数据用户会以为系统坏了。参数传递用navigation-set_parameter和navigation-get_parameter。set_parameter把输入框的值绑定到 navigation 对象get_parameter从 navigation 对象取值赋给 ABAP 变量。这个机制是 BSP 页面间传参的标准做法next_page和goto_page也依赖 navigation 对象传递参数。3.2 数据库查询与内表循环的写法查询主数据用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.。into corresponding fields of table表示按字段名匹配把 VBAK 的字段值填入内表ZSDFB_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.汇总。产品组描述和分销渠道描述分别从 TSPAT 和 TVTWT 取。每取完一条记录的所有补充字段执行modify ZSDFB_YSHDDCX from wa_YSHDDCX.把更新后的行写回内表。循环结束后内表ZSDFB_YSHDDCX就包含了完整的报表数据Layout 里的loop at ZSDFB_YSHDDCX into wa_YSHDDCX.会逐行输出。3.3 CSS 引用与页面样式控制CSS 引用在 Layout 的link relstylesheet href./style.css typetext/css这一行。./style.css表示当前目录下的 style.css 文件。在 BSP 应用里CSS 文件通常放在 MIME 资源节点下或者通过 SE80 的 BSP 应用资源管理上传。引用后HTML 里的classInputBox、classform1、classTableDiv、classtable1、classth等样式类就会生效。常见做法是把表格样式、输入框样式、按钮样式都写在 CSS 里Layout 里只写结构。这样改样式不用动 ABAP 代码维护更方便。注意 CSS 文件路径要写对如果 BSP 应用部署在子路径下相对路径可能失效可以用绝对路径或者 BSP 提供的资源 URL 生成方式。3.4 测试与激活的检查清单保存所有页签的代码后点击激活按钮。激活时系统会检查语法错误如果有未定义的变量或类型会报错并阻止激活。激活成功后右键 BSP 应用或页面选择测试系统会在浏览器里打开页面。测试时重点检查输入框是否能输入、提交按钮是否触发 Event Handler、错误信息是否显示、表格是否有数据、CSS 样式是否生效。如果页面打开后空白先检查 Layout 里是否有语法错误导致页面渲染中断。如果提交后没有反应检查 Event Handler 里case event_id. when sub.的sub是否和提交按钮的name属性一致。如果表格没有数据检查select语句的查询条件是否匹配到了数据可以在 Event Handler 里加break-point调试。4. 避坑与排查BSP 报表开发中容易翻车的五个点4.1 内表定义与循环处理的类型不匹配现象激活时报错“类型 TT_YSHDDCX 未定义”或“ZSDFB_YSHDDCX 不是内表”。原因Type Definition 里定义的tt_YSHDDCX和 Page Attribute 里定义的ZSDFB_YSHDDCX类型不一致或者 Type Definition 没有激活。解决确认 Type Definition 里TYPES: tt_YSHDDCX TYPE STANDARD TABLE OF ZSDFB_YSHDDCX INITIAL SIZE 0.已激活Page Attribute 里ZSDFB_YSHDDCX TYPE TT_YSHDDCX引用正确。循环时用DATA: wa_YSHDDCX LIKE LINE OF ZSDFB_YSHDDCX.定义工作区不要直接用内表名当工作区。4.2 循环内 SELECT SINGLE 导致的性能问题现象报表数据量稍大时页面加载极慢甚至超时。原因loop at ZSDFB_YSHDDCX into wa_YSHDDCX.里每行都执行多个select single如果内表有几百行数据库查询次数就是几百乘以字段数。解决把能合并的查询合并比如用select ... into table ... for all entries in ZSDFB_YSHDDCX一次性取客户名称、销售组描述等然后在内存里用read table匹配。如果数据量确实大考虑分页或者限制查询条件。4.3 输入框值绑定与 UNPACK 的遗漏现象用户输入客户编号后提交查询不到数据但数据库里明明有。原因HTML 输入框提交的值可能带前导空格gt_custno直接用于select的where条件时匹配不上。解决在 Event Handler 里对输入值执行unpack gt_custno to gt_custno.去掉前导空格。日期字段也要做类似处理或者用condense去掉多余空格。4.4 CSS 文件路径错误导致样式失效现象页面能打开数据也能显示但表格没有边框、输入框没有样式。原因link relstylesheet href./style.css typetext/css的路径不对或者 CSS 文件没有上传到 BSP 应用的 MIME 资源里。解决在 SE80 里检查 BSP 应用的 MIME 资源节点确认 style.css 存在。如果路径不对改成正确的相对路径或绝对路径。测试时可以在浏览器里按 F12 看 Network 面板确认 style.css 是否 404。4.5 事件绑定名称不一致导致提交无反应现象点击提交按钮后页面刷新但没有任何查询结果错误信息也不显示。原因提交按钮的name属性是onInputProcessing(sub)Event Handler 里case event_id. when sub.的sub必须和括号里的参数一致。如果写成onInputProcessing(Submit)而 Event Handler 里是when sub.事件匹配不上。解决统一命名提交按钮用onInputProcessing(sub)Event Handler 里用when sub.。改完后重新激活页面再测试。5. 进阶技巧用 FOR ALL ENTRIES 优化 BSP 报表查询5.1 从逐行 SELECT 到批量查询的改造思路原始实例的查询逻辑是“先查主表到内表再循环内表逐行补充字段”。这种写法在数据量小的时候没问题但订单行数一多数据库交互次数线性增长。优化的核心思路是把“循环内 select single”改成“批量 select into table for all entries”。具体做法是主查询不变把内表ZSDFB_YSHDDCX作为驱动表用for all entries in ZSDFB_YSHDDCX一次性取客户名称、销售组描述、雇员信息等存到对应的内表里然后在循环里用read table匹配。5.2 FOR ALL ENTRIES 的代码实现与参数说明 批量取客户名称 DATA: lt_kna1 TYPE STANDARD TABLE OF kna1. SELECT kunnr name1 INTO TABLE lt_kna1 FROM kna1 FOR ALL ENTRIES IN ZSDFB_YSHDDCX WHERE kunnr ZSDFB_YSHDDCX-kunnr. 批量取销售组描述 DATA: lt_tvgrt TYPE STANDARD TABLE OF tvgrt. SELECT vkgrp bezei INTO TABLE lt_tvgrt FROM tvgrt FOR ALL ENTRIES IN ZSDFB_YSHDDCX WHERE vkgrp ZSDFB_YSHDDCX-vkgrp. 循环内用 READ TABLE 匹配 LOOP AT ZSDFB_YSHDDCX INTO wa_YSHDDCX. READ TABLE lt_kna1 INTO DATA(ls_kna1) WITH KEY kunnr wa_YSHDDCX-kunnr. IF sy-subrc 0. wa_YSHDDCX-name1 ls_kna1-name1. ENDIF. READ TABLE lt_tvgrt INTO DATA(ls_tvgrt) WITH KEY vkgrp wa_YSHDDCX-vkgrp. IF sy-subrc 0. wa_YSHDDCX-bezei20 ls_tvgrt-bezei. ENDIF. MODIFY ZSDFB_YSHDDCX FROM wa_YSHDDCX. ENDLOOP.这段代码的关键是FOR ALL ENTRIES IN ZSDFB_YSHDDCX它把内表里的 KUNNR 和 VKGRP 作为查询条件一次性从 KNA1 和 TVGRT 取回所有匹配记录。注意FOR ALL ENTRIES要求驱动内表不能为空否则会全表扫描。所以在执行前要检查IF ZSDFB_YSHDDCX IS NOT INITIAL.。另外READ TABLE的WITH KEY要用内表的主键或排序字段如果内表没排序可以先SORT ZSDFB_YSHDDCX BY kunnr vkgrp.再循环。5.3 优化前后的性能对比与验证方法优化前假设内表有 100 行每行执行 6 个select single数据库交互 600 次。优化后6 个批量查询各执行一次数据库交互 6 次加上循环内的read table内存操作整体响应时间从秒级降到毫秒级。验证方法在 Event Handler 里加GET RUN TIME FIELD记录开始和结束时间输出到errormsg或日志里对比优化前后的耗时。也可以在 ST05 里开 SQL Trace看数据库交互次数是否明显减少。我一般会在开发阶段就用FOR ALL ENTRIES替代循环内select single因为后期数据量增长是不可控的。从那以后我每次写 BSP 报表只要涉及循环内取数都强制走一遍“能不能批量查”的检查避免上线后被业务追着优化。希望帮到你。本文还有配套的精品资源点击获取