从MVC到MVVC

vue.js网址:https://cn.vuejs.org/v2/guide/
Vue就是用来代替MVC中的View的。
MVVC = MVC + 双向绑定

课程简介


从 MVC 到 MVVM
预习:Vue 官方文档

代码

MVC 版代码:http://jsbin.com/dujutov/1/edit?js,output
Vue 版代码:http://jsbin.com/dujutov/2/edit?html,js,output
Vue 浮层例子:http://jsbin.com/nabugot/1/edit?js,output
Vue 轮播例子:http://jsbin.com/liqicir/2/edit?js,output
Vue tab切换例子:http://jsbin.com/hijawuv/1/edit?html,css,output

睁大眼睛看清楚我用的是 jsbin.com 不是 js.jirengu.com,用 js.jirengu.com 有 bug 我才改成 jsbin.com 的。

用 Vue 代替 View

双向绑定

引入axios

  1. 复习一下MVC

  2. 我们做一个书籍展示的页面,需要引入axios这个库(https://github.com/axios/axios

  3. axios是一个ajax的库,是一个基于Promise的Http客户端。

    1. axios相对于jQuery的好处是除了get和post,还有put,patch,delete等,其用法几乎是照抄jQuery,但是好处是支持更多的API,
    2. 除了ajax功能之外就没有其余功能了,也就是更加专注。
  4. 以前我们使用jQuery,发现jQuery有一个问题,就是所有功能都混在一起,即ajax相关使用.ajax(),而dom相关也是用。现在我们想将ajax和dom操作分开,使用ajax的时候就使用axios,谁用dom操作的时候就使用vue

    image.png

  5. 所以,后面基本不适用jQuery了

  6. 我们做一个图书库存管理的系统,现在以简单的样式实现


    image.png
  7. 我们的数据库最好是从数据库里面拿,axios比jQuery好的一个点在于支持自己给自己造数据,即可以模拟服务器返回响应,其中interceptor就是拦截机的意思,其作用就会在真正返回response之前使用一个函数,这个函数会对response进行修改。


    image.png
  8. 上面这个操作就是mock server:为了更好的分工合作,让前端能在不依赖后端环境的情况下进行开发,其中一种手段就是为前端开发者提供一个web容器,这个本地环境就是 mock server。

  9. 我们可以根据不同的url来mock不同的数据,代码如下所示,当axios的请求路径不一样的时候,将得不到返回数据

    axios.interceptors.response.use(function(response){
      // config里面有重要的url,method,data属性,这个data是请求的data
      let {config: {url, method, data}} = response
      data = JSON.parse(data||'{}')
      // 这个row是响应的data
      let row = {
        id: 1, name: 'JavaScript高级程序设计', number: 2
      }
      if(url === '/books/1' && method === 'get'){
        response.data = row
      }else if(url === '/books/1' && method === 'put'){
        response.data = Object.assign(row, data)
      }
      return response
    })
    
    axios.get('/books/1')
      .then((response)=>{
      console.log(response)
    })
    
    image.png
  10. ES6中,支持这种解构语法:let {config: {url, method, data}} = response,下面图片中,第三行等于第一行+第二行


    image.png
  11. 同样,下面第二种比第一种简洁,也是ES6的解构语法

    // 第一种写法
    axios.get('/books/1')
      .then((response)=>{
      let data = response.data
      console.log(data)
    })
    
    // 第二种写法
    axios.get('/books/1')
      .then(({data})=>{
      console.log(data)
    })
    
  12. 我们将拿到的数据重新渲染到页面中,使用替换即可

    axios.interceptors.response.use(function(response){
      // config里面有重要的url,method,data属性,这个data是请求的data
      let {config: {url, method, data}} = response
      data = JSON.parse(data||'{}')
      // 这个row是响应的data
      let row = {
        id: 1, name: 'JavaScript高级程序设计', number: 2
      }
      if(url === '/books/1' && method === 'get'){
        response.data = row
      }else if(url === '/books/1' && method === 'put'){
        response.data = Object.assign(row, data)
      }
      return response
    })
    
    axios.get('/books/1')
      .then(({data})=>{
      let originalHtml = $('#app').html()
      let newHtml = originalHtml.replace('__name__', data.name)
        .replace('__number__', data.number)
      $('#app').html(newHtml)
    })
    
    image.png
  13. 上面最终我们可以看到前面这个拿到的数据会显示在网页中

  14. 委托:但是我们发现一个BUG,发现原先的点击事件起不了作用,这是因为我们在操作$('#app').html(newHtml)这句话的时候,原先里面的button被替换了,我们需要做一个委托,下面这个写法的意思就是在点击app里面的任何一个元素的时候,如果这个元素符合#addOne这个条件,就会执行相关代码,这样就算代码被替换页没有关系,因为app始终是没有动的,只是里面的内容换了罢了

    $('#app').on('click', '#addOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 +1
      $('#number').text(newNumber)
    })
    
  15. 整体的JS代码如下

    axios.interceptors.response.use(function(response){
      // config里面有重要的url,method,data属性,这个data是请求的data
      let {config: {url, method, data}} = response
      data = JSON.parse(data||'{}')
      // 这个row是响应的data
      let row = {
        id: 1, name: 'JavaScript高级程序设计', number: 2
      }
      if(url === '/books/1' && method === 'get'){
        response.data = row
      }else if(url === '/books/1' && method === 'put'){
        response.data = Object.assign(row, data)
      }
      return response
    })
    
    axios.get('/books/1')
      .then(({data})=>{
      let originalHtml = $('#app').html()
      let newHtml = originalHtml.replace('__name__', data.name)
        .replace('__number__', data.number)
      $('#app').html(newHtml)
    })
    
    $('#app').on('click', '#addOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 +1
      $('#number').text(newNumber)
    })
    
    $('#app').on('click', '#minusOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 -1
      $('#number').text(newNumber)
    })
    
    $('#app').on('click', '#reset', function(){
      $('#number').text(0)
    })
    
  16. 到目前为止,我们先使用ajax获取一个很简单的数据,获取到数据之后,将数据替换到html中,同时通过事件委托监听app的点击事件;当我们点击的时候,我们需要发送请求,到后台去改数值,而不是表面上的加一减一。每一次改变number的时候,我们需要将新的number先put到服务器上面去,put成功就在页面中更改值,发送不成功就不要更改值。后端那边也要做一个监听,将put上去的值给服务器。Object.assign(book, data),这个API用于部分更新,只更改对应的部分,可以一次性赋值更改,可以多次覆盖性更改


    image.png

    image.png
  17. 目前为止,我们做了一个模拟后台,三个点击按钮当被点击的时候,就会发送put请求,但是这个代码很麻烦,因为每次都要重复写put请求,这种属于意大利面条式代码,没有太多组织性,总体的代码如下,每次点击的时候会出现延迟更新的状态

    let book = {
      id: 1,
      name: 'JavaScript高级程序设计',
      number: 2
    }
    axios.interceptors.response.use(function(response){
      // config里面有重要的url,method,data属性,这个data是请求的data
      let {config: {url, method, data}} = response
      if(url === '/books/1' && method === 'get'){
        // 这个data是响应的data
        response.data = book
      }else if(url === '/books/1' && method === 'put'){
        Object.assign(book, data)
        response.data = book
      }
      return response
    })
    
    axios.get('/books/1')
      .then(({data})=>{
      let originalHtml = $('#app').html()
      let newHtml = originalHtml.replace('__name__', data.name)
        .replace('__number__', data.number)
      $('#app').html(newHtml)
    })
    
    /* 上面是加了一个假的后台 */
    
    $('#app').on('click', '#addOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 +1
      axios.put('/books/1', {
        number: newNumber
      }).then(()=>{
        $('#number').text(newNumber)
      })
    })
    
    $('#app').on('click', '#minusOne', function(){
      var oldNumber = $('#number').text()
      var newNumber = oldNumber -0 -1
      axios.put('/books/1', {
        number: newNumber
      }).then(()=>{
        $('#number').text(newNumber)
      })
    })
    
    $('#app').on('click', '#reset', function(){
      axios.put('/books/1', {
        number: 0
      }).then(()=>{
        $('#number').text(0)
      })
    })
    

