【进阶】文件上传

导航:
FormData
XMLHttpRequest
上传前预览图片和文件
上传进度信息
下载进度信息
<input type='file'>
new Image()
最后: 完整示例

(1) FormData()对象

提供一种表示表单数据的键值对构造方式,即用键值对来模拟一系列表单控件
( 即: 把form中所有表单元素的 name 和 value 组装成一个queryString )

  • 特点:相对于普通的ajax,使用FormData最大的优点是可以 - 异步上传二进制文件
  • 如果表单的 enctype 属性设置为 multipart/form-data,则会使用表单的submit()方法来发送数据,即会使用和表单一样的格式
  • 可以使用XMLHttpRequest的 send() 方法来异步的提交这个"表单"

FormData对象如何使用?

  1. FormData对象的方法都在原型链条上,自身没有任何属性和方法


    FormData对象的操作方法都在原型链上
const formData = new FormData()  ----------- FormData构造函数生成一个新的 FormData 对象

formData.append('name', 'wang')  ----------- 添加数据
//通过append(key, value)来添加数据,如果 key不存在则会新增一条数据,如果key存在,则添加到数据的末尾

formData.get('name')  ---------------------- 获取数据,获取key=name的第一个值
formData.getAll('name') -------------------- 获取key=name的所有值

formData.set('name', 'zhang') -------------- 修改数据

formData.has('name') ----------------------- 是否存在

formData.delete('name') -------------------- 删除数据

https://segmentfault.com/a/1190000006716454
https://blog.csdn.net/zqian1994/article/details/79635413
https://segmentfault.com/a/1190000012327982
https://segmentfault.com/a/1190000004664783

(2) XMLHttpRequest

XMLHttpRequest对象来发送一个Ajax请求,
XMLHttpRequest()构造函数初始化一个 XMLHttpRequest 对象,必须在所有其他方法被调用前调用构造函数。

  • 如何获取response?xhr.responsexhr.responseTextxhr.responseXML
const api = new XMLHttpRequest();


方法:
(1) api.open() -------- 初始化HTTP请求参数(url, http方法等),但并不发送请求!!! 供 send() 方法使用

api.open(method, url, async, username, password)
method参数:是http请求的方法,值包括:GET,POST,HEAD
url参数:请求的地址
async参数:是否异步,默认是true,即异步的发送请求。
           false,同步,对send()方法的调用将阻塞,直到响应完全接收
           true或者省略,异步,且通常需要调用 onreadystatechange() 方法



(2) api.send() -------- 发送一个http请求,请求参数写在send()方法的参数中

api.send(body)
get请求:参数可以直接写在open()方法中
post请求:参数写在该方法中,即body
-- 注意:body参数的数据类型会影响requestHeader中的 Content-Type 的默认值,如何手动指定则会覆盖默认值
如果data是 Document 类型,同时也是HTML Document类型,
  则content-type默认值为text/html;charset=UTF-8;否则为application/xml;charset=UTF-8;
如果data是 DOMString 类型,content-type默认值为text/plain;charset=UTF-8;
如果data是 FormData 类型,content-type默认值为multipart/form-data; boundary=[xxx]
如果data是其他类型,则不会设置content-type的默认值



(3) api.setRequestHeader() -------- 指定一个http请求的头部,只有在readyState 为 1 的时候才能调用

api.setRequestHeader('name', 'value')
name参数:头部名称
value参数:头部的值
注意: setRequestHeader()方法可以多次调用,最终的值不是覆盖override而是追加append
注意: setRequestHeader()方法只有在readyState为1时才能调用,即open()方法之后,send()方法之前



(4) api.getResponseHeader() -------- 返回指定的 HTTP 响应头部的值



(5) api.abort() -------- 取消当前响应,关闭连接并且结束任何未决的网络活动
api.abort()将readystate重置为0
应用:如果请求用了太长时间,而且响应不再必要的时候,可以调用这个方法。
abort:是中止的意思




(6) api.onreadystatechange() -------- 在 readyState 状态改变时触发
api.onreadystatechage() :当 readyState 为 3 时,它也可能调用多次。
注意:onreadystatechange()所有字符都是小写,但readyState又是驼峰写法

