背景

前端开发通常基于开源框架,而非直接写原生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文件样例

  1. import * as CTX from '../context.gs';
  2. //Scene 定义
  3. export var scanContainerScene = {
  4. sceneCode: 'scanContainer',//Scene编码,必填
  5. //pageInit页面初始化之前需要完成基本操作
  6. pageInit:{
  7. //标准action定义
  8. action:[{
  9. name: "scanEmployeeScenePageInit",
  10. //如果是自定义方法 eg:"$.testFn" 以“$.” 并定义在 function.gs中
  11. when: true,
  12. //类型定义下面,有详解
  13. type: "request",
  14. payload: {
  15. //在执行 exec 中的方法的时候,是否需要刷新session 默认不填写需要刷新session
  16. refreshDisable:true,
  17. //参数 此参数支持传入exec的function执行,如果是多个function都会传入
  18. param:XXXXX
  19. //执行方法,支持数组,方法的返回值默认会放到 CTX.rpcResult中
  20. exec: ["$.scanEmployeeScenePageInit"],
  21. //钩子定义 执行完当前action之后 后续执行钩子流程
  22. hooks:"testService.initPage"
  23. }
  24. }]
  25. },
  26. //页面定义
  27. page: [{
  28. //条件表达是 支持 lambda表达式 和 自定义方法
  29. //如果是自定义方法 eg:"$.testFn" 以“$.” 并定义在 function.gs中
  30. when: () => CTX.sceneData.employeeCode == "100",
  31. //页面地址
  32. pageUrl: "/pages/scanContainer/index.lemo.json",
  33. //页面渲染数据
  34. pageData: {
  35. "header_title": "扫运单1234",
  36. "maincard_title": "已扫运单数1234",
  37. //支持lambda表达式和自定义方法
  38. "maincard_content": () => CTX.sceneData.employeeCode,
  39. "maincard_desc": "",
  40. "bottom_tip": "< 按左键可手动输入"
  41. },
  42. //页面类型定义
  43. pageType: "scan",
  44. //更多按钮选项
  45. pageOption: {
  46. //option中选项名称
  47. "回到首页": {
  48. //支持条件表达,当前选项是否展示在前端,不写when默认展示
  49. when : true,
  50. //点击option操作的时候执行的action
  51. action: [{
  52. name: "finishScene_pageOption_go_first_page",
  53. when: true,
  54. type: "request",
  55. payload: {
  56. exec: ["$.goBackFirstPageFunction"],
  57. hooks:"locationService.goBackFirstPage",
  58. }
  59. }]
  60. },
  61. "回到扫容器页面": {
  62. action: [{
  63. name: "finishScene_pageOption_go_scanContainer",
  64. when: true,
  65. type: "request",
  66. payload: {
  67. exec: ["$.goScanContainerPageFunction"],
  68. hooks:"locationService.goBackScanContainerPage"
  69. }
  70. }],
  71. },
  72. //系统设置
  73. "设置": {
  74. action: [{
  75. name:"settings",
  76. type:"lemo",
  77. payload:{
  78. "menu":"settings",
  79. data:{}
  80. }
  81. }]
  82. },
  83. },
  84. },
  85. {
  86. when: () => CTX.sceneData.employeeCode != "100",
  87. pageUrl: "/pages/scanContainer/index.lemo.json",
  88. pageData: {
  89. "header_title": "扫运单5678",
  90. "maincard_title": "已扫运单数5678",
  91. "maincard_content": () => CTX.sceneData.employeeCode,
  92. "maincard_desc": "",
  93. "bottom_tip": "< 按左键可手动输入"
  94. },
  95. pageType: "scan",
  96. }
  97. ],
  98. //page流程执行完成之后,进入action流程
  99. action: [{
  100. name: "scanContainerIsNotBack",
  101. when: "$.isNotBack",
  102. type: "request",
  103. payload: {
  104. exec: ["$.scanContainerFunction"],
  105. hooks:"testService.scanContainer"
  106. }
  107. },
  108. {
  109. name: "scanContainerIsBack",
  110. when: "$.isBack",
  111. type: "jump",
  112. payload: {
  113. sceneCode: 'scanEmployee',
  114. }
  115. }
  116. ],
  117. hooks: {
  118. //钩子执行流程
  119. 'testService.scanContainer': {
  120. action: [{
  121. name: "scanContainerNumber",
  122. when: "$.goToNumber",
  123. type: 'jump',
  124. payload: {
  125. sceneCode: 'scanInputNumber'
  126. },
  127. },
  128. {
  129. name: "scanContainerYes",
  130. when: "$.isSuccess",
  131. type: 'jump',
  132. payload: {
  133. sceneCode: 'finish',
  134. sceneData: () => CTX.rpcResult.data.sceneData,
  135. },
  136. },
  137. {
  138. name: "scanContainerNo",
  139. when: "$.isNotSuccess",
  140. type: 'tipsWithoutJump',
  141. payload: {
  142. level: "warning",
  143. code: () => CTX.rpcResult.error.code,
  144. message: () => CTX.rpcResult.error.message
  145. },
  146. },
  147. ]
  148. },
  149. },
  150. }

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

    1. page: [{
    2. pageUrl: "/pages/scanEmployee/index.lemo.json",
    3. pageData: {
    4. "header_title": "扫劳动力工号",
    5. "bottom_tip": "< 按左键可手动输入"
    6. },
    7. pageType: "scan",
    8. }]

    pageType=confirm

    1. page: [{
    2. pageUrl: "/pages/confirm/index.lemo.json",
    3. pageData: {
    4. "confirmdialog_title": () => CTX.rpcResult.title,
    5. "confirmdialog_content": () => CTX.rpcResult.content,
    6. "left_btn_text": "取消",
    7. "right_btn_text": "确定"
    8. },
    9. pageType: "confirm"
    10. }],

    pageType=keypad

    1. page: [{
    2. when: true,
    3. pageData: {
    4. title: "输入箱体数量",
    5. placeholder: "placeholder提示",
    6. keypad_tips: "提示",
    7. type: "number"
    8. },
    9. pageType: "keypad",
    10. }],

    pageType = scanWithoutJump

    1. {
    2. when: () => CTX.scanCount > 1,
    3. pageUrl: "/pages/locationList/index.lemo.json",
    4. pageData: {
    5. "header_title": "扫库位/商品",
    6. "list_data_source": "$.buildLocationList"
    7. },
    8. pageType: "scanWithoutJump",
    9. }

    pageType = welcome

    1. page: [{
    2. when: true,
    3. pageUrl: "welcome",
    4. pageData: {
    5. apps: ()=>CTX.sceneData.apps,
    6. laborId: ()=>CTX.sceneData.laborId
    7. },
    8. pageOption: {
    9. "退出": {
    10. action: [{
    11. name: "welcomeQuit",
    12. when: true,
    13. type: "request",
    14. payload: {
    15. exec: ["$.larborQuit"]
    16. }
    17. }]
    18. },
    19. "设备工作地点": {
    20. action: [{
    21. name: "selectWorkPlace",
    22. when: true,
    23. type: "jump",
    24. payload: {
    25. sceneCode: "selectWorkPlace"
    26. }
    27. }]
    28. },
    29. "设置": {
    30. action: [{
    31. name:"settings",
    32. type:"lemo",
    33. payload:{
    34. "menu":"settings",
    35. data:{}
    36. }
    37. }]
    38. }
    39. },
    40. pageType: "welcome",
    41. }]
  • action:页面的动作定义 (inputValue的action)

    • name:action的名称,仅用于定位问题
    • when:action是否执行的判定条件,如果不填写,默认执行
    • type:action类型 (“request”、“end”、“tipsWithoutJump”、“jump”、“tipsWithJump”,”confirm”)
    • payload:

      • request: param、exec、hooks

        1. {
        2. // 给exec function传参
        3. param: ()=> {result:""},
        4. //执行方法列表, 方法返回值会放到 CTX.rprResult中.支持多个方法执行
        5. exec: ["$.scanEmployeeScenePageInit"],
        6. //钩子,会匹配名称 在hooks中找到对应的action进行执行
        7. hooks:"testService.initPage"
        8. }
      • end时:无

      • cancel:只能使用在action type=confirm的内部嵌套的action中.在option的action不要使用,会有流转问题
      • tipsWithoutJump时:level、code、message

        1. {
        2. level: "warning",
        3. code: () => CTX.rpcResult.error.code,
        4. message: () => CTX.rpcResult.error.message
        5. }
      • 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

执行示意图

image.png
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文件内