前端全栈从零单排 -- 上传、点赞功能,悬浮球、弹窗...

本文简介:

① 基于express,multer中间件,mongose的上传功能,
② vue双向绑定ui,vueresource请求后台数据,mongose work表添加likeContract数组实现点赞
③悬浮球,弹窗,上传组件的实现
前端:https://github.com/woaigmz/postcard
后台:https://github.com/woaigmz/mp

wx.gif

①上传功能:

后台部分 ---

work(作品)表:

const mongoose = require('mongoose')

const workSchema = mongoose.Schema({
  imgurl: String,
  userId: String,
  content: String,
  username: String,
  like: Number,
  share: Number,
  likeContract:Array //存储点赞用户,建立关联
}, { collection: 'work' })

const Work = module.exports = mongoose.model('work', workSchema);

control层:定义接口,

const express = require('express');
const router = express.Router();
const WorkModel = require('../model/workSchema');
const StringUtil = require('../utils/StringUtil');
const JsonUtil = require('../utils/JsonUtil');
//const TokenCheckUtil = require('../utils/TokenCheckUtil');
const multer = require('../utils/MulterUtil');
//接收 image 并静态存储
const upload = multer.single('image');


//上传作品  image:blob  name:string content:string 
router.post('/upload', upload, function (req, res) {

  WorkModel.create({
      username: req.body.name,
      content: req.body.content,
      imgurl: 'http://' + req.headers.host + '/images/' + req.file.filename,
      userId: req.body.userId,
      like: 0,
      share: 0,
      likeContract: []
  }, (err, success) => {
      if (err) {
          console.log(err);
          JsonUtil.response(res, '201', err, "返回错误");
      } else {
          console.log(success);
          JsonUtil.response(res, '200', success, "上传图片成功");
      }
  });
})

上传需要用到中间件multer,具体MulterUtil (npm install multer ..省去):

const multer = require('multer');

const storage = multer.diskStorage({
  destination: function (req, file, callback) {
      // 注: window / linux 不会自动创建 images 文件夹时要给予权限或手动创建
      callback(null, "./images");
  },
  filename: function (req, file, callback) {
      //data拼接防止上传同一个作品造成覆盖
      callback(null, Date.now() + "_" + file.originalname);
  }
});

const m = multer({
  storage: storage
});

module.exports = m;
前端请求上传部分:
//引入upload_form和api
import {
items,
cards,
works,
upload_form,
like_form
} from "../data/localData.js";
import Api from "../data/api.js";
//上传
upload: function() {
    let that = this;
    this.upload_form.name = this.username;
    console.log(this.upload_form.data);
    console.log(this.upload_form.name);
    console.log(this.upload_form.content);
    if (
      !isEmpty(this.upload_form.name) &&
      !isEmpty(this.upload_form.content) &&
      !isEmpty(this.upload_form.data)
    ) {
      let formData = new window.FormData();
      formData.append("image", this.upload_form.data, ".jpg");
      formData.append("name", this.upload_form.name);
      formData.append("content", this.upload_form.content);
      this.$http.post(Api.UPLOAD, formData).then(
        response => {
          if (response.ok && response.body.code == "201") {
            that.showSnap("error", "上传失败");
          } else {
            that.showSnap("success", response.body.message);
            that.closeDialog();
            that.works.splice(0, 0, {
              _id: response.body.data._id,
              username: response.body.data.username,
              content: response.body.data.content,
              imgurl: response.body.data.imgurl,
              like: response.body.data.like,
              share: response.body.data.share,
              isLike: response.body.data.isLike
            });
            console.log(response.body);
            console.log(that.works);
            that.upload_form.data = "";
            that.upload_form.content = "";
            that.upload_form.name = "";
          }
        },
        () => {
          that.showSnap("error", "上传失败");
        }
      );
    } else {
      that.showSnap("error", "请保证您的明信片完整");
    }
  },

api.js:抽取便于维护

