jsplumb 流程图,常用功能配置记录

前言:

jsplumb 有2个版本一个Toolkit Edition(付费版),另外一个就是Community Edition(社区版本)。Toolkit Edition版本功能集成的比较丰富,社区版本的就差好多,很多功能都没有,需要我们自己去添加,当然了自己添加多多少少有些麻烦,而且也不完善。但是我们还是用Community Edition(社区版本),毕竟不收费,没办法,下边所说的版本默认都是以社区版。

最近公司项目有用到这个流程图,所以也就看到了这个框架,这个框架是英文版本的,地址:https://jsplumbtoolkit.com/community/doc/home.html(可以用浏览器翻译了看)。他的缺陷就是文档不全,api感觉也有点乱,实例交代的也不清楚,github地址是:https://github.com/jsplumb/jsplumb (里面有demo,自己可以下载运行,多动手试试)。如果只是简单的画个图,这个框架是没有什么问题的,demo里也有,但是如果要实现高级的动能呢鞥,还是得多多尝试。此文也是记录一下我自己用到的一些功能,很多我还没用到,用到了在慢慢补充。

jsplumb.png

上图也就是我这次用到的jsplumb实现的功能,连接线能够拖拽生成,也可以删除,编辑label。

1、数据结构

{
  "nodes": [{  //节点集合
    "icon": "el-icon-loading",
    "id": "start",
    "nodeStyle": {
      "top": 100,
      "left": 200
    },
    "text": "开始",
    "type": "circle"
  }, {
    "icon": "el-icon-upload",
    "id": "end",
    "nodeStyle": {
      "top": 300,
      "left": 400
    },
    "text": "结束",
    "type": "circle"
  }] ,
  "connections": [{  //连接线集合
      "sourceId": "start",
      "targetId": "end",
      "label":"编辑"
    }]
}

jsplumb实例里面的数据结构就是这样的,这里我们沿用他的数据结构,你也可以自己定义自己想的数据结构,但是对比起来这个结构是最清晰也最方便的。

2、初始化

jsplumb在DOM渲染完毕之后才会执行,所以需要为jsplumb的执行代码绑定一个ready事件:

jsPlumb.ready(function() { 
    // your jsPlumb related init code goes here
});

jsplumb默认是注册在浏览器窗口的,将整个页面提供给我们作为一个实例,但我们也可以使用getInstance方法建立页面中一个独立的实例:

var _instance = jsPlumb.getInstance();

3、功能实现(允许哪些元素拖拽,允许拆卸连接)

let instance = jsPlumb.getInstance({
                PaintStyle:{ 
                    strokeWidth:2, 
                    stroke:"#567567", 
                }
            })
        //拖拽功能
        var els = document.querySelectorAll(".box");//.box是允许拖拽的元素class类名
        instance.draggable(els,{
           containment:true,
           filter: ".ep",//除去不能拖拽的,这里是个class类名
        });
        //不允许拆卸连接,不设置的话默认是可以的
        instance.importDefaults({ 
          ConnectionsDetachable:false
        });

4、连线监听事件(拖动connection 事件)

        // 监听拖动connection 事件,判断是否有重复链接
        instance.bind("beforeDrop", function(info) {
            // info.connection.getOverlay("label").setLabel(info.connection.id);
            // 判断是否已有该连接
            let isSame = true;
            //下边的forEach循环就是处理数据结构里的connections不能自己跟自己连线。当然你也可以处理其他
            _this.chartData.connections.forEach(item => {
              if ((item.targetId === info.targetId && item.sourceId === info.sourceId) ||  (item.targetId === info.sourceId && item.sourceId === info.targetId)) {
                isSame = false;
              }
            });
            if (isSame) {
                //允许连线后处理的情况
            } else {
              alert("不允许重复连接!");
            }
            return isSame;//这里返回了ture就会自定进行连线。
        });

5、上图实现的完整代码

下边代码就是实现上图的,需要指出的是运用了vue,但是里面掺杂了jquery,和jquery-ui,其实不想用这2个的,但是项目紧,之前项目也用到了,所以就延续了。还有就是上面代码是我自己的测试代码,写的可能有些杂乱,就是测试一个一个功能而写,写的有点乱。

还有一个想说的就是之前想实现,缩放,引入了panzoom.js,流程图也实现了滚动鼠标放大放小,但是有个问题就是滚动鼠标放大放小后如果拖动单个元素或者连线,你就会发现鼠标点对不齐了,这点还没有解决,如果有好的方案,可以告知我下。Toolkit Edition(付费版)的这些功能都有,就不会出现这样的问题。

