01vue+axios+formData实现文件上传(包含简单的Java后台)

参考文章:

用纯css美化<input type=file/>按钮
vue+axios实现文件上传简单原理以及操作
萌新用vue + axios + formdata 上传文件的爬坑之路
从0开始做一个的Vue图片/ 文件选择(上传)组件[基础向]
vue中利用axios实现文件上传进度实时更新
Vue实现带进度条的文件拖动上传
spring boot 文件上传
阿里巴巴iconfont怎么是正确的使用方式?

实现效果

效果图

1.用html和css画出文件上传组件

参考文章用纯css美化<input type=file/>按钮并且利用里面的样式实现。
其中文章的要点是:
1.1文件上传使用<input type=file/>
1.2组合label标签和<input type=file/>美化文件上传

label好用在于,它可以跟input的click事件关联上,这就实现了语义化解决方案。因此点击这两个元素的任何一个都能得到相同的结果——弹出文件上传选择对话框。

其中隐藏<input type=file/>标签的css为:

       .inputfile {
            width: 0.1px;
            height: 0.1px;
            opacity: 0;
            overflow: hidden;
            position: absolute;
            z-index: -1;
        }

你可能会好奇为什么宽和高会设成0.1px而不是0px,因为在某些浏览器下0宽高将会让<input>元素被tab键忽略。而position: absolute的目的是不干扰随后元素的位置。

1.3html+css实现静态页面代码

<body>
    <div id="app" class="m-5">
        <div class="uploadBox">
            <h3>上传文件</h3>
            <div class="fileBox">
                <input type="file" id="myFile" class="inputfile" @change="handlerUpload($event)">
                <label for="myFile">
                    <i class="iconfont">&#xe632;</i>点击上传本地文件
                </label>
            </div>
            <div class="fileInfo">
                <ul class="files">
                    <li v-for="(file, index) in files">
                        {{ file.name }}
                    </li>
                </ul>
            </div>
        </div>
    </div>
</body>

1.4实现效果
1.5icon
在静态页面中引用了阿里的icon库。引入方式可以参考:
阿里巴巴iconfont怎么是正确的使用方式?
详细的步骤为:搜索获取适合的图标->加入购物车->添加至项目->下载->将对应iconfont.css复制至css文件夹下引用即可

页面效果

2.构造form'Data,使用axios上传文件

在项目中使用axios上传文件,记得new一个纯净的axios或者考虑用ajax请求。因为axios在项目估计已经用了全局配置请求头等信息,这里的配置可能被全局请求头拦截,导致请求失败。
2.1构造formData

 let param = new FormData();
 param.append("name", "wiiiiiinney");
//通过append向form对象添加数据
 param.append("file", file);
//FormData私有类对象,访问不到,可以通过get判断值是否传进去
 console.log(param.get("file"));

2.2以下为全局请求头的配置

      let config = {
        //添加请求头
        headers: { "Content-Type": "multipart/form-data" },
        //添加上传进度监听事件
        onUploadProgress: e => {
          var completeProgress = ((e.loaded / e.total * 100) | 0) + "%";
          this.progress = completeProgress;
        }
      };

2.2axios发送请求

 axios.post('http://127.0.0.1:8778/upload', param, config).then(
function (response) 
{ console.log(response); })
.catch(function (error) {
 console.log(error);
 });

4.后端接收文件

java接收代码:

@Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 允许cookies跨域
        config.addAllowedOrigin("*");// 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080 ,以降低安全风险。。
        config.addAllowedHeader("*");// 允许访问的头信息,*表示全部
        config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了

        config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等
        /*
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");// 允许Get的请求方法
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        */
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

