背景
前端开发通常基于开源框架,而非直接写原生JS;GS目前只能写原生代码,没有任何框架,因此存在以下痛点:
- 10个人写的代码就有10个风格,开发者能力参差不齐,某些开发者不知道怎样写脚本更好
- 复杂实操的脚本内部有大量页面跳转逻辑,且跳转逻辑、接口调用、数据处理、页面渲染都揉杂在一起,没有清晰的脉络,后人不易读、不敢改
- 不同脚本内相同的逻辑需要在所有脚本里写一遍,公共逻辑无法复用
- GS本身的一些特性需要所有开发者了解,徒增理解成本,不利于GS的大规模推广
解决方案
实操脚本做的事本身是确定的、简单的,脚本本质就是在表达一个“由多场景组成的实操流程”,因此我们通过抽象建模,给脚本开发者提供了一套轻量的小框架,把开发工作由“做简答题”变成“做填空题”,使开发工作变得简单、脚本脉络变得清晰、代码风格变得统一,方便传承、迭代、维护
模板地址
GS项目结构
*仅列出src下的目录结构
-src
-lib
-framework.gs -> 框架代码
-scene -> 用户定义的场景,每个场景文件export一个场景描述对象(见“scene模板”和 “scene文件样例”)
-scanContainer.gs
-scanLocation.gs
-scanItem.gs
-confirmItem.gs
-finishItem.gs
-context.gs -> 全局变量
-function.gs -> 用户自定义的function
-start.gs -> 实操流程入口,通过调用framework.gs的run方法开始实操流程
scene模板
{
sceneCode: “”,
pageInit: {
action: []
}
page: [{
when: “”,
pageUrl: “”,
pageData: {},
pageType: “”,
pageOption: {
“无此商品”: {
action: {}
}
}
}],
action: [{
when: “”,
type: “jump”,
payload: {
sceneCode: “”,
sceneData: {}
}
}],
hooks: {
“”: {
action: []
},
}
}
scene文件样例
import * as CTX from '../context.gs';
//Scene 定义
export var scanContainerScene = {
sceneCode: 'scanContainer',//Scene编码,必填
//pageInit页面初始化之前需要完成基本操作
pageInit:{
//标准action定义
action:[{
name: "scanEmployeeScenePageInit",
//如果是自定义方法 eg:"$.testFn" 以“$.” 并定义在 function.gs中
when: true,
//类型定义下面,有详解
type: "request",
payload: {
//在执行 exec 中的方法的时候,是否需要刷新session 默认不填写需要刷新session
refreshDisable:true,
//参数 此参数支持传入exec的function执行,如果是多个function都会传入
param:XXXXX
//执行方法,支持数组,方法的返回值默认会放到 CTX.rpcResult中
exec: ["$.scanEmployeeScenePageInit"],
//钩子定义 执行完当前action之后 后续执行钩子流程
hooks:"testService.initPage"
}
}]
},
//页面定义
page: [{
//条件表达是 支持 lambda表达式 和 自定义方法
//如果是自定义方法 eg:"$.testFn" 以“$.” 并定义在 function.gs中
when: () => CTX.sceneData.employeeCode == "100",
//页面地址
pageUrl: "/pages/scanContainer/index.lemo.json",
//页面渲染数据
pageData: {
"header_title": "扫运单1234",
"maincard_title": "已扫运单数1234",
//支持lambda表达式和自定义方法
"maincard_content": () => CTX.sceneData.employeeCode,
"maincard_desc": "",
"bottom_tip": "< 按左键可手动输入"
},
//页面类型定义
pageType: "scan",
//更多按钮选项
pageOption: {
//option中选项名称
"回到首页": {
//支持条件表达,当前选项是否展示在前端,不写when默认展示
when : true,
//点击option操作的时候执行的action
action: [{
name: "finishScene_pageOption_go_first_page",
when: true,
type: "request",
payload: {
exec: ["$.goBackFirstPageFunction"],
hooks:"locationService.goBackFirstPage",
}
}]
},
"回到扫容器页面": {
action: [{
name: "finishScene_pageOption_go_scanContainer",
when: true,
type: "request",
payload: {
exec: ["$.goScanContainerPageFunction"],
hooks:"locationService.goBackScanContainerPage"
}
}],
},
//系统设置
"设置": {
action: [{
name:"settings",
type:"lemo",
payload:{
"menu":"settings",
data:{}
}
}]
},
},
},
{
when: () => CTX.sceneData.employeeCode != "100",
pageUrl: "/pages/scanContainer/index.lemo.json",
pageData: {
"header_title": "扫运单5678",
"maincard_title": "已扫运单数5678",
"maincard_content": () => CTX.sceneData.employeeCode,
"maincard_desc": "",
"bottom_tip": "< 按左键可手动输入"
},
pageType: "scan",
}
],
//page流程执行完成之后,进入action流程
action: [{
name: "scanContainerIsNotBack",
when: "$.isNotBack",
type: "request",
payload: {
exec: ["$.scanContainerFunction"],
hooks:"testService.scanContainer"
}
},
{
name: "scanContainerIsBack",
when: "$.isBack",
type: "jump",
payload: {
sceneCode: 'scanEmployee',
}
}
],
hooks: {
//钩子执行流程
'testService.scanContainer': {
action: [{
name: "scanContainerNumber",
when: "$.goToNumber",
type: 'jump',
payload: {
sceneCode: 'scanInputNumber'
},
},
{
name: "scanContainerYes",
when: "$.isSuccess",
type: 'jump',
payload: {
sceneCode: 'finish',
sceneData: () => CTX.rpcResult.data.sceneData,
},
},
{
name: "scanContainerNo",
when: "$.isNotSuccess",
type: 'tipsWithoutJump',
payload: {
level: "warning",
code: () => CTX.rpcResult.error.code,
message: () => CTX.rpcResult.error.message
},
},
]
},
},
}
scene字段说明
一个场景最外层包含“sceneCode”、“pageInit”、“page”、“action”、“hooks”四种元素
- sceneCode:场景的编码
- pageInit:页面初始化定义
- action:等价于最外层的action
page:页面的定义
- when:页面是否渲染的判定条件
- pageUrl:页面地址
- pageData:页面数据
- pageType:页面类型 (“scan”、“confirm”、“keypad”、“scanWithoutJump”、“welcome”)
- pageOption:
- when: 条件表达式,如果不写默认当前option会展示.
- action:等价于最外层的action
pageType=scan
page: [{
pageUrl: "/pages/scanEmployee/index.lemo.json",
pageData: {
"header_title": "扫劳动力工号",
"bottom_tip": "< 按左键可手动输入"
},
pageType: "scan",
}]
pageType=confirm
page: [{
pageUrl: "/pages/confirm/index.lemo.json",
pageData: {
"confirmdialog_title": () => CTX.rpcResult.title,
"confirmdialog_content": () => CTX.rpcResult.content,
"left_btn_text": "取消",
"right_btn_text": "确定"
},
pageType: "confirm"
}],
pageType=keypad
page: [{
when: true,
pageData: {
title: "输入箱体数量",
placeholder: "placeholder提示",
keypad_tips: "提示",
type: "number"
},
pageType: "keypad",
}],
pageType = scanWithoutJump
{
when: () => CTX.scanCount > 1,
pageUrl: "/pages/locationList/index.lemo.json",
pageData: {
"header_title": "扫库位/商品",
"list_data_source": "$.buildLocationList"
},
pageType: "scanWithoutJump",
}
pageType = welcome
page: [{
when: true,
pageUrl: "welcome",
pageData: {
apps: ()=>CTX.sceneData.apps,
laborId: ()=>CTX.sceneData.laborId
},
pageOption: {
"退出": {
action: [{
name: "welcomeQuit",
when: true,
type: "request",
payload: {
exec: ["$.larborQuit"]
}
}]
},
"设备工作地点": {
action: [{
name: "selectWorkPlace",
when: true,
type: "jump",
payload: {
sceneCode: "selectWorkPlace"
}
}]
},
"设置": {
action: [{
name:"settings",
type:"lemo",
payload:{
"menu":"settings",
data:{}
}
}]
}
},
pageType: "welcome",
}]
action:页面的动作定义 (inputValue的action)
- name:action的名称,仅用于定位问题
- when:action是否执行的判定条件,如果不填写,默认执行
- type:action类型 (“request”、“end”、“tipsWithoutJump”、“jump”、“tipsWithJump”,”confirm”)
payload:
request: param、exec、hooks
{
// 给exec function传参
param: ()=> {result:""},
//执行方法列表, 方法返回值会放到 CTX.rprResult中.支持多个方法执行
exec: ["$.scanEmployeeScenePageInit"],
//钩子,会匹配名称 在hooks中找到对应的action进行执行
hooks:"testService.initPage"
}
end时:无
- cancel:只能使用在action type=confirm的内部嵌套的action中.在option的action不要使用,会有流转问题
tipsWithoutJump时:level、code、message
{
level: "warning",
code: () => CTX.rpcResult.error.code,
message: () => CTX.rpcResult.error.message
}
jump时:sceneCode、sceneData (sceneData非必填)
{ //场景编码,支持使用表达式或function sceneCode: 'scanContainer', //场景数据 CTX.sceneData sceneData: () => { employeeCode: CTX.rpcResult.data.sceneData.employeeCode }, }
tipsWithJump时:level、code、message、sceneCode、sceneData (sceneData非必填)
{ //场景编码 sceneCode: 'finish', //场景数据 会放到 CTX.sceneData中 sceneData: () => { employeeCode: CTX.rpcResult.data.sceneData.employeeCode }, //level level: "warning", //code code: () => CTX.rpcResult.error.code, //message message: () => CTX.rpcResult.error.message }
confirm:
{ //confirm 页面url地址 confirmUrl: "/confirm/a.json", //confirm页面参数 confirmData: { "confirmdialog_title": "", "confirmdialog_content": "", "left_btn_text": "取消", "right_btn_text": "确定" }, //confirm对应的action action: [{ name: "isCancel", when: "$.isCancel", type: "cancel", }, { name: "isConfirm", when: "$.isConfirm", type: "request", payload: { exec: ["$.scanContainerFunction"], hooks: "testService.scanContainer" } }] }
hooks(钩子):监听并处理request请求返回值的钩子
- action:等价于最外层的action
*page数组的when按顺序匹配,只渲染第一个页面;action的when按顺序匹配,执行所有when==true的action
执行示意图
1、黑色箭头描述程序执行顺序,红色箭头表示获取数据
2、page内部有pageOption,pageOption内是action,其等价于hooks内的action、pageInit内的action、scene最外层的action
3、页面有
“scan” 表示扫描页
“confirm”确认页
“keypad”输入页
“scanWithoutJump”扫描页且ajax刷新数据
“welcome” 欢迎页面(应用列表页)
4、场景定义文件export一个页面描述对象,对象的各字段value值可以写字面值、lambda表达式、“$.”+方法名
5、function写在function.gs内,全局变量写在context.gs文件内