<template>
  <div id="test6" style="height:100%;position:relative">
      <section id="focal"  style="position:relative;overflow:hidden;width:610px;height:610px;background:#fff;border:1px solid red">
        <div class="parent" id="parent" style="height:100%;">
          <div class="panzoom" id="panzoom" style="border:1px solid blue;width:6000px;height:6000px; transform:translate(-50%, -50%);position:absolute;">
            <div class="box" :id="item.id" :style="{'top':item.nodeStyle.top+'px','left':item.nodeStyle.left+'px'}" v-for="item in chartData.nodes" :key="item.id">
                <i :class="item.icon" class="oldIcon" :title="item.text"></i>
                <i class="el-icon-circle-close" style="display:none" :title="item.text" :id="item.id"></i>
                <div class="ep"></div>
            </div>
          </div>
      </div>
    </section>
    <div class="source">
        <ul>
            <li v-for="(item,index) in list" :id="item.id" :key="index" class="sourceLi" :disabled="true" :data-icon="item.icon" :data-text="item.text" :data-type="item.type">{{item.text}}</li>
        </ul>
    </div>

        <el-dialog
              title="修改label名称"
              :visible.sync="dialogVisible"
              width="30%"
              :before-close="handleClose">
              <el-input v-model="labelName" placeholder="请输入"></el-input>
              <span slot="footer" class="dialog-footer">
                <el-button @click="dialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="changeNote">确 定</el-button>
              </span>
            </el-dialog>
  </div>