引入MVC


  1. 我们引入MVC,所谓MVC是将其变成三部分。我们写一个model,所有跟数据相关的操作,都用model来实现
  2. 所有跟html相关的操作都用view,来操作
  3. 所有model和View之外的都交给controller来操作。
  4. 最终的代码如下所示:
    fakeData()
    
    let model = {
      data: {
        name: '',
        number: 0,
        id: 1
      },
      fetch: function(){
        return axios.get('/books/1').then((response)=>{
          this.data = response.data
          return response
        })
      },
      update: function(data){
        let id= this.data.id
        return axios.put('/books/1', data).then((response)=>{
          this.data = response.data
          return response
        })
      }
    }
    
    let view = {
      el: '#app',
      template: `
        <div>
          书名:《__name__》
          数量:<span id=number>__number__</span>
        </div>
        <div>
          <button id="addOne">加1</button>
          <button id="minusOne">减1</button>
          <button id="reset">归零</button>
        </div>
      `,
      render(data){
        let html = this.template.replace('__name__', data.name)
          .replace('__number__', data.number)
        $(this.el).html(html)
      }
    }
    
    let controller = {
      init: function(options){
        let {view, model} = options
        this.view = view
        this.model = model
        this.view.render(this.model.data)
        this.bindEvents()
        this.model.fetch(1).then(() => {this.view.render(this.model.data)})
      },
      addOne: function(){
        var oldNumber = $('#number').text()
        var newNumber = oldNumber -0 +1
        this.model.update({number: newNumber}).then(({data})=>{
          this.view.render(this.model.data)
        })
      },
      minusOne: function(){
        var oldNumber = $('#number').text()
        var newNumber = oldNumber -0 -1
        this.model.update({number: newNumber}).then(()=>{
          this.view.render(this.model.data)
        })
      },
      reset: function(){
        this.model.update({number: 0}).then(()=>{
          this.view.render(this.model.data)
        })
      },
      bindEvents: function(){
        $(this.view.el).on('click', '#addOne',this.addOne.bind(this)) 
        $(this.view.el).on('click', '#minusOne', this.minusOne.bind(this))
        $(this.view.el).on('click', '#reset', this.reset.bind(this))
      }
    }
    
    controller.init({view: view, model: model})
    
    /* 上面是加了一个假的后台 */
    
    function fakeData(){
      let book = {
        id: 1,
        name: 'JavaScript高级程序设计',
        number: 2
      }
      axios.interceptors.response.use(function(response){
        // config里面有重要的url,method,data属性,这个data是请求的data
        let {config: {url, method, data}} = response
        if(url === '/books/1' && method === 'get'){
          // 这个data是响应的data
          response.data = book
        }else if(url === '/books/1' && method === 'put'){
          // 传入的data被拆分成一个一个字符串,需要将其放到一起
          data = JSON.parse(data)
          Object.assign(book, data)
          response.data = book
        }
        return response
      })
    }
    
  5. 上面这个代码中,有三个对象,一个对象是Model,一个View,一个是Controller,假设我们有多个页面,如果每个页面都要写这三个对象,那么就会比较麻烦。我们应该创建类,将共同的属性放入class中。或者写构造函数,将公有的东西写到原型中。
  6. 我们将代码进行更好,变成下面这样:
    fakeData()
    
    // 下面是MVC的类
    function Model(options){
      this.data = options.data
      this.resource = options.resource
    }
    Model.prototype.fetch = function(id){
      return axios.get(`/${this.resource}s/${id}`).then((response)=>{
        console.log(response)
        this.data = response.data
        return response
      })
    }
    Model.prototype.update = function(data){
      let id= this.data.id
    
      return axios.put(`/${this.resource}s/${id}`, data).then((response)=>{
        this.data = response.data
        return response
      })
    }
    
    function View({el, template}){
      this.el = el
      this.template = template
    }
    View.prototype.render = function(data){
      let html = this.template
      for(let key in data){
        html = html.replace(`__${key}__`, data[key])
      }
      $(this.el).html(html)
    }
    
    // 下面是MVC对象
    let model = new Model({
      data: {
        name: '',
        number: 0,
        id: ''
      },
      resource: 'book'
    })
    
    let view = new View({
      el: '#app',
      template: `
        <div>
          书名:《__name__》
          数量:<span id=number>__number__</span>
        </div>
        <div>
          <button id="addOne">加1</button>
          <button id="minusOne">减1</button>
          <button id="reset">归零</button>
        </div>
      `
    })
    
    let controller = {
      init: function(options){
        let {view, model} = options
        this.view = view
        this.model = model
        this.view.render(this.model.data)
        this.bindEvents()
        this.model.fetch(1).then(() => {this.view.render(this.model.data)})
    
      },
      addOne: function(){
        var oldNumber = $('#number').text()
        var newNumber = oldNumber -0 +1
        this.model.update({number: newNumber}).then(({data})=>{
          this.view.render(this.model.data)
        })
      },
      minusOne: function(){
        var oldNumber = $('#number').text()
        var newNumber = oldNumber -0 -1
        this.model.update({number: newNumber}).then(()=>{
          this.view.render(this.model.data)
        })
      },
      reset: function(){
        this.model.update({number: 0}).then(()=>{
          this.view.render(this.model.data)
        })
      },
      bindEvents: function(){
        $(this.view.el).on('click', '#addOne',this.addOne.bind(this)) 
        $(this.view.el).on('click', '#minusOne', this.minusOne.bind(this))
        $(this.view.el).on('click', '#reset', this.reset.bind(this))
      }
    }
    
    controller.init({view: view, model: model})
    
    /* 上面是加了一个假的后台 */
    function fakeData(){
      let book = {
        id: 1,
        name: 'JavaScript高级程序设计',
        number: 2
      }
      axios.interceptors.response.use(function(response){
        // config里面有重要的url,method,data属性,这个data是请求的data
        let {config: {url, method, data}} = response
        if(url === '/books/1' && method === 'get'){
          // 这个data是响应的data
          response.data = book
        }else if(url === '/books/1' && method === 'put'){
          // 传入的data被拆分成一个一个字符串,需要将其放到一起
          data = JSON.parse(data)
          Object.assign(book, data)
          response.data = book
        }
        return response
      })
    }
    

