Vue实现图形化积木式编程(十三)

路由

历史回顾

Babylon.js部分

Blockly部分

前言

TIPS:该案例设计主要参考iRobot Coding,只用做学习用途,侵删。

https://code.irobot.com/#/

最终实现效果

最终实现效果

本文内容

  • 步骤运行代码块高亮步骤运行代码块高亮

实现

1. 安装依赖

  • 安装js解析器
npm install js-interpreter

2. 简化语法

在上一篇文章中提到了由于代码块是异步任务,而异步任务需要顺序执行,所以语法上需要构造成一个用async函数包裹的立即执行函数,虽然它的执行逻辑符合需求,但是对于没学过编程的同学第一眼看上去会觉得很乱,所以这里需要引入js-interpreter来简化生成代码语法。

在这里插入图片描述

(async ()=>{
robot.init();
  await robot.move(50);
  await robot.arc(0, 90, 50);
robot.stop();
})()
  • 从原来的async代码块形式改为:
robot.init();
  robot.move(50);
  robot.arc(0, 90, 50);
robot.stop();
  • 相应的,生成代码的地方改成如下形式:
/**
 * 自定义组件生成代码
 * @param block
 * @returns {string}
 */
Blockly.JavaScript['while_program_start'] = function (block) {
    let while_content = Blockly.JavaScript.statementToCode(block, 'while_content');
    while_content = while_content.slice(0, -1) // 去除最后一个\n
    const code = `
robot.init();
${while_content}
robot.stop();
`
    return code;
};

Blockly.JavaScript['move'] = function (block) {
    var text_move_distance = block.getFieldValue('move_distance');
    var code = `robot.move(${text_move_distance});\n`;
    return code;
};

Blockly.JavaScript['turn'] = function (block) {
    var dropdown_dirction = block.getFieldValue('dirction');
    var angle_degree = block.getFieldValue('degree');
    var code = `robot.turn(${dropdown_dirction}, ${angle_degree});\n`;
    return code;
};

Blockly.JavaScript['arc'] = function (block) {
    var dropdown_dirction = block.getFieldValue('dirction');
    var angle_degree = block.getFieldValue('degree');
    var radius = block.getFieldValue('radius');
    var code = `robot.arc(${dropdown_dirction}, ${angle_degree}, ${radius});\n`;
    return code;
};

3. 引入js解析器

  • 当然了,上面简化后的语句使用eval执行并不能实现串行顺序执行的效果,我们需要实例化一个JS Interpreter
  • JS Interpreter是一个与浏览器完全隔离的沙箱环境,任何函数或者变量都需要添加到解释器中。
  • 需要注意的是,interpreter.createNativeFunction接受的函数的最后一个参数callback(额外参数)必须调用才视为异步函数调用结束,具体见链接: JS-Interpreter文档,也可加下图
    在这里插入图片描述
import Interpreter from 'js-interpreter'
// 将blockly工作区挂载到dom树上
let workspace = Blockly.inject(this.$refs.blocklyDiv, this.options)
// 这个Robot实例对象在上一篇文章有定义,其中包含最基本的init()、stop()、move(dir)、arc(direction, degree, distance)等方法
let robotController = new Robot()
// let code = BlocklyJS.workspaceToCode(this.$refs.blocklyDiv.workspace)
// 假设已经通过BlocklyJS.workspaceToCode获取到了blocky代码块生成的代码字符串
let code = `
robot.init();
  robot.move(50);
  robot.arc(0, 90, 50);
robot.stop();
` 


function runCode(code) {
      // 实例化js解析器
      // 在创建js解析器期间,会调用initApi方法创建解析器的全局变量
      let myInterpreter = new Interpreter(code, initApi);
}