private static final Logger logger = LoggerFactory.getLogger(Application.class);
    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setLocation("d:/tmp");
        return factory.createMultipartConfig();
    }

    @RequestMapping(value = "/upload")
    @ResponseBody
    public String upload(@RequestParam("file") MultipartFile file,@RequestParam("name")String name) {
        logger.info("name: "+name);
        if (file.isEmpty()) {
            return "文件为空";
        }
        // 获取文件名
        String fileName = file.getOriginalFilename();
        logger.info("上传的文件名为:" + fileName);
        // 获取文件的后缀名
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        logger.info("上传的后缀名为:" + suffixName);
        // 文件上传路径
        String filePath = "d:/roncoo/ttt/";
        // 解决中文问题,liunx 下中文路径,图片显示问题
        //fileName = UUID.randomUUID() + suffixName;
        File dest = new File(filePath + fileName);
        // 检测是否存在目录
        if (!dest.getParentFile().exists()) {
            dest.getParentFile().mkdirs();
        }
        try {
            file.transferTo(dest);
            return "上传成功";
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "上传失败";
    }

6.将上传信息反馈

6.1给每个文件设置一个进度条
用uploadPercentage记录上传进度,uploadPercentage的变化由axios的progress事件监听计算得出。

                        var item = {
                            name: tFiles[i].name,
                            uploadPercentage: 1,
                            size: this.formatFileSize(tFiles[i].size, 0),
                            uploadStatus: 0
                        }
                        console.log(item)
                        this.files.push(item);

6.2给每个文件设置一个上传状态
uploadStatus记录文件状态。

状态码 含义
0 初始状态
1 上传中
2 已上传
-1 服务器错误
-2 上传文件类型不符合要求
-3 上传文件超出限制

6.3检测函数大小的函数

                checkFileSize:function(fileSize) {
                    //2M
                    const MAX_SIZE = 2 * 1024 * 1024;
                    if (fileSize > MAX_SIZE) {
                        return false;
                    }
                    return true;
                }

6.4检测文件类型的函数

                checkFileType: function (fileType) {
                    const acceptTypes = ['xls', 'doc'];
                    for (var i = 0; i < acceptTypes.length; i++) {
                        if (fileType === acceptTypes[i]) {
                            return true;
                        }
                    }
                    return false;
                }

6.4格式化文件大小的函数

               formatFileSize: function (fileSize, idx) {
                    var units = ["B", "KB", "MB", "GB"];
                    idx = idx || 0;
                    if (fileSize < 1024 || idx === units.length - 1) {
                        return fileSize.toFixed(1) +
                            units[idx];
                    }
                    return this.formatFileSize(fileSize / 1024, ++idx);
                },

7.全部代码

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        li {
            list-style: none;
        }

        /*引入阿里的icon*/

        @font-face {
            font-family: "iconfont";
            src: url('iconfont.eot?t=1534844614970');
            /* IE9*/
            src: url('iconfont.eot?t=1534844614970#iefix') format('embedded-opentype'), /* IE6-IE8 */
            url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAATgAAsAAAAAB1AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8qEgkY21hcAAAAYAAAABLAAABcOdatkBnbHlmAAABzAAAASwAAAFY2/C2dWhlYWQAAAL4AAAALwAAADYSY0OKaGhlYQAAAygAAAAcAAAAJAfeA4NobXR4AAADRAAAAAgAAAAICAAAAGxvY2EAAANMAAAABgAAAAYArAAAbWF4cAAAA1QAAAAgAAAAIAEPAH1uYW1lAAADdAAAAUUAAAJtPlT+fXBvc3QAAAS8AAAAIwAAADQ9SsrveJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeGT0zYm7438AQw9zA0AAUZgTJAQDk9QxHeJxjYGBgZWBgYAZiHSBmYWBgDGFgZAABP6AoI1icmYELLM7CoARWwwISf2b0/z+MBPJZwCQDIxvDKOABkzJQHjisIJiBEQCjLgoxAHicHY3BTsJAGIR3drv/0hZaqXVLIKJSugVNQAOUxCKYeNKLMTHRB+Bi4sGTD2B8GV8C4zNxJepC/vxzmMzMxwRjfz/iW1wzn52xN8YQQLWRzDEdQJA60Co1C5gJhSAzLbZ3BB1TZwgjY2XTaceMixK2UZi8Y2u5Iesb+3ZIl9CJppTyAYaYFFObG21XEp3EI30EzlYbKTernX6B89ALAAFuvARwsiwI7sgPyXBo7p6ffLqe737Eh1K2Wxrifn65bKhKFjXbTtpS6rUu4LvlrFcSwL2y59Dpy/vjQMmZB7HYgRxnq78PwDqSMlpbHM9cv6pk1q3Vbin0KeNdomMDr+4h2W/2uF48+2o03kvhVPpF5gChW7WEoi8SonyIaNloPV3cyPoVZ+wfIBIx1XicY2BkYGAA4r3qZgbx/DZfGbhZGEDg+sL/xxD0/4MsDMwOQC4HAxNIFAA6yguGAHicY2BkYGBu+N/AEMPCAAJAkpEBFTABAEcIAmsEAAAABAAAAAAAAAAArAAAAAEAAAACAHEAAwAAAAAAAgAAAAoACgAAAP8AAAAAAAB4nGWPTU7DMBCFX/oHpBKqqGCH5AViASj9EatuWFRq911036ZOmyqJI8et1ANwHo7ACTgC3IA78EgnmzaWx9+8eWNPANzgBx6O3y33kT1cMjtyDRe4F65TfxBukF+Em2jjVbhF/U3YxzOmwm10YXmD17hi9oR3YQ8dfAjXcI1P4Tr1L+EG+Vu4iTv8CrfQ8erCPuZeV7iNRy/2x1YvnF6p5UHFockikzm/gple75KFrdLqnGtbxCZTg6BfSVOdaVvdU+zXQ+ciFVmTqgmrOkmMyq3Z6tAFG+fyUa8XiR6EJuVYY/62xgKOcQWFJQ6MMUIYZIjK6Og7VWb0r7FDwl57Vj3N53RbFNT/c4UBAvTPXFO6stJ5Ok+BPV8bUnV0K27LnpQ0kV7NSRKyQl7WtlRC6gE2ZVeOEXpc0Yk/KGdI/wAJWm7IAAAAeJxjYGKAAC4G7ICJkYmRmYGrOCMxLz05ozQxj4EBACJZBEAA') format('woff'), url('iconfont.ttf?t=1534844614970') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
            url('iconfont.svg?t=1534844614970#iconfont') format('svg');
            /* iOS 4.1- */
        }

        .iconfont {
            font-family: "iconfont" !important;
            font-size: 16px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
        }

        .icon-shangchuan:before {
            content: "\e632";
        }


        .iconfont {
            font-family: "iconfont";
            font-size: 18px;
            font-style: normal;
            -webkit-font-smoothing: antialiased;
            -webkit-text-stroke-width: 0.2px;
            -moz-osx-font-smoothing: grayscale;
            padding-left: 20px
        }

        .uploadBox {
            width: 400px;
            border: 1px solid #ccc;
            margin: 100px auto;
        }

        .fileBox,
        .fileInfo {
            margin: 16px;
            height: 60px;
            line-height: 60px;
            border: 1px solid #ccc;
            padding-left: 16px;
            font-size: 16px;
        }

        .inputfile {
            width: 0.1px;
            height: 0.1px;
            opacity: 0;
            overflow: hidden;
            position: absolute;
            z-index: -1;
        }

        /*E + F 毗邻元素选择器,匹配所有紧随E元素之后的同级元素F*/

        .inputfile+label {
            color: #3e97df;
            display: inline-block;
        }

        .inputfile:focus+label,
        .inputfile+label:hover {
            color: #0c89f0;
        }

        h3 {
            padding: 10px 0 0 16px;
            font-weight: normal;
            font-size: 18px;
            color: #666;
        }

        .filePart {
            line-height: 30px;
            overflow: hidden;
            float: left;
            text-overflow: ellipsis;
            white-space: nowrap;
            font-size: 12px;
            height: 30px;
        }

        .fileStatus {
            overflow: hidden;
            float: left;
            height: 20px;
            font-size: 10px;
            line-height: 20px;
        }

        .ml10 {
            margin-left: 10px;
        }

        .fileName {
            width: 200px;
        }

        .fileSize {
            width: 120px;
            text-align: center;
        }

        .uploadFail {
            color: #ff0800d3;
        }

        .uploadSuccess {
            color: #2c832c;
        }

        /*对应CSS*/

        .progress {
            position: relative;
            width: 80%;
            height: 8px;
            border: 1px solid #ccc;
            border-radius: 5px;
            overflow: hidden;
            /*注意这里*/
            box-shadow: 0 0 1px 0px #ddd inset;
        }

        .progress span {
            position: absolute;
            display: inline-block;
            width: 10%;
            height: 100%;
            background-color: #3e97df;
        }
    </style>
    <title>Document</title>
</head>

<body>
    <div id="app" class="m-5">
        <div class="uploadBox">
            <h3>上传文件</h3>
            <div class="fileBox">
                <input type="file" id="myFile" class="inputfile" @change="handlerUpload($event)">
                <label for="myFile">
                    <i class="iconfont">&#xe632;</i>点击上传本地文件
                </label>
            </div>
            <ul class="files">
                <li v-for="(file, index) in files">
                    <div class="fileInfo">

                        <div class="fileName filePart">
                            {{ file.name }}
                        </div>
                        <div class="fileSize filePart ml10">
                            {{file.size}}
                        </div>
                        <!--进度条-->
                        <div class="progress">
                            <span :style="{width:file.uploadPercentage,backgroundColor:file.uploadStatus==1 ||file.uploadStatus==2?'':'red'}"></span>
                        </div>
                        <div class="fileStatus">
                            <span v-if="file.uploadStatus == -1" class="uploadFail">出错啦,请重新上传或者删除</span>
                            <span v-if="file.uploadStatus == 2" class="uploadSuccess"> 已上传</span>
                            <span v-if="file.uploadStatus == 1" class="uploadSuccess"> 上传中...</span>
                            <span v-if="file.uploadStatus == -2" class="uploadFail">出错啦,文件类型不符合要求</span>
                            <span v-if="file.uploadStatus == -3" class="uploadFail">出错啦,文件大小超出限制</span>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                files: [],
                uploadSuccess: 0
            },
            methods: {
                handlerUpload: function (e) {
                    //获取选定的文件
                    let tFiles = e.target.files;
                    let len = tFiles.length;
                    for (var i = 0; i < len; i++) {
                        //开始上传每一个文件
                        var item = {
                            name: tFiles[i].name,
                            uploadPercentage: 1,
                            size: this.formatFileSize(tFiles[i].size, 0),
                            uploadStatus: 0
                        }
                        console.log(item)
                        this.files.push(item);
                        //开始上传文件 新建一个formData
                        let param = new FormData();
                        param.append("name", "wiiiiiinney");
                        //通过append向form对象添加数据
                        param.append("file", tFiles[i]);
                        //FormData私有类对象,访问不到,可以通过get判断值是否传进去
                        console.log(param.get("file"));
                        //判断大小
                        if (!this.checkFileSize(tFiles[i].size)) {
                            item.uploadStatus = -3;
                            return false;
                        }
                        if (!this.checkFileType(tFiles[i].name.split('.')[1])) {
                            item.uploadStatus = -2;
                            return false;
                        }
                        //通过axios上传文件
                        //配置
                        let config = {
                            //添加请求头 
                            headers: {
                                "Content-Type": "multipart/form-data"
                            },
                            //添加上传进度监听事件 
                            onUploadProgress: e => {
                                var completeProgress = ((e.loaded / e.total * 100) | 0) + "%";
                                console.log(this.files)
                                item.uploadPercentage = completeProgress;
                            }
                        };
                        axios.post('http://127.0.0.1:8778/upload', param, config).then(function (
                            response) {
                            console.log(response);
                            item.uploadStatus = 2;
                        }).catch(function (error) {
                            console.log(error);
                            item.uploadStatus = -1;
                        });
                    }
                },
                formatFileSize: function (fileSize, idx) {
                    var units = ["B", "KB", "MB", "GB"];
                    idx = idx || 0;
                    if (fileSize < 1024 || idx === units.length - 1) {
                        return fileSize.toFixed(1) +
                            units[idx];
                    }
                    return this.formatFileSize(fileSize / 1024, ++idx);
                },
                checkFileType: function (fileType) {
                    const acceptTypes = ['xls', 'doc', 'jpg'];
                    for (var i = 0; i < acceptTypes.length; i++) {
                        if (fileType === acceptTypes[i]) {
                            return true;
                        }
                    }
                    return false;
                },
                checkFileSize: function (fileSize) {
                    //2M
                    const MAX_SIZE = 2 * 1024 * 1024;
                    if (fileSize > MAX_SIZE) {
                        return false;
                    }
                    return true;
                }
            }
        });
    </script>