</template>
<script>
import ChartNode from "@/components/ChartNode";
export default {
  name: "test6",
  data() {
    return {
        dialogVisible:false,
        labelName:"",
        curSourceId:'',
        curTargetId:'',
        addLabelText:'',//拖拽后生成的连线label文字
        jsp:null,
        myscale:1,
        curScreen:[],//当前屏幕宽高
        chartData: {
            nodes: [],
            connections: [],//{ "targetId": "box2", "sourceId": "box1" }
            props: {},
            screen:[610,610]//提交屏幕宽高
        },
        list: [
          {
            icon: "el-icon-goods",
            text: "伴随车牌",
            type: "circle",
            id:'li1'
          },
          {
            icon: "el-icon-bell",
            text: "常住人口筛选",
            type: "diamond",
            id:"li2"
          },
          {
            icon: "el-icon-date",
            text: "伴随imsi",
            type: "circle",
            id:"li3"
          }
        ]
    };
  },
  mounted() {
  
    let _this = this
    jsPlumb.ready(function() {
        var $section = $('#focal');
        var $panzoom = $section.find('.panzoom').panzoom({ 
             minScale: 0.3,
             maxScale:2,
             eventNamespace: ".panzoom",
             $zoomRange: $(".jtk-endpoint"),
             $set: $section.find('.jtk-overlay'),
             eventsListenerElement: document.querySelector('.box')
        });

        $(document).on('mouseover','.box,.jtk-draggable,.jtk-overlay,.ep',function(){
          $('.panzoom').panzoom("disable");
        })

        $(document).on('mouseleave','.box,.jtk-draggable,.jtk-overlay,.ep',function(){
          $('.panzoom').panzoom("enable");
        })

        let instance = jsPlumb.getInstance({
                PaintStyle:{ 
                    strokeWidth:2, 
                    stroke:"#567567", 
                },
                // Connector: ["Straight", { stub: [0,0], gap:[-30,-30] }],
                Connector:[ "Straight", { curviness: 0 } ],
                Endpoint: ["Blank",{ cssClass: "chart-dot", hoverClass: "chart-dot-hover", radius: 5 }],
                EndpointStyle : { fill: "blue"  },
                HoverPaintStyle:{
                    stroke:"red", 
                },
                DragOptions: { cursor: "pointer", zIndex: 2000 },
                ConnectionOverlays: [
                  [
                    "Arrow",
                    {
                      location: 1,
                      visible: true,
                      width: 11,
                      length: 11,
                      id: "ARROW",
                      events: {
                        click: function() {
                          alert("you clicked on the arrow overlay");
                        }
                      }
                    }
                  ],
                  ["Label", { label: "", id: "label", cssClass: "aLabel" }]
                ],
                Container: "panzoom"
            })
        _this.jsp = instance;
        //拖拽功能
        var els = document.querySelectorAll(".box");
        instance.draggable(els,{
           containment:true,
           filter: ".ep",//除去不能拖拽的
           grid:[50,50]
        });
        //不允许拆卸连接,不设置的话默认是可以的
        instance.importDefaults({ 
          ConnectionsDetachable:false
        });
        // 监听拖动connection 事件,判断是否有重复链接
        instance.bind("beforeDrop", function(info) {
            // info.connection.getOverlay("label").setLabel(info.connection.id);
            console.log(info);
            // 判断是否已有该连接
            let isSame = true;
            _this.chartData.connections.forEach(item => {
              if ((item.targetId === info.targetId && item.sourceId === info.sourceId) ||  (item.targetId === info.sourceId && item.sourceId === info.targetId)) {
                isSame = false;
              }
            });
            if (isSame) {
                _this.addLabelText = "新label"
                _this.chartData.connections.push({
                    sourceId: info.sourceId,
                    targetId: info.targetId,
                    label:_this.addLabelText
                });
            } else {
              alert("不允许重复连接!");
            }
            return isSame;
        });

            var initNode = function(el) {
                     instance.draggable(el, {
                      // containment: true,
                      start(params) {
                        // 拖动开始
                        // console.log(params);
                      },
                      drag(params) {
                        // 拖动中
                        // console.log(params);
                      },
                      stop(params) {
                        // 拖动结束
                        console.log(params);
                        let id = params.el.id;
                        _this.chartData.nodes.forEach(item => {
                          if (item.id === id) {
                            item.nodeStyle.left = params.pos[0];
                            item.nodeStyle.top = params.pos[1] ;
                          }
                        });
                      }
                    });
                    instance.makeSource(el, {
                        filter: ".ep",
                        anchor: ["Perimeter", { shape: "Rectangle" }],
                        // anchor: ["Perimeter", { shape: "Dot" }],
                        connectorStyle: {
                            stroke: "#5c96bc",
                            strokeWidth: 2,
                            outlineStroke: "transparent",
                            outlineWidth: 4
                        },
                        extract: {
                            action: "the-action"
                        },
                        maxConnections: -1,
                        onMaxConnections: function(info, e) {
                            alert("Maximum connections (" + info.maxConnections + ") reached");
                        }
                    });
                    instance.makeTarget(el, {
                      dropOptions: { hoverClass: "dragHover" },
                      anchor: ["Perimeter", { shape: "Rectangle" }],
                      allowLoopback: false
                    });

                    // instance.fire("jsPlumbDemoNodeAdded", el);
                };
            //初始化遮罩层   
            var init = function(connection) {
                if(_this.addLabelText){
                    connection.getOverlay("label").setLabel(_this.addLabelText);
                }else{
                     connection.getOverlay("label").setLabel('编辑');
                }
                $(connection.getOverlay("label").canvas).attr('mySourceId',connection.sourceId)
                $(connection.getOverlay("label").canvas).attr('myTargetId',connection.targetId)
            };

  // 将模块拖入画板中
      $(".sourceLi").draggable({
        scope: "plant",
        helper: "clone",
        opacity: 0.7,
        containment: $("#test1")
      });

      $("#panzoom").droppable({
        scope: "plant",
        drop: function(ev, ui) {
          console.log(ev, ui);
          let helper = ui.helper;
          let id = jsPlumbUtil.uuid();
          let item = {
            id,
            icon: helper.attr("data-icon"),
            type: helper.attr("data-type"),
            text: helper.attr("data-text"),
            nodeStyle: {
              top: ui.offset.top - $("#panzoom").offset().top ,
              left: ui.offset.left - $("#panzoom").offset().left 
            }
          };
          console.log(ui.position)
          _this.chartData.nodes.push(item);
          _this.$nextTick(() => {
            initNode(id);
          });
        }
      });

         instance.batch(() => {

                jsPlumb.getSelector(".box").forEach(item => {
                    console.log(item)
                    initNode(item);
                });

                instance.bind("connection", function(connInfo, originalEvent) {
                    init(connInfo.connection);
                    //显示删除按钮
                    $(connInfo.connection.getOverlay("label").canvas).hover(function() {
                        $(this).append('<div class="x" style="position: absolute;">X</div>');
                    }, function() {
                        $(this).find(".x").stop().remove();
                    })
                    //删除连接
                    $(connInfo.connection.getOverlay("label").canvas).on('click','.x',function(){
                        console.log("shanchu")
                        let _connections = _this.chartData.connections;
                        _connections.forEach((val,index)=>{
                            if(val.targetId == connInfo.connection.targetId && val.sourceId == connInfo.connection.sourceId){
                                _connections.splice(index,1)
                            }
                        })
                        instance.deleteConnection(connInfo.connection);
                        $('.panzoom').panzoom("enable");//这个是为了杜绝删除前的禁止拖拽事件
                    })
                    //label双击事件
                    $(connInfo.connection.getOverlay("label").canvas).on("dblclick",function(conn, connInfo){
                        let _allConnections = _this.jsp.getAllConnections();
                        _this.dialogVisible = true
                        _this.curSourceId = $(conn.target).attr('mySourceId')
                        _this.curTargetId = $(conn.target).attr('myTargetId')
                        _allConnections.forEach((val,index)=>{
                            if(val.targetId == $(conn.target).attr('myTargetId') && val.sourceId == $(conn.target).attr('mySourceId')){
                                _this.labelName =  val.getOverlay('label').label
                            }
                        })
                    })               
                });
        });
        instance.fire("jsPlumbDemoLoaded", instance);
        $(document).on("dblclick",".box",function(){
            $(this).find(".oldIcon").css('display','none')
            $(this).find('.el-icon-circle-close').css('display','inline-block')
        })
        $(document).on("click",".el-icon-circle-close",function(){
            let _note = _this.chartData.nodes
            let _id = $(this).attr("id")
            let _connections = _this.chartData.connections;
            let _allConnections = instance.getAllConnections();
            _this.chartData.connections = _connections.filter((val)=>{
                return (val.targetId != _id && val.sourceId != _id)
            })
            _note.forEach((val,index)=>{
                if(val.id == _id){
                    _note.splice(index,1)
                }
            })
              _allConnections.forEach((val,index)=>{
                if(val.targetId == _id || val.sourceId == _id){
                    instance.deleteConnectionsForElement(_id)
                }
            })
        })
            _this.handleClickTemp(1)
      
});

  },
  methods:{
        myclick(){
            alert("myclickmyclickmyclickmyclick")
        },
        // 初始化node节点
        initNode(el) {
          // initialise draggable elements.
          // 元素拖动,基于 katavorio.js 插件
          let _self = this;
          this.jsp.draggable(el, {
            // containment: true,
            start(params) {
              // 拖动开始
              // console.log(params);
            },
            drag(params) {
              // 拖动中
              // console.log(params);
            },
            stop(params) {
              // 拖动结束
              console.log(params);
              let id = params.el.id;
              _self.chartData.nodes.forEach(item => {
                if (item.id === id) {
                  item.nodeStyle.left = params.pos[0]
                  item.nodeStyle.top = params.pos[1]
                }
              });
            }
          });

          this.jsp.makeSource(el, {
            filter: ".ep",
            // anchor: "Continuous",
            anchor: ["Perimeter", { shape: "Rectangle" }],
            connectorStyle: {
              stroke: "#5c96bc",
              strokeWidth: 2,
              outlineStroke: "transparent",
              outlineWidth: 4
            },
            extract: {
              action: "the-action"
            },
            maxConnections: -1,
            onMaxConnections: function(info, e) {
              alert("Maximum connections (" + info.maxConnections + ") reached");
            }
          });

          this.jsp.makeTarget(el, {
            dropOptions: { hoverClass: "dragHover" },
            anchor: ["Perimeter", { shape: "Rectangle" }],
            allowLoopback: false
          });

          // this is not part of the core demo functionality; it is a means for the Toolkit edition's wrapped
          // version of this demo to find out about new nodes being added.
          //
          this.jsp.fire("jsPlumbDemoNodeAdded", el);
        },
        handleClickTemp(key) {
          this.chartData = {
            nodes: [],
            connections: [],
            props: {}
          };
          this.jsp.empty("panzoom");
          if (key) {
            let url = "/static/json/" + 1 + ".json";
            this.$axios
              .get(url)
              .then(resp => {
                console.log(resp);
                
                let _data = resp.data
                let _reloatScreen = _data.screen
                let _scale = $("#focal").width() / _data.screen[0]
                let _focalWidth = $("#focal").width()
                let _focalHeight = $("#focal").height()
                let _panzoomWidth = $("#panzoom").width()
                debugger
                _data.nodes.forEach((val,index)=>{
                    val.nodeStyle.left = parseInt(val.nodeStyle.left) * _scale - (_panzoomWidth*_scale-_panzoomWidth)/2 
                    val.nodeStyle.top  = parseInt(val.nodeStyle.top) * _scale - (_panzoomWidth*_scale-_panzoomWidth)/2 
                })
                // $("#panzoom").css({'width':_panzoomWidth*_scale+'px','height':_panzoomWidth*_scale+'px'})
                this.chartData = _data;
                this.$nextTick(() => {
                  this.chartData.nodes.forEach(item => {
                    this.initNode(item.id);
                  });
                  this.chartData.connections.forEach(item => {
                    let _connects = this.jsp.connect({
                      source: item.sourceId,
                      target: item.targetId
                    });
                     _connects.getOverlay("label").setLabel(item.label)
                     $(_connects.getOverlay("label").canvas).attr('mySourceId',item.sourceId)
                     $(_connects.getOverlay("label").canvas).attr('myTargetId',item.targetId)
                  });
                });
              })
              .catch(err => {
                console.log(err);
              });
          } else {
            this.$nextTick(() => {
              this.chartData.nodes.push({
                id: "start",
                icon: "el-icon-loading",
                type: "circle",
                text: "开始",
                nodeStyle: {
                  top: "100px",
                  left: "300px"
                }
              });
              this.$nextTick(() => {
                this.jsp.batch(() => {
                  this.initNode(jsPlumb.getSelector("#start"));
                });
              });
            });
          }
        },
        changeNote(){//修改label
            if(!this.labelName){
                alert("名称没有填写")
                return false
            }
            let _allConnections = this.jsp.getAllConnections();
            _allConnections.forEach((val,index)=>{
                if(val.sourceId == this.curSourceId && val.targetId == this.curTargetId ){
                    val.getOverlay("label").setLabel(this.labelName)
                }
            })
            this.chartData.connections.forEach(val => {
                if(val.sourceId == this.curSourceId && val.targetId == this.curTargetId ){
                    val.label = this.labelName
                }
           });
           this.dialogVisible = false
        },
        handleClose(){
            this.dialogVisible = false
        }

  },
  components: {
    ChartNode
  }
};
</script>