function initApi(interpreter, globalObject) {
      // 创建 'robot' 的全局对象
      var robot = interpreter.nativeToPseudo({});
      interpreter.setProperty(globalObject, 'robot', robot);
      // 定义 'robot.init' 的函数
      let iniWrapper = function init() {
        return robotController.init();
      };
      interpreter.setProperty(robot, 'init',
          interpreter.createNativeFunction(iniWrapper));
      // 定义 'robot.stop' 的函数
      var stopWrapper = function stop() {
        return robotController.stop();
      };
      interpreter.setProperty(robot, 'stop',
          interpreter.createNativeFunction(stopWrapper));
      // 定义 'robot.move' 的函数
      // interpreter.createAsyncFunction接受的函数最后一个参数为callback必须调用了才视为异步函数执行完成
      var moveWrapper = function move(distance, callback) {
        console.log('调用moveWrapper', that.robot,that.robot.move)
        robotController.move(distance).then(()=>{
          console.log('move完成了')
          callback(1)
        })
      };
      interpreter.setProperty(robot, 'move',
          interpreter.createAsyncFunction(moveWrapper));
      // 定义 'robot.arc' 的函数
      var arcWrapper = function arc(dir, degree, radius, callback) {
        robotController.arc(dir, degree, radius).then(()=>{
          console.log('arc完成了')
          callback()
        })
      };
      interpreter.setProperty(robot, 'arc',
          interpreter.createAsyncFunction(arcWrapper));
    }

4. 运行代码

  • 上一个操作中js-Interpreter已经将code解析成一系列可单步执行的代码块
  • 可通过myInterpreter.step()来单步执行代码,或者通过myInterpreter.run()一次性执行所有
function runStepByStep(myInterpreter){
      if (myInterpreter) {
      // run函数是一次性执行完所有的,单步执行可使用myInterpreter.step()
        var hasMore = myInterpreter.run();
        if (hasMore) {
          // 当前程序处于某个异步调用函数中,被阻塞了,设置延迟再调用。
          setTimeout(runStepByStep, 10, myInterpreter);
        } else {
          console.log('代码全部执行完了');
        }
      }
    }

5. 加入高亮

使用workspace.highlightBlock(id) 能高亮某个代码块
使用Blockly.JavaScript.STATEMENT_SUFFIX 可再每个语句前插入highlightBlock函数- 具体见: STATEMENT_PREFIX解释
在这里插入图片描述

  • 在创建worksapce之前设置在每个语句之前出入高亮块
function initHighlightBlock() {
      // 可以在生成JavaScript代码之前通过设置STATEMENT_PREFIX在逐条语句级别上完成此操作
      Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
      // 将highlightBlock添加为保留字
      Blockly.JavaScript.addReservedWords('highlightBlock');
    }
  • 在原来initApi函数基础上再加一个
      // 创建 'highlightBlock' 的函数
      var hightlightWrapper = function(id) {
        // console.log("highlightBlock")
        id = String(id || '');
        return workspace.highlightBlock(id);
      };
      interpreter.setProperty(globalObject, 'highlightBlock',
          interpreter.createNativeFunction(hightlightWrapper));
  • 在原来runStepByStep函数基础上,调用结束后设置workspace.highlightBlock(null)
function runStepByStep(myInterpreter){
      if (myInterpreter) {
      // run函数是一次性执行完所有的,单步执行可使用myInterpreter.step()
        var hasMore = myInterpreter.run();
        if (hasMore) {
          // 当前程序处于某个异步调用函数中,被阻塞了,设置延迟再调用。
          setTimeout(runStepByStep, 10, myInterpreter);
        } else {
          console.log('代码全部执行完了');
          workspace.highlightBlock(null)
        }
      }
    }

完整代码

  • 测试用例