module.exports = {
  REGISTER: "http://localhost:3001/api/register",
  LOGIN: "http://localhost:3001/api/login",
  UPLOAD:"http://localhost:3001/api/upload",
  GETWORKLIST:"http://localhost:3001/api/getWorkList",
  GETCARDLIST:"http://localhost:3001/api/getCardList",
  LIKE:"http://localhost:3001/api/like"
};

localData.js: 不至于你的vue文件里过多出现数据结构

exports.login_form = {
name: "",
password: "",
token: ""
};

exports.register_form = {
name: "",
age: "",
sex: "",
address: "",
imgArr: "",
phone: "",
password: "",
token: ""
};

exports.items = [
{
  href: "",
  name: "粉丝",
  count: 0
},
{
  href: "",
  name: "关注",
  count: 0
},
{
  href: "",
  name: "获赞",
  count: 0
}
];

exports.cards = [];

exports.works = [];

exports.like_form = {
type: "",
workId: "",
username: ""
};

exports.upload_form = {
data: "",
content: "",
name: ""
}

具体点击上传的组件后面聊 :D 感谢大家阅读

②点赞功能:

后台部分:

点赞接口 ---
like路由,通过1或0定义点赞或取消,记录当前用户和点赞作品
$addToSet 增加到不重复元素 Arrray
$pull 移除 Array 里的对象
$inc 运算 只针对 Number 类型

//点赞/取消 1/0  type:1/0 username:string workId:string
router.post('/like', function (req, res) {
  if (req.body.type === "1") {
      //点赞
      console.log("点赞");
      WorkModel.update({ _id: req.body.workId }, { $addToSet: { likeContract: req.body.username }, $inc: { like: 1 } }, (err, success) => {
          if (err) {
              JsonUtil.response(res, '201', err, "点赞失败");
          } else {
              console.log(success);
              JsonUtil.response(res, '200', success, "点赞成功");
          }
      });
  } else {
      //取消
      console.log("取消");
      WorkModel.update({ _id: req.body.workId }, { $pull: { likeContract: req.body.username }, $inc: { like: -1 } }, (err, success) => {
          if (err) {
              JsonUtil.response(res, '201', err, "取消失败");
          } else {
              console.log(success);
              JsonUtil.response(res, '200', success, "取消成功");
          }
      });
  }
})

推荐作品接口(后期会用推荐算法优化):

//推荐列表
router.post('/getCardList', function (req, res) {
  WorkModel.where({ 'like': { $gte: StringUtil.isEmpty(req.body.max) ? 100 : req.body.max } }).find((err, success) => {
      if (err) {
          JsonUtil.response(res, '201', err, "返回错误");
      } else {
          if (!StringUtil.isEmpty(success)) {
              let arr = new Array();
              success.forEach(function (value, index, array) {
                  let isLike = StringUtil.isInArray(value.likeContract, req.body.name);
                  let newObj = {
                      _id: value._id,
                      username: value.username,
                      content: value.content,
                      imgurl: value.imgurl,
                      like: value.like,
                      share: value.share,
                      isLike: isLike
                  }
                  arr.push(newObj)
              })

              console.log(arr);
              JsonUtil.response(res, '200', arr, "返回成功");
          } else {
              console.log("e" + success);
              JsonUtil.response(res, '201', success, "数据为空");
          }
      }

  }).sort({ like: -1 })
})

1) 返回isLike便于前端判断和通过vm更新视图操作;
2) 传入username便于后期推荐和判断是否对某项作品点过赞;
3) $gte: 大于等于100的作品上推荐列表;
4) sort({ like: -1 }) 降序排列

前端部分:

v-for 列表中对 item (推荐作品)点赞,

