在制造业中,一般仓库和物流都会配备扫码枪来进行诸如收货、移库、盘点等操作。
SAP提供了ITS Mobile和RF Framework用来开发能够运行在扫码枪上的应用,但本质还是通过WebGUI技术,让扫码枪通过浏览器来登陆SAP,使用专门为小屏设备开发的Dialog程序。
扫码枪设备长期以来,操作系统一直被WinCE把持,或许是因为微软税的缘故,导致这些扫码枪除了耐操的优点外,别无长处。万年电阻屏、屏幕小、性能孱弱,更要命的是价格奇贵无比。
进入智能机时代,安卓发力,扫码枪系统多了一个选择。安卓扫码枪不仅配置强大,系统先进,而且因为系统免费的缘故,售价远低于搭载Win CE的扫码枪。在这样的背景下,笔者公司已采购安卓扫码枪来替代旧设备。
这时带来一个问题,无论ITS Mobile还是RF Framework,都是根植于Win CE设备,在安卓扫码枪上,体验落后。
为了改善用户体验,笔者起初想通过UI5开发HTML5应用来适配安卓,于是便浏览SCN查找资料。但随着笔者的深入研究,发现论坛上出现了另外一些声音。
虽然UI5是SAP极力推荐的前端框架,但有些资深开发人员(甚至SAP Lab的ABAP)都并不是很推崇使用UI5。主要诟病的地方在于UI5太过于复杂和庞大,学习成本高且极为占用硬件资源。有位ABAP甚至直言,除了SAP外,你找不到第二个公司会使用UI5去开发Web应用。
一位国内的ABAP顾问便提供了另一种思路。他曾参与过国内一家公司的项目,因甲方Manager是一位Vue.js的粉丝,所以他们内部采用Vue.js在BSP上开发了移动端Web App。
相较于UI5,Vue.js是目前极为流行的JS框架,网络资源丰富,还有完整的中文教程。且GitHub上有大量开箱即用的Vue前端框架可以选择,不需要担心界面美化的问题。Vue极为轻量,完整的JS文件大小100K不到,而SAP Open UI5都已经150M起步。另外Vue是MIT协议,不会产生任何费用。更为优秀的一点,是Vue完全基于JS,而不像UI5还须要受制于SAP版本,理论上只要你的SAP版本支持BSP(这要求实在是太低了),就能用Vue.js开发符合潮流的现代化移动端应用。
废话不多说,下面笔者就以一个查询库存的应用来简单介绍下如何在BSP上使用Vue.js搭建Web App。
附:SCN相关链接
如何使用vue.js和axios开发能与SAP交互的Web Appmodern-web-development-with-sap-hands-on-vue.js-axios
如何在BSP上跑vue.js的应用(这个案例使用了webpack打包JS,好处是能够进一步压缩js文件大小,缺点是代码不能直接在SAP中管理,笔者建议无深厚JS功底的读者请暂时不要尝试该博文的方法)step-by-step-to-run-vue-application-in-bsp
笔者假定用户现在须要一个移动端Web App,能够查询SAP中的实时库存信息。就像传统的报表一样,我们须要一个界面来给用户提供查询条件,并提供一个按钮,当用户输入查询条件并点击按钮后,查询SAP库存的信息,以表格的形式将结果返回给用户。
首先我们须要搭建一个交互式的现代BSP页面,我们须要Vue.js作为引擎来驱动这个页面,右键另存下方的js文件。
vue.js
Vue相当于一个Controller,负责控制前台的渲染和界面事件交互,展现须要另外的前端框架,这里笔者选用的是Buefy,这个框架的优点是极为轻量,这样可以确保在网速不佳的情况下也能较快的获取到js和css文件。右击另存下方的js文件和css文件。
buefy.min.js
buefy.min.css
因为SAP本身限制,MIME资源如果全部放在BSP项目下,读取速度会非常慢,所以除了核心的js文件,其它像是图片一类的文件,建议能不使用尽量不要使用,一定要使用,就优先使用SVG格式的图片,或是干脆引用互联网图片
需求中涉及与SAP后端交互的功能,笔者这里使用了axios这个HTTP库来实现。同样右键另存到本地先。
axios.min.js
继续新建另一个页面process.htm,这个页面并不用于展现,而是用于接收用户前台的查询条件,然后查找库存并将库存结果以json
格式的字符串返回。注意,这里我们须要修改process.htm的Mime Type为application/json;charset=utf-8
。
建议将Layout中的缺省HTML都删除,并在Page Attributes中定义一个没用的属性DUMMY来避免因为SAP缺少一些必要参数而导致无法激活的情况。
将我们之前下载好的js文件和css文件统统上传到应用里。上传结束后,激活整个项目,你的项目下的目录应该如下面第二张图一样:
SAP中点击Index.htm的Layout页签,将下面的源码复制进去。(笔者已将具体代码的用途在备注中标明,如果还有不懂的地方可以参考文末的地址,去相关项目的官方文档中了解)
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./buefy.min.css">
<script src="./vue.js">script>
<script src="./axios.min.js">script>
head>
<body>
<script src="./buefy.min.js">script>
<div id="my_app">
<section class="hero is-dark">
<div class="hero-body">
<div class="container">
<h1 class="title">
Hello Vue.js
h1>
<h2 class="subtitle">
{{ sap_func }}
h2>
div>
div>
section>
<section class="section">
<b-field label="Material Number" :label-position="labelPosition">
<b-input type="search" maxlength="18" v-model="matnr" @keyup.enter.native="search">b-input>
<p class="control">
<b-button class="button is-primary" @click="search">Searchb-button>
p>
b-field>
<b-table
:data="tab"
:bordered="false"
:striped="false"
:narrowed="true"
:hoverable="false"
:loading="false"
:focusable="false"
:mobile-cards="true"
:paginated="true"
:per-page="2"
>
<template slot-scope="tab">
<b-table-column field="id" label="ID">
{{ tab.row.id }}
b-table-column>
<b-table-column field="matnr" label="Material">
{{ tab.row.matnr }}
b-table-column>
<b-table-column field="maktx" label="Description">
{{ tab.row.maktx }}
b-table-column>
<b-table-column field="lgort" label="Location">
{{ tab.row.lgort }}
b-table-column>
<b-table-column field="labst" label="Quantity">
<span class="tag is-success">
{{ tab.row.labst }}
span>
b-table-column>
template>
<template slot="empty">
<section class="section">
<div class="content has-text-grey has-text-centered">
<b>No Datab>
div>
section>
template>
b-table>
<b-loading :is-full-page="true" :active.sync="isLoading" :can-cancel="false">b-loading>
section>
div>
<script>
// 这是用于处理查询请求,返回查询结果的界面
const restService = 'process.htm';
// 初始化我们的应用
var app = new Vue({
el: "#my_app", //绑定my_app
data:{
sap_func: "Inventory Report",// 应用名是库存报表
labelPosition: 'on-border',
isLoading: false, // 初始化不弹出loading界面
matnr: '', // 初始没有查询条件
tab: [] // 初始没有查询结果
},
// 与SAP交互的核心查询功能
methods:{
// 定义search方法,它会在点击查询按钮时被调用
search: function(){
// axios会使用HTTP POST方式,将matnr中的值,发送到另一个SAP页面并将返回的JSON值赋给my_app的tab
axios.post(restService,{
data:{
'matnr': this.matnr
}
}).then((response) =>{
this.tab = eval(response.data);
this.isLoading = false; // 获取到值后关闭遮罩
});
this.isLoading = true; // 查询时打开遮罩
}
}
});
script>
body>
html>
"axios发过来的json request格式为data-data-matnr这样的数据结构
"我们须要定义相同的类型才能解析request的入参
TYPES:BEGIN OF TY_DATA,
MATNR TYPE MATNR,
END OF TY_DATA,
BEGIN OF TY_JSON,
DATA TYPE TY_DATA,
END OF TY_JSON.
"这是我们返回查询结果的表
TYPES:BEGIN OF TY_MARD,
ID TYPE SY-TABIX,
MATNR TYPE MATNR,
MAKTX TYPE MAKTX,
LGORT TYPE LGORT_D,
LABST TYPE LABST,
END OF TY_MARD.
DATA:LV_STR TYPE STRING,
LS_PARA TYPE TY_JSON.
DATA:LS_MARD TYPE TY_MARD,
LT_MARD TYPE TABLE OF TY_MARD,
LO_JSON TYPE REF TO CL_TREX_JSON_SERIALIZER,
LV_RESULT_JSON TYPE STRING.
"获取用户的查询输入参数
LV_STR = RUNTIME->SERVER->REQUEST->GET_CDATA( ).
CALL METHOD CL_FDT_JSON=>JSON_TO_DATA
EXPORTING
IV_JSON = LV_STR
CHANGING
CA_DATA = LS_PARA.
TRANSLATE LS_PARA-DATA-MATNR TO UPPER CASE.
"获取库存信息
DO 5 TIMES.
CLEAR:LS_MARD.
LS_MARD-ID = SY-INDEX.
LS_MARD-MATNR = 'MAT'.
LS_MARD-MAKTX = 'TEST VUE'.
LS_MARD-LGORT = 'SL01'.
LS_MARD-LABST = SY-INDEX.
APPEND LS_MARD TO LT_MARD.
ENDDO.
DELETE LT_MARD WHERE MATNR NE LS_PARA-DATA-MATNR.
"将结果转化为JSON格式返回给客户端
CREATE OBJECT LO_JSON
EXPORTING
DATA = LT_MARD.
LO_JSON->SERIALIZE( ).
LV_RESULT_JSON = LO_JSON->GET_DATA( ).
RUNTIME->SERVER->RESPONSE->SET_CDATA( DATA = LV_RESULT_JSON ).
完成上述所有步骤后,激活整个BSP项目,并找到index.htm的网页地址。在浏览器中打开。
如果没有问题,就会看到这样的页面,在Material Number中输入MAT
并点击Search按钮或敲击回车。
如果没有问题,你就应该可以看到如下的页面了:
buefy
vuejs
axios