<template>
  <div id="blockly">
    <!-- 工作区 -->
    <div id="blocklyDiv" ref="blocklyDiv" style="height: 500px; width: 800px;"></div>
    <button style="position: fixed;left: 50px;top: 10px;" @click="block2code">生成代码</button>
    <!-- 代码显示区 -->
    <div style="background-color: lightgrey;width: 800px;text-align: left">
      <pre v-html="code?code:'请点击生成代码按钮'"></pre>
    </div>
    <button style="position: fixed;left: 150px;top: 10px;" @click="runCode">eval执行代码</button>
    <button style="position: fixed;left: 300px;top: 10px;" @click="runCode2">new Function执行代码</button>
    <button style="position: fixed;left: 500px;top: 10px;" @click="runCode3">js-interpreter执行代码</button>
  </div>
</template>

<script>
import Blockly from 'blockly'
import BlocklyJS from 'blockly/javascript';
import './customBlock'
import Robot from './robot'
import Interpreter from "js-interpreter";
export default {
  name: "blocklyClass4",
  data() {
    return {
      code: '',
      options: {
        horizontalLayout: true,//工具箱水平
        toolboxPosition: "end",//工具箱在底部
        toolbox: {
          "kind": "flyoutToolbox",
          "contents": [
            {
              "kind": "block",
              "type": "while_program_start",
            },
            {
              "kind": "block",
              "type": "move",
            },
            {
              "kind": "block",
              "type": "turn",
            },
            {
              "kind": "block",
              "type": "arc"
            },
            {
              "kind": "block",
              "type": "draw"
            },
            {
              "kind": "block",
              "type": "pencilcolor"
            },
            {
              "kind": "block",
              "type": "controls_repeat_ext"
            },
            {
              "kind": "block",
              "type": "controls_whileUntil"
            },
            {
              "kind": "block",
              "type": "controls_for"
            },
            {
              "kind": "block",
              "type": "controls_if"
            },
            {
              "kind": "block",
              "type": "logic_compare"
            },
            {
              "kind": "block",
              "type": "logic_operation"
            },
            {
              "kind": "block",
              "type": "logic_negate"
            },
            {
              "kind": "block",
              "type": "logic_boolean"
            },
            {
              "kind": "sep",
              "gap": "32"
            },
            {
              "kind": "block",
              "blockxml": "<block type='math_number'><field name='NUM'>10</field></block>"
            },
            {
              "kind": "block",
              "type": "math_arithmetic"
            },
            {
              "kind": "block",
              "type": "math_single"
            },
            {
              "kind": "block",
              "type": "text"
            },
            {
              "kind": "block",
              "type": "text_length"
            },
            {
              "kind": "block",
              "type": "text_print"
            },
            {
              "kind": "block",
              "type": "variables_get"
            },
            {
              "kind": "block",
              "type": "variables_set"
            },
          ]
        }
      },
      workspace: null
    }
  },
  mounted() {
    this.initHighlightBlock()
    this.workspace = Blockly.inject(this.$refs.blocklyDiv, this.options);
    this.robot = new Robot()
  },
  methods: {
    /**
     * block代码块转为代码
     */
    block2code() {
      this.code = BlocklyJS.workspaceToCode(this.$refs.blocklyDiv.workspace)
    },
    /**
     * 执行生成代码
     */
    runCode() {
      if (!this.code) {
        alert('请先点击生成代码');
        return
      }
      window.robot = this.robot
      eval(this.code)
    },
    runCode2() {
      if (!this.code) {
        alert('请先点击生成代码');
        return
      }
      let fn = new Function('robot', this.code)
      fn(this.robot)
    },
    runCode3() {
      if (!this.code) {
        alert('请先点击生成代码');
        return
      }
      // 实例化js解析器
      // 在创建js解析器期间,会调用initApi方法创建解析器的全局变量
      let myInterpreter = new Interpreter(this.code, this.initApi);
      console.log('myInterpreter', myInterpreter)
      // this.initJsInterpreter(this.code)
      this.runStepByStep(myInterpreter)
    },
    runStepByStep(myInterpreter){
      if (myInterpreter) {
        var hasMore = myInterpreter.run();
        if (hasMore) {
          // 执行当前被某个异步调用阻止。
          //请稍后再试。
          setTimeout(this.runStepByStep, 10, myInterpreter);
        } else {
          this.highlightBlock(null);
          console.log('代码全部执行完了');
        }
      }

    },
    initHighlightBlock() {
      // 可以在生成JavaScript代码之前通过设置STATEMENT_PREFIX在逐条语句级别上完成此操作
      Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
      // 将highlightBlock添加为保留字
      Blockly.JavaScript.addReservedWords('highlightBlock');
    },

    highlightBlock(id) {
      this.workspace.highlightBlock(id);
    },

    initApi(interpreter, globalObject) {
      let that = this

      // 创建 'highlightBlock' 的函数
      var hightlightWrapper = function(id) {
        // console.log("highlightBlock")
        id = String(id || '');
        return that.highlightBlock(id);
      };
      interpreter.setProperty(globalObject, 'highlightBlock',
          interpreter.createNativeFunction(hightlightWrapper));

      // 创建 'robot' 的全局对象
      var robot = interpreter.nativeToPseudo({});
      interpreter.setProperty(globalObject, 'robot', robot);
      // 定义 'robot.init' 的函数
      let iniWrapper = function init() {
        return that.robot.init();
      };
      interpreter.setProperty(robot, 'init',
          interpreter.createNativeFunction(iniWrapper));
      // 定义 'robot.stop' 的函数
      var stopWrapper = function stop() {
        return that.robot.stop();
      };
      interpreter.setProperty(robot, 'stop',
          interpreter.createNativeFunction(stopWrapper));
      // 定义 'robot.move' 的函数
      // interpreter.createAsyncFunction接受的函数最后一个参数为callback必须调用了才视为异步函数执行完成
      var moveWrapper = function move(distance, callback) {
        that.robot.move(distance).then(()=>{
          console.log('move完成了')
          callback(1)
        })
      };
      interpreter.setProperty(robot, 'move',
          interpreter.createAsyncFunction(moveWrapper));
      // 定义 'robot.arc' 的函数
      var arcWrapper = function arc(dir, degree, radius, callback) {
        that.robot.arc(dir, degree, radius).then(()=>{
          console.log('arc完成了')
          callback()
        })
      };
      interpreter.setProperty(robot, 'arc',
          interpreter.createAsyncFunction(arcWrapper));
    }
  }
}
</script>