</body>

</html>

8.参考代码、使用ajax上传文件、实现拖拽功能

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
    <style>
        .dropbox {
            border: .25rem dashed #007bff;
            min-height: 5rem;
        }
    </style>
    <title>Document</title>
</head>

<body>
    <div id="app" class="m-5">
        <div class="dropbox p-3">
            <h2 v-if="files.length===0" class="text-center">把要上传的文件拖动到这里</h2>
            <div class="border m-2 d-inline-block p-4" style="width:15rem" v-for="file in files">
                <h5 class="mt-0">{{ file.name }}</h5>
                <div class="progress">
                    <div class="progress-bar progress-bar-striped" :style="{ width: file.uploadPercentage+'%' }"></div>
                </div>
            </div>
        </div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                files: []
            },
            methods: {
                uploadFile: function (file) {
                    var item = {
                        name: file.name,
                        uploadPercentage: 0
                    };
                    this.files.push(item);
                    var fd = new FormData();
                    fd.append('myFile', file);

                    var xhr = new XMLHttpRequest();
                    xhr.open('POST', 'http://127.0.0.1:8778/upload', true);
                    xhr.upload.addEventListener('progress', function (e) {
                        item.uploadPercentage = Math.round((e.loaded * 100) / e.total);
                    }, false);
                    xhr.send(fd);
                },
                onDrag: function (e) {
                    e.stopPropagation();
                    e.preventDefault();
                },
                onDrop: function (e) {
                    e.stopPropagation();
                    e.preventDefault();
                    var dt = e.dataTransfer;
                    for (var i = 0; i !== dt.files.length; i++) {
                        this.uploadFile(dt.files[i]);
                    }
                }
            },
            mounted: function () {
                var dropbox = document.querySelector('.dropbox');
                dropbox.addEventListener('dragenter', this.onDrag, false);
                dropbox.addEventListener('dragover', this.onDrag, false);
                dropbox.addEventListener('drop', this.onDrop, false);
            }
        });
    </script>
</body>

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

推荐阅读更多精彩内容