readyState状态:
0 UNSENT ---- xhr对象成功构造,open()方法未被调用
1 OPENED ---- open()被调用,send()还未被调用,setRequestHeader()可以被调用
2 HEADERS_RECEIVED ---- send()方法已经被调用, 响应头和响应状态已经返回
3 LOADING ---- 响应体(response entity body)正在下载中,此状态下通过xhr.response可能已经有了响应数据
4 DONE ---- 整个数据传输过程结束,不管本次请求是成功还是失败




(7)api.onload  -------- 请求成功时触发,此时readyState为4
重要:所有请求成功的回调:
  1.除了在api.onreadystatechange指定的回调函数的readyState===4时取值
  2.也可以在api.onload事件中取值,如
    api.onload = function () {
      //如果请求成功
      if(api.status == 200){
        //do successCallback
      }
    }
  3.注意判断api.status===200是有坑的,因为成功时返回的状态码不止有200,下面的写法更靠谱
    靠谱的写法:当http状态码为2xx或304时才认为成功
     api.onload = function () {
        //如果请求成功
        if((api.status >= 200 && api.status < 300) || api.status == 304){
            //do successCallback
        }
      }




(8)api.timeout --------- timeout属性用来设置过期时间
问题1:请求的开始时间怎么确定?是api.onloadstart事件触发的时候,也就是api.send()调用的时候
解析:因为api.open()只是创建了链接,当并没有真正传输数据,只有调用api.send()时才真正开始传输
问题2:什么时候是请求结束?
解析:api.loadend事件触发时结束




(9) 
api.onprogress 下载进度信息
api.upload.onprogress 上传进度信息

api.upload.onprogress = function(e) {
    if ( e.lengthComputable ) {
      const present = e.loaded / e.total * 100;
    }
}





-----------------------------------------------------------------------------------------------
1.
问题1:如何获取response
提供三个属性来获取response:( api.response ) 和 ( api.responseText ) 和 ( responseXML )
api.responseText --- api.responseType='text'、''、不设置时,xhr对象上才有此属性,此时才能调用
api.response --- responseType为""或"text"时,值为"";responseType为其他值时,值为 null


2.
问题2:api.responseType有哪些类型
api.responseType类型有:text, document, json, blob, arrayBuffer

get请求

go() {
      console.log('1111111111');
      const api = new XMLHttpRequest();
      api.open('GET',  ----- 初始http请求参数,请求方式,url, 是否异步
      'http://image.baidu.com/channel/listjson?pn=0&rn=30&tag1=明星&tag2=全部&ie=utf8', true);
      api.responseType = 'text';   ------ 文本格式的响应
      api.timeout = 5000; ---- 请求过期时间
      api.setRequestHeader('Content-type', 'application/json'); ----- 必须在open()后,send()前设置
      api.onreadystatechange = function() { ------ readyState改变时触发
        if ( api.readyState === 4 && this.status === 200) { ---- this指的是api实例
          console.log(JSON.parse(this.responseText)) ------ this.response也能拿到同样的数据
        }
      }
      // 除了在api.onreadystatechange指指定的会调中判断readyState===4,也可以直接在onload中触发
      // 两种方法都可以
      // 只判断200状态码不完善,应该判断 2xx 或者 304 则请求成功
      api.onload = function() { 
        if ( api.status >= 200 && api.status < 300 || api.status === 304 ) {
          console.log(JSON.parse(api.responseText), 'onload在请求成功时触发');
        }
      }
      api.send();  ---- 发送数据
    }

http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html 大家
https://segmentfault.com/a/1190000004322487重要
http://www.w3school.com.cn/xmldom/dom_http.asp
https://blog.csdn.net/harryhare/article/details/80778066四种 post 请求的写法
https://www.cnblogs.com/wgbs25673578/p/5056300.html 可以