<style scoped>
#blockly {
  position: absolute;
  left: 50px;
  top: 50px;
  bottom: 0;
  width: calc(100vw - 50px);
  height: calc(100vh - 50px);
  display: flex;
  flex-direction: column;
}
</style>
  • Blockly自定义组件
import * as Blockly from 'blockly/core'

import * as hans from 'blockly/msg/zh-hans'

Blockly.setLocale(hans);//汉化

/**
 * 自定义组件注册
 */
Blockly.defineBlocksWithJsonArray(
    [
        //事件
        {
            "type": "while_program_start",
            "message0": "当程序运行 %1 %2",
            "args0": [
                {
                    "type": "input_dummy"
                },
                {
                    "type": "input_statement",
                    "name": "while_content"
                }
            ],
            "previousStatement": null,
            "nextStatement": null,
            "colour": "#609FD6",
            "strokeColour": "#4088C8",
            "tooltip": "123",
            "helpUrl": "1"
        },
        //指令
        {
            "type": "move",
            "message0": "移动 %1 CM",
            "args0": [
                {
                    "type": "field_input",
                    "name": "move_distance",
                    "text": "50"
                }
            ],
            "previousStatement": null,
            "nextStatement": null,
            "colour": "#F7D233",
            "strokeColour": "#CCAD2B",
            "tooltip": "",
            "helpUrl": ""
        },
        {
            "type": "turn",
            "message0": "向 %1 %2",
            "args0": [
                {
                    "type": "field_dropdown",
                    "name": "dirction",
                    "options": [
                        [
                            "左转",
                            "0"
                        ],
                        [
                            "右转",
                            "1"
                        ]
                    ]
                },
                {
                    "type": "field_angle",
                    "name": "degree",
                    "angle": 90
                }
            ],
            "previousStatement": null,
            "nextStatement": null,
            "colour": "#F7D233",
            "strokeColour": "#CCAD2B",
            "tooltip": "",
            "helpUrl": ""
        },
        {
            "type": "arc",
            "message0": "弧形 %1 %2 ,半径 %3 CM",
            "args0": [
                {
                    "type": "field_dropdown",
                    "name": "dirction",
                    "options": [
                        [
                            "向左",
                            "0"
                        ],
                        [
                            "向右",
                            "1"
                        ]
                    ]
                },
                {
                    "type": "field_angle",
                    "name": "degree",
                    "angle": 90
                },
                {
                    "type": "field_number",
                    "name": "radius",
                    "value": 50,
                    "min": 1,
                    "max": 100
                }
            ],
            "previousStatement": null,
            "nextStatement": null,
            "colour": "#F7D233",
            "strokeColour": "#CCAD2B",
            "tooltip": "",
            "helpUrl": ""
        },
        {
            "type": "draw",
            "message0": "设置 %1",
            "args0": [
                {
                    "type": "field_dropdown",
                    "name": "pencilState",
                    "options": [
                        [
                            {
                                "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFIGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjEtMDgtMjNUMTE6Mzk6NTQrMDg6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIxLTA4LTIzVDExOjUxOjAxKzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIxLTA4LTIzVDExOjUxOjAxKzA4OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmVkZGRkMGNlLTU4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",
                                "width": 50,
                                "height": 50,
                                "alt": "pencil down"
                            },
                            "1"
                        ],
                        [
                            {
                                "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFIGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjEtMDgtMjNUMTE6NDA6MTErMDg6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDIxLTA4LTIzVDExOjUyOjA2KzA4OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIxLTA4LTIzVDExOjUyOjA2KzA4OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjU3Y2UzN2ViLTQ0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",
                                "width": 50,
                                "height": 50,
                                "alt": "pencil up"
                            },
                            "0"
                        ]
                    ]
                }
            ],
            "previousStatement": null,
            "nextStatement": null,
            "colour": "#81C679",
            "tooltip": "",
            "helpUrl": ""
        },
        {
            "type": "pencilcolor",
            "message0": "设置笔颜色: 红 %1 绿 %2 蓝 %3",
            "args0": [
                {
                    "type": "field_number",
                    "name": "red",
                    "value": 100,
                    "min": 0,
                    "max": 255
                },
                {
                    "type": "field_number",
                    "name": "green",
                    "value": 100,
                    "min": 0,
                    "max": 255
                },
                {
                    "type": "field_number",
                    "name": "blue",
                    "value": 100,
                    "min": 0,
                    "max": 255
                }
            ],
            "previousStatement": null,
            "nextStatement": null,
            "colour": "#81C679",
            "tooltip": "",
            "helpUrl": ""
        }
    ]
);