引入Vue


  1. 引入Vue,我们先删除自己的view,将里面的let view = new View()改成let view = new Vue()。里面的标记__改成两个大括号,如name改成{{name}}。并且我们需要将model里面的data传到Vue里面来,因为vue需要根据这个data初始化这个template
    image.png
  2. template里面只能有一个根元素,如果有两个根元素,那么特点1:Vue只看第一个,这样原先没有显示的button就出现了


    image.png
  3. Vue就是MVC做了升级,特点2:Vue里面不需要render事件,因为Vue里面自动有一个render机制
  4. 当vue的data里面的数据发生变化的时候,会自动更新,记住,直接更改vue.data = ...是没有用的,需要更改里面的值,这是因为Vue的第三个特点是会将data里面的所有属性升级到当前的view上面,不能改data去改view,应该改data里面的属性,也就是我们找data属性没用了
  5. 以前的更新,最重要的一句话是view.render(midel.data),现在只需要更新view.data,html更新会自动进行
  6. 我们可以将三个属性同时放到一个属性里面,只要这个属性变了就会更新,这个book是view的属性,而不是放到data里面的,Vue会自动将book属性提升到view层面


    image.png
  7. Vue只更改该改的地方,不会整个去刷新页面,而之前的MVC写法,一旦app里面的任意内容发生变化,这个app都会被重新替换。
  8. Vue支持不适用Controller,我们看到我们的Controller最重要的语法是绑定监听,对应到Vue上面就是method,将controller里面所有的函数写到这个method里面,而且bindEvents不需要了,因为vue内置了bindEvents
  9. Vue是不管model的,但是我们省略了所有dom的获取更新操作,这个形成了自动化
  10. 我们还需要进行初始化,Vue里面有一个created属性函数,用于在元素创建成功之后调用,还有一个mount函数,用于挂载成功之后调用。
  11. Vue的好处就是让以前写的代码更智能,让MVC的C合并到Vue里面去
  12. 现在的代码如下所示:
    fakeData()
    
    // 下面是MVC的类
    // 使用Vue之后,M保留了。C被合并到V中了,View使用了Vue
    // 初始化以及事件绑定等操作都放入View中了
    function Model(options){
      this.data = options.data
      this.resource = options.resource
    }
    Model.prototype.fetch = function(id){
      return axios.get(`/${this.resource}s/${id}`).then((response)=>{
        this.data = response.data
        return response
      })
    }
    Model.prototype.update = function(data){
      console.log(data)
      let id= this.data.id
      return axios.put(`/${this.resource}s/${id}`, data).then((response)=>{
        this.data = response.data
        return response
      })
    }
    
    
    
    // 下面是MVC对象
    let model = new Model({
      data: {
        name: '',
        number: 0,
        id: ''
      },
      resource: 'book'
    })
    
    // 使用Vue创建view
    let view = new Vue({
      el: '#app',
      // 放在model里面的数据可以放一份到data中,data里面的属性会自动提升给view
      // 访问的时候使用this.book,而不是this.view.book
      data: {
        book: {
          name: '未命名',
          number: 0,
          id: ''
        },
        n: 1
      },
      // 变量的标识使用两个大括号包围
      template: `
        <div>
          <div>
            书名:《{{book.name}}》
            数量:<span id=number>{{book.number}}</span>
          </div>
          <div>
            <input v-model="n" />
          </div>
          <div>
            <button v-on:click="addOne">加{{n}}</button>
            <button v-on:click="minusOne">减{{n}}</button>
            <button v-on:click="reset">归零</button>
          </div>
        </div>
      `,
      // 这个函数是在el被创建的时候自动调用,可以认为是初始化
      created(){
        model.fetch(1).then(()=>{
          this.book = model.data
        })
      },
      // 里面所有的绑定事件可以放到这个里面。使用v-on:click="方法名"进行调用
      // Vue有一个好处是,只要放在data标识里面的数据被更改了,就会自动渲染更改的部分,而不会更改其他部分
      methods: {
        addOne(){
          model.update({
            number: this.book.number + this.n*1
          }).then(()=>{
            this.view.book = model.data
          })
        },
        minusOne(){
          model.update({
            number: this.book.number - this.n*1
          }).then(()=>{
            this.view.book = model.data
          })
        },
        reset(){
          model.update({
            number: 0
          }).then(()=>{
            this.view.book = model.data
          })
        }
      }
    })
    
    
    
    /* 上面是加了一个假的后台 */
    
    function fakeData(){
      let book = {
        id: 1,
        name: 'JavaScript高级程序设计',
        number: 2
      }
      axios.interceptors.response.use(function(response){
        // config里面有重要的url,method,data属性,这个data是请求的data
        let {config: {url, method, data}} = response
        if(url === '/books/1' && method === 'get'){
          // 这个data是响应的data
          response.data = book
        }else if(url === '/books/1' && method === 'put'){
          // 传入的data被拆分成一个一个字符串,需要将其放到一起
          data = JSON.parse(data)
          Object.assign(book, data)
          response.data = book
        }
        return response
        console.log(response)
      })
    }
    
  13. 使用v-model="n"是将值绑定到data里面的n去。这是一种双向绑定,我们将input的值绑定到data的n值上面去,同时又拿到这个值渲染到这个页面中来,现在我们只需要更改input里面的值,后面的渲染也会随即更改


    image.png
  14. 只有input能实现双向绑定,span是不可以的
    • 我们之前使用span的时候,span能拿到data里面的n进行渲染,当更改内存中n的时候,再次渲染将n的值给span。所有的过程都是单向的,拿到内存中的n,渲染给span
    • 但是当有input的时候,我们第一次拿到n的是给input,这是一个默认值,但是当我们使用v-model="n"将input的value值与n链接起来,使得input的value变化的时候,n也变化,页面中其余引用n的部分也跟着变化,这就是双向绑定。
    • image.png
    • 只从内存到页面,这是单向的,但是如果页面能映射到内存,这就是双向的。
  15. Vue是一种自动化的MVC,又叫MVVM