(3) <input type="file"/>图片上传

  • type属性:规定input元素的类型
  • multiple属性:上传多张图片或多个文件
  • accept属性:允许上传的文件类型
    --- image/*:上传所有图片类型,注意会影响速度
    --- image/gif,image/jpeg,image/jpg,image/png ---- 直接写出来则不会影响速度
  • 修改默认样式,自带的样式很丑,且后面带有未选择任何文件的文字,选中后的文件名
    思路: 隐藏<input type="file"/>,使用自定义的button通过$refs去触发文件上传,实现自定义显示
  • 上传同一个文件,不会触发change事件,即使该文件做过修改
    思路:文件上传之后,处理完文件,将<input type="file" />的value设置为null,这样下次即使上传的是同一个文件,仍然会触发change事件
  • display: none不占文档空间
  • visibility: hidden占据空间
<template>
  <div class="hello">
    <div>图片上传</div>
    <br>
    <div class="upload-file" @click="upFile">
      <div>+</div>
    </div>
    <span v-for="(item, index) in filesName" :key="index" style="margin-right: 10px;">{{item}}</span>
    <input
      type="file"
      class="input-file"
      ref="input-file"
      @change="upload"
      accept="image/gif, image/jpeg"
      multiple="multiple"
    >
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      filesName: []
    }
  },
  methods: {
    upFile() {
      this.$refs['input-file'].click(); // 点击触发input上的click事件,偷梁换柱,触发文件上传
    },
    upload(e) {
      const files = e.target.files || e.dataTransfer.files;
      if (!files.length) {
        this.filesName = [];
        return;
      }
      console.log(files)
      this.filesName = Array.from(files).map(v => v.name);

      // 走上传接口
      const body = new FormData();
      bocy.append('upload-files', files);
      // ajax send到接口,此处省略
    },
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
.input-file {
  display: none;
}
.upload-file {
  background: rgb(41, 129, 245);
  display: inline-block;
  width: 70px;
  height: 30px;
  line-height: 30px;
  margin: 0 auto;
  border-radius: 4px;
  color: white;
  cursor: pointer;
  user-select: none;
}
</style>

https://segmentfault.com/a/1190000013799753
https://www.cnblogs.com/fozero/p/8835628.html
https://www.jianshu.com/p/c619b81e8f04

(4) <input type='file' />上传前预览图片

读取本地图片,有两种方法:FileReader 和 URL.createObjectURL
-----URL.createObjectURL性能更好

objectURL = URL.createObjectURL(blob)

  • blob:是用来创建 URL 的 ( File对象 ) 或 ( Blob对象 )
    //获取本地图片地址
    getObjectURL(file) {  ---------------- 传入file对象
      let url = null;
      if (window.createObjectURL != undefined) {  ---------- 判断不同浏览器类型
        // basic
        url = window.createObjectURL(file);
      } else if (window.webkitURL != undefined) {
        // webkit or chrome
        url = window.webkitURL.createObjectURL(file);
      } else if (window.URL != undefined) {
        // mozilla(firefox)
        url = window.URL.createObjectURL(file);
      }
      return url;
    }
完整案例:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <div class="up-btn" @click="upBtn">上传图片</div>
    <input
      type="file"
      style="display: none"
      accept="image/jpg, image/png, image/jpeg, image/gif"
      multiple="multiple"
      @change="upImg($event, 'fiel1')"
      ref="input-file"
    />
    <br>
    <div style="color: red">{{errMessage}}</div>
    <div class="imgs-wrap" v-for="(item, index) in imgMessage" :key="index">
      <span><img :src="item.url" alt="" width="200" height="100"></span>
      <span>{{item.name}}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {
      imgMessage: [],
      errMessage: ''
    }
  },
  methods: {
    upBtn() {
      this.$refs['input-file'].click()
    },
    upImg(e, type) {
      const files = e.target.files || e.dataTransfer.files;
      this.localImg(files);
    },
    localImg(files) {
      this.errMessage = ''; // 清除错误信息
      const imgs = Array.from(files)
      .map((item, index) => {
        // 先过滤掉重复上传的图片
        if ( this.imgMessage.map(item => item.name).includes(item.name) ) {
          this.errMessage = '有重复项';
          return;
        }
        // 本地图片url
        const localImgUrl = this.getLocalImgUrl(item);
        return {
          name: files[index].name,
          url: localImgUrl,
        }
      })
      .filter(Boolean); // 过滤掉在map中return回来的undefine
      this.imgMessage = this.imgMessage.concat(imgs);
    },
    // 获取上传前图片url
    getLocalImgUrl(file) {
      let localImgUrl = null;
      if ( window.createObjectURL != undefined ) {
        localImgUrl = window.createObjectURL(file);
      } else if ( window.webkitURL != undefined ) {
        localImgUrl = window.webkitURL.createObjectURL(file);
      } else if ( window.URL != undefined ) {
        localImgUrl = window.URL.createObjectURL(file);
      }
      return localImgUrl;
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.up-btn {
  display: inline-block;
  margin: 20px 10px auto;
  padding: 6px 20px;
  border-radius: 4px;
  cursor: pointer;
  user-select: none;
  color: white;
  background: rgb(255, 0, 221);
}
.up-btn:hover {
  background: rgb(133, 2, 115);
}
.imgs-wrap {
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
}
</style>


QQ截图20190309185701.jpg

https://blog.csdn.net/weixin_38023551/article/details/78318532
https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL
https://www.cnblogs.com/stephenykk/p/3558887.html

(5) 上传进度信息和下载进度信息

onprogress事件用来返回进度信息

  • xhr.onprogress = updateProgress; -------------------------- 下载进度信息
  • xhr.upload.onprogress = updateProgress; ---------------- 上传进度信息
api.upload.onprogress=function (e){
  if(e.lengthComputable){
     var precent=100 * e.loaded/e.total;
     console.log(precent);
  }
}


解析:
1. e.total -------------------- 传输的总字节
2. e.loaded ------------------- 已经传输的字节
3. e.lengthComputable --------- 指定上传总数居的大小(上传文件总大小已知)
4. e.transferSpeed ------------ 传输速度
5. e.timeRemaining ------------ 剩余时间

(5) blob对象

blob对象表示一个不可变的,原始数据的类文件对象。File接口基于Blob,Blob对象可以看作是存放二进制数据的容器

##### new Blob(dataArr:Array<any>, opt:{type:string});

第一个参数:是要添加到Bolb对象的数据数组,注意:是一个数组
第二个参数:配置对象
  • 构造函数:Blob()
  • 返回一个新创建的 Blob 对象,其内容由参数中给定的数组串联组成
  • 属性:
    Blob.size:blob中包含数据的大小
    Blob.type:一个字符串,表明该Blob的对象所包含数据MIME类型

http://www.cnblogs.com/hhhyaaon/p/5928152.html 应用场景( 分片上传 )
https://blog.csdn.net/qq_42842709/article/details/82500029

(6) new Image() ----- 用于生成 HTMLImageElement 实例

参数:new Image(width, height) ---- 接受两个参数,分别表示宽度和高度
属性:src,currentSrc,alt,srcset,sizes
注意:用js生成的img实例,并不在文档中,需要手动插入
事件:

  • onload:图像加载完成,会触发onload属性指定的回调函数
  • onerr0r:图像加载完成,同时也会触发onerror属性指定的回调函数

注意事项:
生成的image一定要手动插入到文档,onload和onerror的回调都要指定

  mounted() {
    const limg = require('../images/1.jpg');
    const img = new Image(200, 200);   ------------- 参数分别是 width 和 height
    img.src = limg;    ---------------- 除了src,还有currentSrc表示当前src,因为src可以动态指定
    img.onload = function() {
      console.log('加载完成');
      document.body.appendChild(img);   -------------- 插入文档
    }
    img.onerror = function() {
      console.log('错误')
    }
  }






完整示例

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <div class="up-btn" @click="upBtn">上传图片</div>
    <input
      type="file"
      style="display: none"
      accept="image/jpg, image/png, image/jpeg, image/gif"
      multiple="multiple"
      @change="upImg($event, 'fiel1')"
      ref="input-file"
    />
    <br>
    <div style="color: red">{{errMessage}}</div>
    <div>上传进度:{{persent}}</div>
    <div class="imgs-wrap" v-for="(item, index) in imgMessage" :key="index">
      <span><img :src="item.url" alt="" width="200" height="100"></span>
      <span>{{item.name}}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {
      imgMessage: [],
      errMessage: '',
      persent: 0,
    }
  },
  methods: {
    upBtn() {
      this.$refs['input-file'].click()
    },
    upImg(e, type) {
      const files = e.target.files || e.dataTransfer.files;
      this.viewLocalImages(files);
      this.uploadAllImg(files);
    },
    // 获取上传前图片url
    getLocalImgUrl(file) {
      let localImgUrl = null;
      if ( window.createObjectURL != undefined ) {
        localImgUrl = window.createObjectURL(file);
      } else if ( window.webkitURL != undefined ) {
        localImgUrl = window.webkitURL.createObjectURL(file);
      } else if ( window.URL != undefined ) {
        localImgUrl = window.URL.createObjectURL(file);
      }
      return localImgUrl;
    },
    // 预览上传前图片
    viewLocalImages(files) {
      this.errMessage = ''; // 清除错误信息
      const imgs = Array.from(files)
      .map((item, index) => {
        // 先过滤掉重复上传的图片
        if ( this.imgMessage.map(item => item.name).includes(item.name) ) {
          this.errMessage = '有重复项';
          return;
        }
        // 本地图片url
        const localImgUrl = this.getLocalImgUrl(item);
        return {
          name: files[index].name,
          url: localImgUrl,
        }
      })
      .filter(Boolean); // 过滤掉在map中return回来的undefine
      this.imgMessage = this.imgMessage.concat(imgs);
    },
    // 上传图片
    uploadAllImg(files) {
      // 构造表单
      const form = new FormData();
      Array.from(files).forEach((item, index) => {
        form.append(`file${index}`, item)
      });
      // ajax
      const api = new XMLHttpRequest();
      api.open('POST', '............cmsiw/attachment/upload', true);
      // api.setRequestHeader()必须在open()之后,send()之前
      api.setRequestHeader('Authorization', 'Bearer .....');
      // api.setRequestHeader('Content-type', 'application/json;charset=utf-8');
      api.responseType = 'text';
      api.timeout = 10000;
      // 上传进度
      const that = this;
      api.upload.onprogress = function(e) {
        if (e.lengthComputable) {
          const persent = e.loaded / e.total * 100;
          console.log(persent);
          that.persent = persent;
        }
      }
      // 上传成功
      api.onreadystatechange = function() {
        if (api.readyState === 4 && api.status === 200 ) { // 200用最好 >=200 && <300 || 304代替
          console.log(JSON.parse(api.responseText));
        }
      }
      api.send(form);
    }
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.up-btn {
  display: inline-block;
  margin: 20px 10px auto;
  padding: 6px 20px;
  border-radius: 4px;
  cursor: pointer;
  user-select: none;
  color: white;
  background: rgb(255, 0, 221);
}
.up-btn:hover {
  background: rgb(133, 2, 115);
}
.imgs-wrap {
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
}
</style>

QQ截图20190309202243.jpg

复习: 2019/3/22

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script>
    window.onload = function() {
      const fileDom = document.getElementById('file');
      // 本地预览
      fileDom.addEventListener('change', function(e) {
        const files = e.target.files;
        const file = e.target.files[0];
        console.log(files);
        // 插入名字
        const imgName = Array.from(files)[0].name;
        const imgNameDom = document.createElement('span')
        imgNameDom.innerHTML = imgName;
        document.getElementById('content').appendChild(imgNameDom);
        // 插入图片
        Array.from(files).forEach(item => {
          let url = null;
          if (window.createObjectURL) {
            url = window.createObjectURL(item);
          } else if ( window.URL) {
            url = window.URL.createObjectURL(item);
          } else if ( window.webkitURL) {
            url = window.webkitURL.createObjectURL(item);
          }
          const img = new Image(40, 40);
          img.src = url;
          img.onload = function() {
            document.getElementById('content').appendChild(img);
          }
        })
      }, false)
    };
  </script>
</head>
<body>
  <div id="file-wrap" style="position: relative">
    <div
      style="
        cursor: pointer;
        background:blueviolet;
        color: white;
        width: 100px;
        text-align: center;
        margin: 0 auto;
        border-radius: 8px;
        height: 30px;
        line-height: 30px;
    ">上传</div>
    <input
      type="file"
      multiple="multiple"
      accept="image/*"
      style="
        opacity: 0;
        position: absolute;
        border: 1px solid red;
        z-index: 99999;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        height: 30px;
        width: 100px;
        "
        id="file"
    >
  </div>
  <br>
  <div id="content" style="width: 600px; height: 600px; border: 1px solid black; margin: 0 auto">
  </div>
</body>
</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352

推荐阅读更多精彩内容