/**
 * 自定义组件生成代码
 * @param block
 * @returns {string}
 */
Blockly.JavaScript['while_program_start'] = function (block) {
    let while_content = Blockly.JavaScript.statementToCode(block, 'while_content');
    while_content = while_content.slice(0, -1) // 去除最后一个\n
    const code =
`robot.init();
${while_content}
robot.stop();
`
    return code;
};

Blockly.JavaScript['move'] = function (block) {
    var text_move_distance = block.getFieldValue('move_distance');
    var code = `robot.move(${text_move_distance});\n`;
    return code;
};

Blockly.JavaScript['turn'] = function (block) {
    var dropdown_dirction = block.getFieldValue('dirction');
    var angle_degree = block.getFieldValue('degree');
    var code = `robot.turn(${dropdown_dirction}, ${angle_degree});\n`;
    return code;
};

Blockly.JavaScript['arc'] = function (block) {
    var dropdown_dirction = block.getFieldValue('dirction');
    var angle_degree = block.getFieldValue('degree');
    var radius = block.getFieldValue('radius');
    var code = `robot.arc(${dropdown_dirction}, ${angle_degree}, ${radius});\n`;
    return code;
};

Blockly.JavaScript['draw'] = function (block) {
    var dropdown_pencilstate = block.getFieldValue('pencilState');
    var code = `robot.drawable(${dropdown_pencilstate});\n`;
    return code;
};