<div class="card-list">
    <div class="card-item" v-for="(item,index) in cards" :key="index">
      <img class="card-item-img" :src="item.imgurl">
      <div class="card-item-userinfo">
        {{item.username}}
      </div>
      <div class="card-item-content">
        {{item.content}}
      </div>
      <div class="card-item-operator">
        <!-- 通过likeForCards方法传入的index 进行点赞相关逻辑操作 -->
        <span title="喜欢" class="like" @click="likeForCards(index)">
          <!--  通过item.isLike设置点赞图标的背景样式  -->
          <i v-bind:class="[item.isLike? 'likeafter':'likebefore']"></i>{{item.like}}</span>
        <span title="分享" class="share">
          <i class="share-icon"></i>{{item.share}}</span>
      </div>
    </div>

样式:

.likebefore {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 4px;
vertical-align: bottom;
background: url(/static/imgs/unlike.svg) 0 0 no-repeat;
}
.likeafter {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 4px;
vertical-align: bottom;
background: url(/static/imgs/like.svg) 0 0 no-repeat;
}

script逻辑部分:

//点赞  like_form 承载了 调用点赞接口的请求参数(type\workId\username)
likeForCards: function(index) {
     console.log( "点赞数量:" + this.cards[index].like + "是否点赞:" + this.cards[index].isLike);
     let that = this;
     //如果作品目前的状态是点赞状态,则进行取消点赞的操作( type:"0") ;未点赞,则进行点赞操作( type:"1")
     this.like_form.type = this.cards[index].isLike ? "0" : "1";
     this.like_form.workId = this.cards[index]._id;
     this.like_form.username = this.username;
     //请求网络
     this.$http.post(Api.LIKE, like_form).then(
       response => {
         if (response.ok && response.code == "201") {
           that.showSnap("error", response.body.message);
         } else {
           console.log(response.body);
           that.showSnap("success", response.body.message);
           // vm 更新数据来做视图更新
           that.cards[index].like = that.cards[index].isLike? parseInt(that.cards[index].like) - 1:parseInt(that.cards[index].like) + 1;
           that.cards[index].isLike = !that.cards[index].isLike;
           console.log( "点赞数量:" + that.cards[index].like + "是否点赞:" + that.cards[index].isLike);
         }
       },
       () => {
         that.showSnap("error", "点赞失败");
       }
     );
   }

③悬浮球:

template

 <!-- 悬浮球 -->
   <div id="float-ball" @click="showUploadWorkDialog" v-show="showFloatBall">

style

#float-ball {
 position: fixed;
 border-radius: 50%;
 bottom: 100px;
 right: 100px;
 width: 60px;
 height: 60px;
 z-index: 100;
 background: #409eff;
 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
 background-image: url("../assets/publish.svg");
 background-position: center;
 background-repeat: no-repeat;
}

mount,dom形成以后做一些初始化操作

mounted() {
   window.addEventListener("scroll", this.handleScroll);
   //... 省略
   this.initWorks();
   this.initCards();
 },

页面销毁要重置标记

destroyed() {
   window.removeEventListener("scroll", this.handleScroll);
   this.start_pos = 0;
 },

//悬浮球隐藏出现逻辑,滑动完成记录位置到标记位,开始滑动时比较判断向上还是向下

//记录标志位和是否隐藏,通过更改数据更新ui显示
data: function() {
   return {
     ...
     start_pos: 0,
     showFloatBall: true,
     ...
   };
 },
methods: {
     handleScroll: function() {
     //适配chrome、safari 、firfox
     let scrollTop =window.pageYOffset || document.documentElement.scrollTop ||document.body.scrollTop;
     let offsetTop = document.querySelector("#float-ball").offsetTop;
     //console.log("scrollTop:" + scrollTop);
     //console.log(offsetTop);
     if (scrollTop > this.start_pos) {
       this.showFloatBall = false;
     } else {
       this.showFloatBall = true;
     }
     this.start_pos = scrollTop;
   },
}

④弹窗:

Dialog.vue 通过slot插槽引入div,this.$emit("on-close");发送事件