使用Vue做三个小东西


  1. 我们使用了Vue之后,jQuery就可以不使用了,因为我们已经摆脱了操作DOM。jQuery中的ajax已经用axios代替了,我们现在只需要想数据之间的逻辑就好了,不用花大量精力去处理DOM等。

  2. 我们使用Vue可以很容易就实现一个点击的浮动图层,其中v-if="open"的意思就是当open为true的时候,显示,为false的时候就不显示,toggle函数会更改open的值

    let view = new Vue({
      el: '#app',
      data: {
        open: false
      },
      template: `
        <div>
          <button v-on:click="toggle">点我</button>
          <div v-if="open">你好</div>
        </div>
      `,
      methods:{
        toggle(){
          this.open = !this.open
        }
      }
    })
    
    image.png
  3. 做个轮播,使用了VUE就不用碰DOM,绑定的函数既支持加参数,也支持只写函数名;可以使用v-bind:style绑定一个style属性,

    let view = new Vue({
      el: '#app',
      data: {
        transformValue: ''
      },
      template: `
        <div>
          <div class="window">
            <div class="slides" v-bind:style="{transform:transformValue}"></div>
          </div>
          <button v-on:click="go(1)">1</button>
          <button v-on:click="go(2)">2</button>
          <button v-on:click="go(3)">3</button>
        </div>
      `,
      methods:{
        go(index){
          this.transformValue = `translateX(${-100*(index-1)}px)`
        }
      }
    })
    
    image.png

    如果要做图片切换,需要使用v-for进行循环

  4. 做一个Tab切换,v-show="值",如果值是真的,那么就展示,如果值是false,那么就不展示;v-on:click="selected = 0"这后面的字符串会被解析成公式进行执行,v-bind:class="{active:selected === 0}的意思就是使用bind绑定一个active属性,但是只有在selected === 0的时候,绑定才生效


  5. 最终的代码如下所示

    let view = new Vue({
      el: '#app',
      data: {
        selected: 'a',
        tabs: [
          {name: 'a', content: 'aaa'},
          {name: 'b', content: 'bbb'},
          {name: 'c', content: 'ccc'}
        ]
      },
      template: `
        <div>
          <ol>
            <li v-for="tab in tabs"
              v-on:click="selected = tab.name"
              v-bind:class="{active: tab.name === selected}"
            >{{tab.name}}</li>
          </ol>
          <ol>
            <li v-for="tab in tabs"
              v-show="selected === tab.name"
            >{{tab.content}}</li>
          </ol>
        </div>
      `,
      methods:{
    
      }
    })
    
  6. 点击的时候会出现内容,以及样式会进行更改


    image.png
  7. VUE学起来真的很简单,但是一开始学VUE会不知道怎么实现

  8. 一个很好的Vue模拟网页:https://jsfiddle.net/chrisvfritz/50wL7mdz/

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

推荐阅读更多精彩内容