Blockly.JavaScript['pencilcolor'] = function (block) {
    var number_red = block.getFieldValue('red') / 255.0;
    var number_green = block.getFieldValue('green') / 255.0;
    var number_blue = block.getFieldValue('blue') / 255.0;
    var code = `robot.pencilcolor(${number_red}, ${number_green}, ${number_blue});\n`;
    return code;
};
  • Robot控制类
// eslint-disable-next-line no-unused-vars
class Robot {
    constructor() {
        this.isRun = false
    }
    init() {
        console.log("robot模块化程序初始化")
        this.isRun = true
    }
    stop() {
        console.log("robot模块运行结束")
        this.isRun = false
    }
    checkStatus() {
        if (!this.isRun) {
            throw '程序需要初始化模块'
        }
    }
    async move(distance) {
        this.checkStatus()
        // 模拟小车运动
        return new Promise(resolve => {
            let moveDis = 0
            let interval = setInterval(() => {
                if (moveDis < distance) {
                    console.log(`move ${moveDis++}`)
                } else {
                    clearInterval(interval)
                    interval = undefined
                    resolve()
                }
            }, 100)
        })
    }
    async arc(direction, degree, distance) {
        this.checkStatus()
        // 模拟小车运动
        return new Promise(resolve => {
            let moveDis = 0
            let interval = setInterval(() => {
                if (moveDis < distance) {
                    console.log(`direction: ${direction}, move:${moveDis++}, degree: ${degree}`)
                } else {
                    clearInterval(interval)
                    interval = undefined
                    resolve()
                }
            }, 100)
        })
    }
}
export default Robot

本文章的用例代码已经同步到github上,运行程序后访问链接即可查看效果: http://localhost:3000/#/Class/blockclass4

开源项目GitHub链接

https://github.com/Wenbile/Child-Programming-Web

资源下载链接

你的点赞是我继续编写的动力

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Vue可以通过使用第三方库来实现图形验证码登录。下面我会给你一个基本的实现步骤: 1. 首先,需要安装一个适用于Vue的图形验证码库,比如`vue-verify-code`。通过运行以下命令进行安装: ``` npm install vue-verify-code ``` 2. 在你的Vue组件中,导入所需的库,并使用它来生成图形验证码。在模板中添加一个`<verify-code>`标签来展示图形验证码,如下所示: ```vue <template> <div> <verify-code ref="verifyCode"></verify-code> <button @click="refreshCode">刷新验证码</button> <input type="text" v-model="inputCode" placeholder="请输入验证码"> <button @click="verifyCode">验证</button> </div> </template> <script> import VerifyCode from 'vue-verify-code'; export default { components: { VerifyCode }, data() { return { inputCode: '' }; }, methods: { refreshCode() { this.$refs.verifyCode.refresh(); }, verifyCode() { const result = this.$refs.verifyCode.verify(this.inputCode); if (result) { // 验证通过,执行登录操作 // ... } else { // 验证失败,提示用户重新输入 // ... } } } } </script> ``` 3. 上述代码中,通过`this.$refs.verifyCode.refresh()`方法可以刷新验证码,而`this.$refs.verifyCode.verify(this.inputCode)`方法可以用于验证用户输入的验证码是否正确。根据验证结果,你可以执行相应的操作。 请注意,上述代码只是一个简单的示例,你需要根据具体的需求进行适当的修改和扩展。另外,确保你已经在项目中导入了相关的CSS文件,以使图形验证码正常显示。 希望以上内容对你有所帮助!如有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温温温B

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值