<template>
 <div class="dialog">
   <!-- 遮罩 -->
   <div class="dialog-cover back" v-if="isShow" @click="closeMyself"></div>
   <!-- props 控制内容的样式  -->
   <div class="dialog-content" :style="{top:topDistance+'%',width:widNum+'%',left:leftSite+'%'}" v-if="isShow">
     <div class="dialog_head back ">
       <slot name="header">header</slot>
     </div>
     <div class="dialog_main " :style="{paddingTop:pdt+'px',paddingBottom:pdb+'px'}">
       <slot name="main">body</slot>
     </div>
     <!-- 弹窗关闭按钮 -->
     <div class="foot_close " @click="closeMyself">
     </div>
   </div>
 </div>
</template> 

<script>
export default {
 name: "dialogComponent",
 props: {
   isShow: {
     type: Boolean,
     default: false,
     required: true
   },
   widNum: {
     type: Number,
     default: 86.5
   },
   leftSite: {
     type: Number,
     default: 6.5
   },
   topDistance: {
     type: Number,
     default: 18
   },
   pdt: {
     type: Number,
     default: 30
   },
   pdb: {
     type: Number,
     default: 30
   }
 },
 methods: {
   closeMyself() {
     this.$emit("on-close");
   }
 }
};
</script>
<style lang="scss" scoped>
.dialog {
 position: relative;
 color: #2e2c2d;
 font-size: 16px;
}
// 遮罩
.dialog-cover {
 background: rgba(0, 0, 0, 0.8);
 position: fixed;
 z-index: 200;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
}
// 内容
.dialog-content {
 position: fixed;
 top: 35%;
 display: flex;
 flex-direction: column;
 justify-content: center;
 align-items: center;
 z-index: 300;
 .dialog_head {
   background: #409eff;
   width: 600px;
   height: 43px;
   display: flex;
   justify-content: center;
   align-items: center;
   color: #ffffff;
   border-top-left-radius: 10px;
   border-top-right-radius: 10px;
 }
 .dialog_main {
   background: #ffffff;
   width: 600px;
   border-bottom-left-radius: 10px;
   border-bottom-right-radius: 10px;
 }
 .foot_close {
   width: 50px;
   height: 50px;
   border-radius: 50%;
   background: #409eff;
   margin-top: -25px;
   background-position: center;
   background-repeat: no-repeat;
   background-image: url("../assets/dialog_close.svg");
 }
}
</style>

使用:
引入组件:

export default {
 name: "HomePage",
 个人感觉这种引入方式比较优雅
 components: {
   toolbar: require("../components/Toolbar.vue").default,
   workDialog: require("../components/Dialog.vue").default,
   imgUpload: require("../components/upload-img.vue").default
 },
 data: function() {
 .....省略

templete布局

<!-- work 对话框 -->
   <work-dialog :is-show="isShowWorkArea" @on-close="closeDialog">
     <!-- title -->
     <div class="dialog_upload_header" slot="header">
       我的明信片:D
     </div>
     <!-- work 内容 -->
     <div class="dialog_upload_main" slot="main">
       <imgUpload v-on:select-complete="secelted"></imgUpload>
       <div class="work-content">
         <!-- 用户信息 -->
         <div class="work-username">
           作者:{{username}}
         </div>
         <!-- 添加文字 -->
         <div class="edit-content">
           <textarea name="text" rows="3" class="card-add-content" placeholder="这里写下你想说的话(*^-^*)" v-bind:maxlength="140" @input="descArea" v-model="upload_form.content"></textarea>
           <span style="font-size:10px;float:right;color: #409eff;">剩余字数 {{surplus}}/140</span>
         </div>
         <!-- 发布 -->
         <el-button id="publish" size="small" type="primary" @click="upload">点击上传</el-button>
       </div>
     </div>
   </work-dialog>

⑤上传组件(参考github项目)

upload-img.vue 隐藏input样式,易于定制个性化上传框样式,压缩图片(瓦片上传和canvas两种方式),EXIF判断图片方向并适当旋转图片

<template>
 <div class="upload">
   <input type="file" @change="handle($event)" name="model" accept="image/*">
   <img :src="imgSrc" alt="" v-show="imgSrc" :name="model">
 </div>
</template>
<script>
import EXIF from "exif-js";
export default {
 data() {
   return {
     imgSrc: ""
   };
 },
 props: ["model"],
 created() {},
 ready() {},
 methods: {
   handle(evt) {
     var _name = this.model;
     const files = Array.prototype.slice.call(evt.target.files);

     let that = this;

     files.forEach(function(file, i) {
       var orientation;
       if (!/\/(?:jpeg|png|gif)/i.test(file.type)) return;
       //读取图片的元信息
       EXIF.getData(file, function() {
         orientation = EXIF.getTag(this, "Orientation");
       });

       let reader = new FileReader();

       reader.onload = function() {
         let result = this.result;
         that.imgSrc = result;
         //使用exif
         that.getImgData(this.result, orientation, function(data) {
           //这里可以使用校正后的图片data了
           var img = new Image();
           img.src = data;

           //图片加载完毕之后进行压缩,然后上传
           if (img.complete) {
             callback();
           } else {
             img.onload = callback;
           }

           function callback() {
             var data = that.compress(img);
             that.upload(data, file.type, file.name, _name);
           }
         });
       };

       reader.readAsDataURL(file);
     });
   },
   //压缩图片
   compress(img) {
     //用于压缩图片的canvas
     let canvas = document.createElement("canvas");
     let ctx = canvas.getContext("2d");

     //    瓦片canvas
     var tCanvas = document.createElement("canvas");
     var tctx = tCanvas.getContext("2d");

     let initSize = img.src.length;
     let standard = 200;
     let width = img.naturalWidth;
     let height = img.naturalHeight;

     height = standard * height / width;

     width = standard;

     console.log("w:" + width + "h:" + height);

     //如果图片大于四百万像素,计算压缩比并将大小压至400万以下
     var ratio;
     if ((ratio = width * height / 4000000) > 1) {
       ratio = Math.sqrt(ratio);
       width /= ratio;
       height /= ratio;
     } else {
       ratio = 1;
     }
     canvas.width = width * 2;
     canvas.height = height * 2;
     //铺底色
     ctx.fillStyle = "#fff";
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     //如果图片像素大于100万则使用瓦片绘制
     var count;
     if ((count = width * height / 1000000) > 1) {
       count = ~~(Math.sqrt(count) + 1); //计算要分成多少块瓦片
       //计算每块瓦片的宽和高
       var nw = ~~(width / count);
       var nh = ~~(height / count);
       tCanvas.width = nw;
       tCanvas.height = nh;
       for (var i = 0; i < count; i++) {
         for (var j = 0; j < count; j++) {
           tctx.drawImage(
             img,
             i * nw * ratio,
             j * nh * ratio,
             nw * ratio * 2,
             nh * ratio * 2,
             0,
             0,
             nw,
             nh
           );
           ctx.drawImage(tCanvas, i * nw, j * nh, nw * 2, nh * 2);
         }
       }
     } else {
       ctx.drawImage(img, 0, 0, width * 2, height * 2);
     }

     //进行最小压缩
     let ndata = canvas.toDataURL("image/jpeg/jpg/png", 0.9);
     console.log("压缩前:" + initSize);
     console.log("压缩后:" + ndata.length);
     console.log(
       "压缩率:" + ~~(100 * (initSize - ndata.length) / initSize) + "%"
     );
     return ndata;
   },
   //上传图片
   upload(basestr, type, name, model) {
     let text = window.atob(basestr.split(",")[1]);
     let buffer = new ArrayBuffer(text.length);
     let ubuffer = new Uint8Array(buffer);

     for (let i = 0; i < text.length; i++) {
       ubuffer[i] = text.charCodeAt(i);
     }

     let Builder = window.WebKitBlobBuilder || window.MozBlobBuilder;
     let blob;

     if (Builder) {
       let builder = new Builder();
       builder.append(buffer);
       blob = builder.getBlob(type);
     } else {
       blob = new window.Blob([buffer], { type: type });
     }

     //选择完毕触发事件
     this.$emit("select-complete", blob);
   },
   getImgData(img, dir, next) {
     // @param {string} img 图片的base64
     // @param {int} dir exif获取的方向信息
     // @param {function} next 回调方法,返回校正方向后的base64
     var image = new Image();
     image.onload = function() {
       var degree = 0,
         drawWidth,
         drawHeight,
         width,
         height;
       drawWidth = this.naturalWidth;
       drawHeight = this.naturalHeight;
       //以下改变一下图片大小
       var maxSide = Math.max(drawWidth, drawHeight);
       if (maxSide > 1024) {
         var minSide = Math.min(drawWidth, drawHeight);
         minSide = minSide / maxSide * 1024;
         maxSide = 1024;
         if (drawWidth > drawHeight) {
           drawWidth = maxSide;
           drawHeight = minSide;
         } else {
           drawWidth = minSide;
           drawHeight = maxSide;
         }
       }
       var canvas = document.createElement("canvas");
       canvas.width = width = drawWidth;
       canvas.height = height = drawHeight;
       var context = canvas.getContext("2d");
       //判断图片方向,重置canvas大小,确定旋转角度,iphone默认的是home键在右方的横屏拍摄方式
       switch (dir) {
         //iphone横屏拍摄,此时home键在左侧
         case 3:
           degree = 180;
           drawWidth = -width;
           drawHeight = -height;
           break;
         //iphone竖屏拍摄,此时home键在下方(正常拿手机的方向)
         case 6:
           canvas.width = height;
           canvas.height = width;
           degree = 90;
           drawWidth = width;
           drawHeight = -height;
           break;
         //iphone竖屏拍摄,此时home键在上方
         case 8:
           canvas.width = height;
           canvas.height = width;
           degree = 270;
           drawWidth = -width;
           drawHeight = height;
           break;
       }
       //使用canvas旋转校正
       context.rotate(degree * Math.PI / 180);
       context.drawImage(this, 0, 0, drawWidth, drawHeight);
       //返回校正图片
       next(canvas.toDataURL("image/jpeg/jpg/png", 0.4));
     };
     image.src = img;
   }
 }
};
</script>
<style scoped>
.upload {
 background-image: url(../assets/add.svg);
 background-repeat: no-repeat;
 background-position:center;
 display: flex;
 justify-content: center;
 align-items: center;
 width: 200px;
 height: 280px;
 position: relative;
 border: 1px solid #409eff;
 box-sizing: border-box;
 z-index: 8;
}
img {
 position: absolute;
 object-fit: cover;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
}
input {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 opacity: 0;
 z-index: 99;
}
</style>

使用方式:同上(dialog)压缩完成拿到的数据是blob二进制对象

<imgUpload v-on:select-complete="secelted"></imgUpload>

赋值给上传参数对象

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

推荐阅读更多精彩内容

  • https://cn.vuejs.org 转载 :OpenDigg awesome-github-vue 是由Op...
    文朝明阅读 6,589评论 0 38
  • 目录 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 UI组件 element★13489 ...
    余生社会阅读 19,726评论 7 233
  • “在股市追涨杀跌是许多人都有的缺点。看到股市上涨的时候抑制不住贪婪的欲望,开始追涨,看到股市下跌的时候,又克服不了...
    Renatayaaaa阅读 307评论 0 0
  • 鹊桥仙·朱淑真 晋言 独行独坐,独眠独卧,选遍长街无味。独怜挂月总孤单,夜夜里、观人入睡。 难思难过,难宁难破,未...
    a7f7e0267748阅读 609评论 1 1
  • 小时候什么也不懂,随着时间的推移,随着年龄的增长,随着踏入社会自己慢慢积累的经验,虽然赶不上社会发展的速度,...
    忆甜人生阅读 181评论 0 3