<style lang="scss" scoped>
#test1{
  position:relative;
  width:90%;
  height:90%;
  border:1px solid #ddd;
  background:#fff;
}
.box{
    border-radius:50%;
    text-align: center;
    cursor: pointer;
  background-color: white;
  border: 1px solid #346789;
  text-align: center;
  z-index: 24;
  cursor: pointer;
  box-shadow: 2px 2px 19px #aaa;
  -o-box-shadow: 2px 2px 19px #aaa;
  -webkit-box-shadow: 2px 2px 19px #aaa;
  -moz-box-shadow: 2px 2px 19px #aaa;

  position: absolute;
  color: black;
  padding: 0.5em;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-transition: -webkit-box-shadow 0.15s ease-in;
  -moz-transition: -moz-box-shadow 0.15s ease-in;
  -o-transition: -o-box-shadow 0.15s ease-in;
  transition: box-shadow 0.15s ease-in;
    .ep {
        opacity: 0;
        position: absolute;
        right: -10px;
        top: 0;
        width: 10px;
        height: 10px;
        background: #409eff;
        border-radius: 5px;
      }
      &:hover {
        .ep {
          opacity: 1;
        }
      }
      &.dragHover {
        .ep {
          opacity: 0;
        }
      }
  }

.box:hover {
  border: 1px solid #123456;
  box-shadow: 2px 2px 19px #444;
  -o-box-shadow: 2px 2px 19px #444;
  -webkit-box-shadow: 2px 2px 19px #444;
  -moz-box-shadow: 2px 2px 19px #fff;
  opacity: 0.9;
}
.box:hover,
.box.jtk-source-hover,
.box.jtk-target-hover {
  border: 1px solid orange;
  color: orange;
}

.box1{
  top:50px;
  left:50px;
}
.box2{
  top:160px;
  left:250px;
}
.box3{
  top:360px;
  left:150px;
}
.box4{
  top:350px;
  left:450px;
}


.chart-dot-hover{
  display: block;
  background: red
}

.source{
    position:absolute;
    top:50px;
    right:50px;
    border:1px solid red;
    width:200px;
    height:300px;
    li{
        line-height:36px;
        border:1px solid #ddd;
        margin-bottom:10px;
        cursor:pointer
    }
}
</style>

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

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,912评论 2 89
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • 怎么切洋葱而不会流泪呢?放在水里切就不会流泪了。 一.洋葱先与兔子姑娘 洋葱先生对兔子姑娘一见钟情。兔子姑娘白净乖...
    一只游荡成精的猫阅读 659评论 0 49
  • 杨倩,焦点讲师三期,漯河,坚持分享362天,(2017-12-27) 做咨询时清空自己很重要,不要预设...
    温心怡然阅读 136评论 0 0
  • 有个女孩美艳,她每一次出场都有女王范,走路带风,回头率高。她是人见人爱,花见花开的万人迷,但是她形势逼人,更让人无...
    飘絮柳阅读 332评论 2 1