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

前言

前段时间想要做一个web端的图形化积木式编程(类似少儿编程)的案例,网上冲浪了一圈又一圈,终于技术选型好,然后代码一顿敲,终于出来了一个雏形。

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

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

最终实现效果

最终实现效果

本文实现效果

Blockly自定义块

完整代码

  • Blockly自定义块
<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">执行代码</button>
  </div>
</template>

<script>
import Blockly from 'blockly'
import BlocklyJS from 'blockly/javascript';
import './customBlock'
export default {
  name: "blocklyClass1",
  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"
            },
          ]
        }
      }
    }
  },
  mounted() {
    Blockly.inject(this.$refs.blocklyDiv, this.options);
  },
  methods:{
    /**
     * block代码块转为代码
     */
    block2code(){
      this.code = BlocklyJS.workspaceToCode(this.$refs.blocklyDiv.workspace)
    },

    /**
     * 执行生成代码
     */
    runCode(){
        if(!this.code){alert('请先点击生成代码');return}
        eval(this.code)
    },
  },
}
</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>
  • 封装的自定义块方法 - customBlock.js
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) {
    block
    var while_content = Blockly.JavaScript.statementToCode(block, 'while_content');
    var code = 
        'robot.init();\n'
        + while_content +
        'robot.stop();\n';
    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 = 'await robot.pencilcolor(' + number_red + ',' + number_green + ',' + number_blue + ');\n';
    return code;
};

代码分解

自定义块主要分3部分:
1、定义块行为
2、注册块
3、定义块生成代码
4、引入块

0.代码块前置知识

0.1 汉化

import * as hans from 'blockly/msg/zh-hans'
Blockly.setLocale(hans);//汉化

0.2 预置块

  • Blockly原本已经预置了很多代码块了

  • 逻辑块(logic_compare、logic_operation、logic_negate、logic_boolean)


    逻辑块
  • 循环控制块(controls_repeat_ext、controls_whileUntil、controls_if)


    循环控制块
  • 数学块(math_number、math_arithmetic、math_single)


    数学块
  • 文本块(text、text_length、text_print)


    文本块
  • 变量块(variables_get、variables_set)


    变量块

0.3 不同样式块含义

0.3.1 连接方式

  • 上下连接


    上下连接

以javascript为例,在一个事件循环中,代码块会顺序执行(不包含异步代码块),代码块之间就是上下连接方式

let loop = 10//上连接为空,下连接为print
print('hello world')//上连接为赋值语句,下连接为for循环代码块
for(let i = 0; i < 10; i++){...}//上连接为print,下连接为while循环代码块
while(loop){...}//上连接为for循环代码块,下连接为空
  • 左连接


    左连接

左连接可以理解为其为输出
任何值的输出都使用左连接方式,如(数字模块输出数字、文本模块输出文本、逻辑模块输出真假)

0.3.2 输入方式

输入方式,即函数传入的参数
输入的连接方式不一样,日常使用中感觉不出区别
输入可以现在输入的类型,如if、when模块的外接类型输入限制了类型只能为逻辑模块

  • 内联


    内联
  • 外接


    外接

0.3.2 输入类型

输入的模块的类型分为值输入和块输入

  • 值输入(数字、文本、逻辑)

  • 块输入(代码块)

0.4 代码块构建代码块

  • Blockly预置的很多代码块基本符合日常编程的各种使用场景,而其更出色的地方在于其能自定义代码块,而且是通过代码块来生成代码块

官方的自定义代码块链接:自定义代码块构建工具
工具的食用方法可参考大佬文章:blockly构建自定义块及其工具

  • 而在本文中的案例中,控制小车的移动等操作,小车的动作是依次执行的,其实用得最多是上下连接的代码块,该代码块中包含内联的一个或多个值输入:


    弧度运动控制块
  • 对应的构造如下:


    构建工具编辑说明
  • 还有就是小车启动的函数,用的是上下连接的代码块,该代码块中包含外接的块输入:


    小车启动控制块
  • 对应的构造如下,其中空输入只是为了在其中填入文本让其能单独一行显示(好看一点):


    构建工具编辑说明

1. 自定义块

  • 熟悉Block Definition的生成规则当然是最好的了,但是官方提供的代码块构建工具,进行了可视化预览、定义配置、块的生成代码,极大方便了我们的开发。


    代码块构建工具布局描述
  • 所以自定义块的流程变得很简单啦
    1、通过toolbox提供的各种工具块,拖动到编辑区,根据自己的需求区构建自定义块。
    2、通过预览区实时查看自定义块是否达到预期效果
    3、达到预期效果后,将定义配置区中的JSON配置信息在项目中使用Blockly.defineBlocksWithJsonArray的方法注册自定义块
    4、如果是需要生成JavaScript代码,生成代码区选择JavaScript,然后将代码复制到项目中
    5、最后就是将代码块引入到toolbox工具箱中

1.1 定义块行为

  • 定义块行为就是上面所说的编辑区编辑块规则,或者熟悉Block Definition的同学通过块定义的规则编写JSON格式文本,从而形成块的形状(其形状基本是决定了请行为和功能)

1.2 注册块

  • 将定义配置区中的JSON配置信息在项目中使用Blockly.defineBlocksWithJsonArray的方法注册自定义块
/**
 * 自定义组件注册
 */

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": "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": ""
      }
    ]);

1.2 定义块生成代码

  • 生成代码区选择JavaScript,然后将代码复制到项目中,其中代码已经写好了变量的获取方式,返回值就是生成的代码,我们需要编写的就是返回值的内容。
  • 而对于控制小车对象而言,笔者这里统一使用robot对象,然后调用对象中的各种方法,如下面代码中拼接出来的代码为robot.arc('方向','弧的角度','弧的半径')
/**
 * 弧度运动自定义块
 * @param block
 * @returns {string}
 */
Blockly.JavaScript['arc'] = function(block) {
  var dropdown_dirction = block.getFieldValue('dirction');
  var angle_degree = block.getFieldValue('degree');
  var radius = block.getFieldValue('radius');
  // TODO: Assemble JavaScript into code variable.
  var code = 'robot.arc(' + dropdown_dirction + ', ' + angle_degree + ',' + radius + ');\n';
  return code;
};
  • 如下代码是控制程序生命周期的,拼接出来的代码为
robot.init();
...(控制小车运动的代码块)
robot.stop();
/**
 *程序启动自定义块
 * @param block
 * @returns {string}
 */
Blockly.JavaScript['while_program_start'] = function (block) {
  block
  var while_content = Blockly.JavaScript.statementToCode(block, 'while_content');
  var code = 'robot.init();\n' +
      while_content +
      'robot.stop();\n';

  return code;
};

1.3 引入块

  • 将自定义块引入到toolbox工具箱中
data() {
      options: {
        toolbox: {
          "kind": "flyoutToolbox",

          "contents": [
            {
              "kind": "block",
              "type": "while_program_start",
            },
            {
              "kind": "block",
              "type": "arc"
            },
            ...
          ]
        }
      }
  },
mounted() {
    //注入选项
    Blockly.inject(this.$refs.blocklyDiv, this.options);
  },     

后续计划

Blockly

  • blockly第三方组件使用
  • 小车控制方法的封装
  • 接入js-interpreter,步骤运行block块
  • ......(想到啥写啥)

开源项目GitHub链接

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容