ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SAP简单报表开发全流程:从需求分析到ALV输出实践

SAP简单报表开发全流程:从需求分析到ALV输出实践 SAP简单报表开发这个需求做过SAP项目的朋友肯定不陌生。不管是企业内部IT还是乙方顾问三天两头就会接到一句帮我做个报表很简单的。采购入库明细、总账科目余额、销售订单跟踪、库存流水听起来都简单但真做起来坑一个接一个。这些年我经手过的ABAP报表没有上百个也有几十个从最简单的单表查询到跨模块的复杂取数都有涉猎慢慢沉淀了一套相对固定的开发流程。这篇文章就把我从需求确认、底层表定位、选择屏幕设计、数据读取到ALV输出的完整链路梳理一遍重点讲那些正规文档里没人写、但实际项目天天遇到的细节可以直接当作你的简单报表开发模板。1. 需求梳理别急着写代码先搞清楚三件事很多报表开发翻车不是代码写得烂而是需求压根没弄明白。我见过太多人拿到需求就打开SE38开始写写到一半发现取数逻辑不对再回去改来回折腾的时间比认真问清楚需求多得多。1.1 报表给谁看用户视角决定输出粒度第一个问题永远是这张表是给谁看的。别看这个问法很基础它能直接决定报表的字段和汇总粒度。比如同样是库存报表财务看的是按物料、工厂汇总的库存金额采购看的是按供应商、采购订单的收货入库明细仓管看的却是物料、库位、批次级别的实时库存量。场景不一样你选择的透明表、查询条件、输出列全都不一样。我通常会把用户的需求问细到这个程度你在什么场景下打开这张表你是拿它对数还是拿它做分析还是拿它去和别的系统核对如果用户自己也说不清字段那就让他们提供一份Excel样例照着样例做字段映射是最不容易出错的方式。这个步骤做扎实了后面写代码会非常省心。1.2 数据从哪来学会反向查表和事务代码拿到需求后最核心的问题是数据在哪张表。SAP的表成千上万新人最容易卡在这一步。我的习惯是三步走先用事务代码SE11或SE16N去查已知的事务代码例如总账凭证过账用F-02、FB50那我就能通过SE93去看这些事务代码对应的后台程序程序里面自然能找到BKPF和BSEG这两张核心表。然后再用SE11查表结构确认字段名和数据类型。最后用SE16N/SE16H去看看真实数据长什么样确认字段里的值是否符合业务逻辑。这里有个实用技巧对于比较冷门的表你还可以通过表名规律去猜。比如以B开头的大多是财务会计表BKPF凭证抬头、BSEG凭证行项目以M开头的大多是物料管理表MARA物料主数据、MARC物料工厂视图、MSEG物料凭证行项目以V开头大多是销售分销表VBAK销售订单抬头、VBAP销售订单行项目。记住这些常见前缀查表效率能提升一大截。1.3 报表的交互需求查询条件、输出格式、跳转动作最后要问清楚的是交互需求。最简单的报表是像ALV列表一样用户输入几个筛选条件点执行下面出一个清单。但也有的报表需要带输出格式控制比如合计、小计有的需要点击某一行能跳到对应的事务代码比如MM03、FB03也就是ALV的热点跳转还有的需要导出到Excel后继续加工这就需要注意ALV导出的格式和字段顺序。另外别忽略性能需求。用户说要全量导出但你的表有几百万条这时候就需要在技术设计上做取舍。这些需求如果不在开发前对齐后期改起来会很痛苦。我一般会用邮件或者需求确认单把这些问题列出来发给业务方确认一下别怕麻烦这一步能挡掉一大半后续的需求变更。2. 数据取数准备底层表结构和选择屏幕设计这块是整个报表开发最核心的部分。代码结构其实就那么回事真正的功夫都在取数逻辑上。2.1 高频业务表的结构记忆法简单报表最常碰到的就是财务和物流模块的表。财务侧BKPF是凭证抬头表里面有公司代码、凭证编号、过账日期、会计年度等抬头信息BSEG是凭证行项目表有科目、金额、利润中心、成本中心、文本等明细信息。两者通过BUKRS、BELNR、GJAHR三个字段关联这三个字段的组合就是财务凭证的身份证。物流侧MSEG是物料凭证行项目表里面有移动类型BWART、物料号、数量、金额、工厂库位等MARA是物料主数据里面有物料描述、物料类型、行业领域等基础信息MARC是物料的工厂视图里面有采购组、MRP类型、安全库存等参数。做库存报表时经常要把MSEG和MARA、MARC关联起来取物料描述。我建议不要把整张表的每个字段都背下来而是建立业务场景→事务代码→主要表→关键字段的四层索引。比如遇到库存就想到MMBE→用MSEG、MARD遇到总账就想到FBL3N→用BKPF、BSEG遇到销售订单就想到VA03→用VBAK、VBAP、VBUK、VBUP。建立这种索引之后开发速度会快很多。2.2 选择屏幕设计PARAMETERS和SELECT-OPTIONS的选用法则选择屏幕看起来简单但设计合理与否直接影响用户体验。单值输入用PARAMETERS比如公司代码、物料号范围输入用SELECT-OPTIONS比如日期区间、物料编号区间。这里有个经验能用SELECT-OPTIONS的地方尽量不要只用PARAMETERS。因为用户往往当时觉得我只查一个值但真用起来一定会想查多个值、查区间、甚至排除某些值。SAP的SELECT-OPTIONS天然支持这些操作还能用SP$、SIGN等逻辑操作省得你以后改程序。如果确实需要单值就在屏幕里加个参数但尽量用MATCHCODE帮助搜索帮助来避免用户输入非法值。选择屏幕还有个容易忽略的点默认值的设置。比如日期类参数很多报表默认查当月可以用INITIALIZATION. p_date-low sy-datum. p_date-high sy-datum.或者默认取前一个月。这个细节很能提升使用体验。另外如果在数据量大的表上查询一定要把必填条件设计好能用公司代码、工厂、会计年度这些高度筛选性的条件就尽量作为必填项否则用户一键执行全表扫描数据库迟早被搞死。2.3 取数前的检查标准功能优先自开发兜底在写SQL之前先问自己SAP有没有标准的报表已经能覆盖或者能用标准功能BAdI增强实现这个社会上很多报表开发需求其实用标准事务代码就能查或者稍微配置一下就能满足。比如MD07、MDVP这类MRP库存/需求清单标准功能已经做得很细有时候根本不需要另写报表。如果确实要做自开发也要考虑能不能用现成的BAPI或函数来取数而不是自己直接读底层表。比如要读取采购订单的净价值可以用BAPI_PO_GETDETAIL要读取物料库存可以用BAPI_MATERIAL_AVAILABILITY或BAPI_MARD_GETLIST。用BAPI的好处是逻辑经过了标准验证比如BAPI会自动处理一些历史数据的特殊逻辑、权限控制等比自己拼SQL稳得多。不过需要注意BAPI往往性能比直查表慢数据量大时要先用直查表后续逻辑处理的方式验证一下性能再决定用哪种方案。3. 核心实现从数据读取到ALV展示讲完前期准备下面进入正题。我用一个相对完整的总账科目余额表示例来串流程这是SAP报表开发里最典型的场景之一代码结构可以套用到大部分简单报表。3.1 取数与内表设计不要用工作区循环追加尽量批量读报表开发的一个常见坏习惯是一行为单位逐条查询。比如在LOOP里对每一行凭证去查文本描述、查供应商名称这种写法在小数据量下没什么感觉数据量一上来就会奇慢无比。更好的做法是先全量取数再批量补充描述字段。具体来说取数的逻辑分四步第一步从表格中按条件把核心行项目取到内表里比如BSEG凭证行项目。 第二步用FOR ALL ENTRIES或者SPLIT的方式把关联表的数据批量取进来。 第三步拼接显示需要的文本字段比如物料描述、公司代码描述。 第四步做金额、数量等数据的二次处理比如累计汇总、负数转换、币种转换等。我实际项目中用的取数核心逻辑类似这样SELECT b~bukrs b~belnr b~gjahr b~buzei b~hkont b~dmbtr b~waers a~bldat a~budat a~usnam INTO CORRESPONDING FIELDS OF TABLE gt_data FROM bseg AS b INNER JOIN bkpf AS a ON a~bukrs b~bukrs AND a~belnr b~belnr AND a~gjahr b~gjahr WHERE b~bukrs IN s_bukrs AND b~gjahr IN s_gjahr AND a~budat IN s_budat.这里重点说一个JOIN的注意事项BSEG和BKPF的关联条件必须把BUKRS、BELNR、GJAHR三个字段都写上漏掉一个就会造成数据膨胀或者取不到数据。另外如果只是取BSEG自身的字段直接用BSEG就行不需要非得JOIN BKPFJOIN一张表查询本身会带来性能开销先确定是否真的需要那个表的字段避免无谓的JOIN。取完核心行项目后描述字段可以这样批量补充IF gt_data IS NOT INITIAL. SELECT ska1~ktonr ska1~txt50 INTO TABLE gt_txt FROM ska1 FOR ALL ENTRIES IN gt_data WHERE ska1~ktonr gt_data-hkont AND ska1~ktopl 1000. 会计科目表按实际情况写 ENDIF.3.2 最简单的ALV输出REUSE_ALV_GRID_DISPLAYALV有三种主流实现方式函数REUSE_ALV_GRID_DISPLAY、类CL_GUI_ALV_GRID、以及S/4HANA新推出的ALV OO模式。对于简单报表我依然推荐函数方式因为代码量少、参数清晰、新人也能快速上手。最基础的调用CALL FUNCTION REUSE_ALV_GRID_DISPLAY EXPORTING i_structure_name GT_DATA 前提是内表定义了表类型 i_save A TABLES t_outtab gt_data.注意i_structure_name指向的是一个数据字典结构不是内表变量名。如果你不想在SE11里建结构可以直接用内表引用CALL FUNCTION REUSE_ALV_GRID_DISPLAY EXPORTING i_callback_program sy-repid it_fieldcat gt_fieldcat[] TABLES t_outtab gt_data.使用函数方式字段目录Field Catalog可以用两种方式生成一是上面说的指定结构名让ALV自动生成二是手动构建字段目录这种方式更灵活适合要自定义列标题、金额格式、颜色等的场景。做简单报表时我建议先用结构名自动生成跑通了再手工微调字段目录。3.3 字段目录的常用设置金额、颜色、合计、热点跳转用函数方式时如果要对列做定制常见的写法是PERFORM build_fieldcat CHANGING gt_fieldcat. FORM build_fieldcat CHANGING pt_fcat TYPE lvc_t_fcat. DATA ls_fcat LIKE LINE OF pt_fcat. 关键列调整 CLEAR ls_fcat. ls_fcat-fieldname BUKRS. ls_fcat-ref_table BSEG. ls_fcat-ref_field BUKRS. ls_fcat-coltext 公司代码. ls_fcat-outputlen 10. APPEND ls_fcat TO pt_fcat. 金额列显示合计 CLEAR ls_fcat. ls_fcat-fieldname DMBTR. ls_fcat-ref_table BSEG. ls_fcat-ref_field DMBTR. ls_fcat-do_sum X. ls_fcat-cfieldname WAERS. 金额列前显示币种 APPEND ls_fcat TO pt_fcat. ENDFORM.do_sum设为X后ALV底部会自动显示该列合计这是财务类报表最常用的功能。cfieldname可以指向币种字段这样金额列会跟随币种切换显示格式不过前提是同一列的数据货币类型一致否则会出现金额单位不一致的问题。跨币种报表建议在取数时就统一转换成本位币或者加一个原始币种列和本位币金额列分别展示。热点跳转的实现在函数方式下也不复杂注册回调i_callback_pf_status_set SET_PF_STATUS i_callback_user_command USER_COMMAND然后在USER_COMMAND里判断CASE sy-ucomm. WHEN IC1. 双击 READ TABLE gt_data INTO ls_data INDEX es_row_no-row_id. IF NOT ls_data IS INITIAL. SET PARAMETER ID BUK FIELD ls_data-bukrs. SET PARAMETER ID BLN FIELD ls_data-belnr. SET PARAMETER ID GJR FIELD ls_data-gjahr. CALL TRANSACTION FB03 AND SKIP FIRST SCREEN. ENDIF. ENDCASE.双击ALV行、跳转FB03查看原始凭证这是财务凭证类报表使用频率很高的功能。要注意的是SET PARAMETER ID需要知道对应事务代码的参数ID可以在SE93里查看事务的参数值设置或者去表TSTC/TPARA里查询一般常见的事务码参数ID都是固定已知的例如FB03用的是BUK、BLN、GJR。3.4 数据一致性处理和防呆设计简单报表里最容易被人忽略的是数据一致性。比如BSEG里金额字段DMBTR是以本位币存储的但BSEG还有DMBE2、DMBE3等字段区分不同币种直接使用DMBTR时一定要确认本位币是什么。再比如数量字段MSEG里的MENGE是数量但要注意是基本计量单位还是库存计量单位如果用户要的是采购订单单位的数量还需要用MBEW/MARM的转换关系来换算。另外很多财务报表需要做年初余额和本期发生额的结转。这里的处理逻辑就比简单取数要复杂一般需要读BSEG里对应科目的借贷方向同时要理解SAP的期间和会计年度的关系是累计数还是当期数逻辑不一样取数写法也不同。遇到这种情况我建议先写一个小函数专门负责期间判断和结转逻辑后面多个报表都能复用。4. 性能与查询优化简单报表也要守住的底线很多人写简单报表的时候不care性能觉得反正数据量不大。但SAP系统是企业的核心系统同一个数据库上跑着大量生产任务一个写不好的报表完全可能拖垮整个实例的MYSQL进程造成生产锁表甚至系统停机。4.1 FOR ALL ENTRIES的正确用法FOR ALL ENTRIESFAE是ABAP报表开发中常用的批量取数方式但它也是最容易写错、最容易被数据库误解的语法。正确用法IF lt_main IS NOT INITIAL. SELECT * FROM mseg FOR ALL ENTRIES IN lt_main WHERE mblnr lt_main-mblnr AND mjahr lt_main-mjahr AND zeile lt_main-zeile. ... ENDSELECT. ENDIF.几个必须注意的坑第一FOR ALL ENTRIES左边的WHERE条件必须全部显式写上关联字段不能写mblnr lt_main-mblnr AND mjahr 这种半关联否则查询结果可能完全不对。第二FOR ALL ENTRIES会自动删除内部表中的重复记录而且内部表如果为空FOR ALL ENTRIES默认会全表查询。所以调用前一定要判断IS NOT INITIAL这是铁律。第三FOR ALL ENTRIES不适合超大数据量的全表关联比如内部表有几百万行数据库会扛不住。这种情况下建议用FOR ALL ENTRIES内部分批处理或者直接用OpenSQL的JOIN来替代数据量越大越要考虑JOIN方式。4.2 避免N1查询与SELECT * 滥用N1查询是ABAP报表最常见的性能杀手。比如下面这种写法就是典型的禁忌LOOP AT gt_data INTO ls_data. SELECT SINGLE maktx FROM makt INTO ls_data-maktx WHERE matnr ls_data-matnr AND spras sy-langu. MODIFY gt_data FROM ls_data INDEX sy-tabix. ENDLOOP.这段代码在100条数据时可以接受10000条数据时就是灾难。正确做法是先把所有物料号收集到一个内表再一次性取回所有物料描述然后LOOP里用READ TABLE WITH KEY去匹配。业务上物料描述、供应商名称、公司代码名称、文本字段这些描述类数据几乎都不应该在循环里逐个查询。另外SELECT *在简单报表里要慎用。如果你只需要5个字段就明确写5个字段。表字段越多网络传输的数据量越大尤其MSEG这种大宽表几十个字段全部传输过去性能和内存都会受影响。4.3 用ST05和事务码检查性能当报表做好之后别急着交付先花两分钟用SE30或者ST05检查一下。SE30是ABAP程序运行时分析可以看程序总体执行时间和各语句的耗时占比。ST05是SQL跟踪可以抓到程序里实际执行的数据库SQL语句看有没有慢SQL、全表扫描、索引没走等。我在实际项目中遇到的报表性能问题90%可以通过ST05定位。比如某个SQL语句走了全表扫描这时候要么检查索引条件是否合理要么考虑调整查询条件要么把查询挪到内存里做二次处理。还有一个容易忽略的点OpenSQL的COLLECT和SORT BY。很多人写汇总报表时习惯用LOOP AT gt_data再累加如果数据量大这种方式的CPU开销很高而COLLECT专门用来做内表汇总效率高很多。COLLECT ls_sum INTO gt_sum.这个语句会自动按键组合字段累加数值字段非常适合做分组汇总。同样效果的还有AT END OF事件但使用时要保证内表是按汇总字段排序过的逻辑复杂度略高所以简单报表里强烈建议用COLLECT。5. 报表开发中的常见问题与排查实录做报表开发久了遇到的怪问题五花八门。有些是业务逻辑搞错有些是数据问题有些是代码写得不严谨。这里整理几个高频问题都是我在项目里真实踩过的坑。5.1 数据重复和漏数据怎么排查最常见的重复原因有两个一是JOIN条件没写全比如BSEG和BKPF关联时少写了BUKRS导致同一个凭证的行项目被莫名笛卡尔积放大二是FOR ALL ENTRIES的内部表没有去重导致取数时对同一张表查询多次产生重复行。排查时我一般这样操作先在SE16N里手工复现同样的查询条件看标准表本身返回多少条再对比报表内表里的条数找出差异的挡位。如果标准表就是多次幂增长那肯定是JOIN条件问题如果标准表条数正常但报表里重复那要看代码里的LOOP、MODIFY、APPEND逻辑是不是有副作用。漏数据的问题一方面可能是FOR ALL ENTRIES忘记判断IS NOT INITIAL导致内部表为空时走了全表查询然后代码逻辑里又过滤掉了本不该过滤的数据另一方面可能是选择屏幕的条件字段名字写错比如写成了p_date但用户输入的是p_date-to导致查询条件没生效。5.2 ALV显示问题列不显示、金额乱码、导不出ExcelALV列不显示最常见的原因是字段目录和输出内表的结构不一致。比如你用i_structure_name指定了自定义结构但输出内表里没有那个字段或者字段名拼写错误。这种情况ALV通常不报错只是列不出现。排查方法是在调用ALV前把内表字段名和字段目录逐一对一下用CL_ABAP_STRUCTDESCRDESCRIBE_BY_DATA可以快速打印内表结构。金额列乱码或者显示成科学计数法通常是因为内表字段定义成了FLOAT或STRING而字段目录里没有设置REF_TABLE和REF_FIELD。SAP对金额字段的标准处理方式是以CURR类型存储配合VCURRENCY指定币种字段。如果你输出内表的DMBTR定义的是CURR但低版本不指定CURRENCY属性ALV会无法正确格式化。解决方法是设置字段目录的REF_TABLE BSEG和REF_FIELD DMBTR或者直接指定CURRENCY WAERS。Excel导出异常比如导出后数字变成文本格式、列宽错乱多数是因为字段目录里没有正确设置数据类型和输出长度。ALV导出的Excel格式几乎是固定的想深度自定义可以用类CL_GUI_ALV_GRID的LIST_DISPLAY_IMPORT或者干脆在程序里用OLE2去生成Excel不过后者代码量大适合特殊需求场景。5.3 性能慢报表执行卡死或超时遇到报表执行慢先看是不是取数SQL本身慢。用ST05跟踪一下如果SQL语句走了全表扫描优先检查查询条件列有没有建立索引优先检查是不是SELECT *把太多无用字段查出来了优先检查是不是JOIN条件少了等值关系。如果SQL本身不慢但程序还是慢那很可能是后续LOOP做的大量单笔查询。比如前文举的N1例子这是最典型的。另外有一种情况报表本身数据量不大但内存溢出比如内表定义成STANDARD TABLE但数据有几百万行这时候需要把内表改成SORTED TABLE或者HASHED TABLE按查询需求设计主键或者在取数时加上GROUP BY在数据库层面完成聚合而不是把全量数据都捞到应用服务器再做处理。5.4 SAP系统特有报错与权限问题简单报表开发里常见的报错有几种No authorization to display data in table说明数据元素化的权限对象没有通过Short dump最常见的原因是无符号类型存储了负数、字符串赋值给数字字段等还有Runtime Errors MOVE_CAST_ERROR一般是因为对象类型转换问题。权限问题在报表开发里尤其重要。客户端用户执行报表时如果程序没有做权限检查可能查询出无权查看的数据这在大企业里是合规事故。简单报表也需要在关键操作时加上权限检查比如AUTHORITY-CHECK OBJECT S_TCODE ID TCD FIELD ZMMRPT005 ID ACTVT FIELD 03. IF sy-subrc 0. MESSAGE e000(zz) WITH 无权限执行此事务. ENDIF.另外如果你在程序中调用了BAPI来执行过账、修改等操作务必要在调用前做权限对象的验证别指望BAPI会替你检查权限。权限检查不能替代查询条件也不能替代逻辑层面的行级过滤。SAP很多标准报表通过权限对象F_BKPF_BUK来控制公司代码权限自己开发报表时最好也加上这些标准权限对象否则审计的时候会被业务部门指出来。6. 实操中容易忽略的细节从能跑到好用6.1 日志、变式与批量导出先看一个很常见的场景用户每天都要跑同一张报表条件几乎固定只是日期不同。这种重复劳动用SAP变式Variant就能解决。在ALV报表执行时系统一般会自动询问是否保存变式只要在PF状态里设置好变式的支持用户就能把常用查询条件存成一个变式点一下就能执行。如果需要定时后台运行可以在SM36里配置后台作业。但这里有个坑后台作业跑ALV报表没有交互式屏幕所以报表必须在逻辑上设计成能后台执行。也就是说程序不能依赖用户的选择屏幕交互一切数据都由变式提供且输出方式要考虑好最好是把结果写到应用服务器的物理路径或者发邮件给用户或者直接保存为PDF/Excel传到共享盘。这块逻辑如果不在开发时规划好后期做定时任务会发现根本跑不起来。6.2 文本、单位、描述的处理简单报表里文本和单位是两个高频坑点。文本方面SAP的多语言文本表MAKT、T001T、CSKT、SKAT等都要加上语言条件通常是spras sy-langu。如果报表界面支持多语言就需要在设计字段目录时把语言列也带上或者取数时按当前登录语言取。如果你在取数时漏了语言条件可能出现英文用户看到中文描述或者干脆文本为空的情况。单位方面最经典的坑是物料数量单位混用。采购订单里订单数量看MENGE订单单位收货数量看ERFMG收货单位两者可能不同。库存报表里的可用数量在MARD表里是基本计量单位的数量用户问库存还有多少时要根据用户选择的显示单位决定是否需要换算。换算函数有MD_CONVERSION_MATERIAL_TO_UNIT、UNIT_OF_MEASURE_CONVERSION这些函数能处理物料单位转换但要注意传入的物料号和工厂明细要正确否则转换出来就是错数。6.3 用变式保存默认值提升日常使用效率前面提到了变式我再提醒一点报表开发时最好在PF状态里加一个保存变式的按钮。ALV的标准工具栏最右侧一般已经有变式相关的图标比如选择变式、保存变式但如果你用函数方式REUSE_ALV_GRID_DISPLAY需要设置i_save A或X才能允许用户保存变式。如果没设这个参数用户在界面上是看不到变式相关功能的。设置之后用户在输入好查询条件后点保存变式并起个名字下次执行直接选变式就能带出条件。这个功能在大量重复性查询场景里非常实用能明显提升用户日常工作效率。6.4 从能跑到好用用户体验层面的几个打磨点最后再分享几个提升报表体验的小细节预算充裕的话尽量都做上在ALV输出前给数量大的字段设置合计按需加DO_SUM在关键金额列做正负颜色区分借方蓝、贷方红用EMPHASIZE属性给长文本字段设置可换行显示或者点击弹出长文本详情在输出区域上方用WRITE显示查询参数、查询时间、记录条数方便用户截图反馈问题给ALV加刷新按钮在数据源可能有变化的场景里支持F5刷新。这些细节看着不起眼但在实际交付时用户往往就是靠有没有合计、导出Excel方不方便、颜色区分好不好看这些点来评价一个报表开发人员的水平。代码逻辑写清楚是底线体验打磨得好才是加分项。我个人在实际开发里一个很深的体会是所谓简单报表最容易翻车的地方反而不是代码而是你以为你理解的需求和用户真正想要的报表之间的偏差。所以我现在接到需求后再急也会先拿一支笔在纸上把谁来看、看什么字段、条件怎么选、输出后做什么这四个问题理一遍等用户确认了再动手写代码。这个习惯帮我避掉了大量返工也希望对新入行的朋友有帮助。至于代码本身上面这套流程跑通以后你会发现简单报表的开发效率能提升一大截——尤其是再利用好ALV的字段目录、函数调用和性能优化的惯用法之后从需求确认到交付一个普通的单表报表大半天时间就够了。
返回列表