实现Vue+Element-ui登录

在上一章创建Vue项目之后,实现登录功能。

完成之后,整个项目的大致结构如下:


vuep.png

1.首先修改入口页面App.vue,修改之后如下

<template>
  <div id="app">
    <router-view/> 
    <!-- router-view 显示的是当前路由地址所对应的内容-->
  </div>
</template>
<script>
export default {
  name: 'App'
}
</script>

然后修改入口js文件:main.js,修改之后如下

import Vue from 'vue'
import ElementUI from 'element-ui' //ui
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
import '@/styles/index.scss' // global css 核心css

import App from './App'
//import '@/icons' // icon
import router from './router/index' //router

// Vue.config.productionTip = false
Vue.use(ElementUI);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

从上面可以看出引用了element-ui,所以事先要导入,导入有两种方式:
第一种,npm命令导入,可以指定版本号,在@后面加版本号

npm i element-ui@版本号

第二种,直接到项目的package.json配置文件的“dependencies”属性下添加

"dependencies": {
    "element-ui": "^2.13.0",
  },

然后npm命令手动导入,会将新加的js包下载到本项目,类似maven的pom.xml文件中添加新的jar依赖,再用maven重新导入。此处用的是最新的版本,版本号加在^后面。

npm i

到官网查看element-ui的历史版本号


ele.png

2.添加核心css文件,新建styles文件夹,先展示需要添加的css文件目录结构


css.png

index.scss

@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';

body {
  margin: 0; //原始页面会多出8px边距
  height: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}

.el-dialog__body {
  padding: 20px 20px;
  color: #606266;
  font-size: 14px;
}

label {
  font-weight: 700;
}

html {
  height: 100%;
  box-sizing: border-box;
}

#app{
  height: 100%;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

a,
a:focus,
a:hover {
  cursor: pointer;
  color: inherit;
  outline: none;
  text-decoration: none;
}

div:focus{
  outline: none;
}

a:focus,
a:active {
  outline: none;
}

a,
a:focus,
a:hover {
  cursor: pointer;
  color: inherit;
  text-decoration: none;
}

.clearfix {
  &:after {
    visibility: hidden;
    display: block;
    font-size: 0;
    content: " ";
    clear: both;
    height: 0;
  }
}

.warn-content{
  background: rgba(66,185,131,.1);
  border-radius: 2px;
  padding: 11px;
  word-spacing: .05rem;
  a{
    color: #42b983;
    font-weight: 400;
  }
}

//main-container全局样式
.app-main{
  height: 100%;
}

.app-container {
  padding: 20px;
}

.head-container {
  padding-bottom: 10px;
  .filter-item {
    display: inline-block;
    vertical-align: middle;
    margin-bottom: 10px;
  }
  input {
    height: 30.5px;
    line-height: 30.5px;
  }
  .el-input__icon {
    line-height: 31px;
  }
}

.el-avatar {
  display: inline-block;
  text-align: center;
  background: #ccc;
  color: #fff;
  white-space: nowrap;
  position: relative;
  overflow: hidden;
  vertical-align: middle;
  width: 32px;
  height: 32px;
  line-height: 32px;
  border-radius: 16px;
}

.logo-con{
  height: 60px;
  padding: 13px 0px 0px;
  img{
    height: 32px;
    width: 135px;
    display: block;
    //margin: 0 auto;
  }
}

#el-login-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}

#el-main-footer {
  background: none repeat scroll 0 0 white;
  border-top: 1px solid #e7eaec;
  overflow: hidden;
  padding: 10px 6px 0px 6px;
  height: 33px;
  font-size: 0.7rem !important;
  color: #7a8b9a;
  letter-spacing: 0.8px;
  font-family: Arial, sans-serif !important;
  position: fixed;
  bottom: 0;
  z-index: 99;
  width: 100%;
}
.eladmin-upload {
  border: 1px dashed #c0ccda;
  border-radius: 5px;
  height: 45px;
  line-height: 45px;
  width: 368px;
}

variables.scss

#app {
  // 主体区域
  .main-container {
    min-height: 100%;
    transition: margin-left .28s;
    margin-left: 193px;
    position: relative;
  }
  // 侧边栏
  .sidebar-container {
    transition: width 0.28s;
    width: 193px !important;
    height: 100%;
    position: fixed;
    font-size: 0px;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1001;
    overflow: hidden;
    //reset element-ui css
    .horizontal-collapse-transition {
      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
    }
    .el-scrollbar__bar.is-vertical{
      right: 0px;
    }
    .scrollbar-wrapper {
      overflow-x: hidden!important;
      .el-scrollbar__view {
        height: 100%;
      }
    }
    .is-horizontal {
      display: none;
    }
    a {
      display: inline-block;
      width: 100%;
      overflow: hidden;
    }
    .svg-icon {
      margin-right: 16px;
    }
    .el-menu {
      border: none;
      height: 100%;
      width: 100% !important;
    }
    .is-active > .el-submenu__title{
      color: #f4f4f5!important;
    }
  }
  .hideSidebar {
    .sidebar-container {
      width: 36px !important;
    }
    .main-container {
      margin-left: 36px;
    }
    .submenu-title-noDropdown {
      padding-left: 10px !important;
      position: relative;
      .el-tooltip {
        padding: 0 10px !important;
      }
    }
    .el-submenu {
      overflow: hidden;
      &>.el-submenu__title {
        padding-left: 10px !important;
        .el-submenu__icon-arrow {
          display: none;
        }
      }
    }
    .el-menu--collapse {
      .el-submenu {
        &>.el-submenu__title {
          &>span {
            height: 0;
            width: 0;
            overflow: hidden;
            visibility: hidden;
            display: inline-block;
          }
        }
      }
    }
  }
  .sidebar-container .nest-menu .el-submenu>.el-submenu__title,
  .sidebar-container .el-submenu .el-menu-item {
    min-width: 195px !important;
    background-color: $subMenuBg !important;
    &:hover {
      background-color: $menuHover !important;
    }
  }
  .el-menu--collapse .el-menu .el-submenu {
    min-width: 195px !important;
  }

  //适配移动端
  .mobile {
    .main-container {
      margin-left: 0px;
    }
    .sidebar-container {
      transition: transform .28s;
      width: 195px !important;
    }
    &.hideSidebar {
      .sidebar-container {
        transition-duration: 0.3s;
        transform: translate3d(-195px, 0, 0);
      }
    }
  }
  .withoutAnimation {
    .main-container,
    .sidebar-container {
      transition: none;
    }
  }
}

.el-menu--vertical{
  & >.el-menu{
    .svg-icon{
      margin-right: 16px;
    }
  }
}

mixin.scss

@mixin clearfix {
  &:after {
    content: "";
    display: table;
    clear: both;
  }
}

@mixin scrollBar {
  &::-webkit-scrollbar-track-piece {
    background: #d3dce6;
  }
  &::-webkit-scrollbar {
    width: 6px;
  }
  &::-webkit-scrollbar-thumb {
    background: #99a9bf;
    border-radius: 20px;
  }
}

@mixin relative {
  position: relative;
  width: 100%;
  height: 100%;
}

transition.scss

//globl transition css

/*fade*/
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.28s;
}

.fade-enter,
.fade-leave-active {
  opacity: 0;
}

/*fade-transform*/
.fade-transform-leave-active,
.fade-transform-enter-active {
  transition: all .5s;
}
.fade-transform-enter {
  opacity: 0;
  transform: translateX(-30px);
}
.fade-transform-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

/*fade*/
.breadcrumb-enter-active,
.breadcrumb-leave-active {
  transition: all .5s;
}

.breadcrumb-enter,
.breadcrumb-leave-active {
  opacity: 0;
  transform: translateX(20px);
}

.breadcrumb-move {
  transition: all .5s;
}

.breadcrumb-leave-active {
  position: absolute;
}

element-ui.scss

 //to reset element-ui default css
.el-upload {
  input[type="file"] {
    display: none !important;
  }
}

.el-upload__input {
  display: none;
}

//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
.el-dialog {
  transform: none;
  left: 0;
  position: relative;
  margin: 0 auto;
}

//element ui upload
.upload-container {
  .el-upload {
    width: 100%;
    .el-upload-dragger {
      width: 100%;
      height: 200px;
    }
  }
}

sidebar.scss

#app {
  // 主体区域
  .main-container {
    min-height: 100%;
    transition: margin-left .28s;
    margin-left: 193px;
    position: relative;
  }
  // 侧边栏
  .sidebar-container {
    transition: width 0.28s;
    width: 193px !important;
    height: 100%;
    position: fixed;
    font-size: 0px;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1001;
    overflow: hidden;
    //reset element-ui css
    .horizontal-collapse-transition {
      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
    }
    .el-scrollbar__bar.is-vertical{
      right: 0px;
    }
    .scrollbar-wrapper {
      overflow-x: hidden!important;
      .el-scrollbar__view {
        height: 100%;
      }
    }
    .is-horizontal {
      display: none;
    }
    a {
      display: inline-block;
      width: 100%;
      overflow: hidden;
    }
    .svg-icon {
      margin-right: 16px;
    }
    .el-menu {
      border: none;
      height: 100%;
      width: 100% !important;
    }
    .is-active > .el-submenu__title{
      color: #f4f4f5!important;
    }
  }
  .hideSidebar {
    .sidebar-container {
      width: 36px !important;
    }
    .main-container {
      margin-left: 36px;
    }
    .submenu-title-noDropdown {
      padding-left: 10px !important;
      position: relative;
      .el-tooltip {
        padding: 0 10px !important;
      }
    }
    .el-submenu {
      overflow: hidden;
      &>.el-submenu__title {
        padding-left: 10px !important;
        .el-submenu__icon-arrow {
          display: none;
        }
      }
    }
    .el-menu--collapse {
      .el-submenu {
        &>.el-submenu__title {
          &>span {
            height: 0;
            width: 0;
            overflow: hidden;
            visibility: hidden;
            display: inline-block;
          }
        }
      }
    }
  }
  .sidebar-container .nest-menu .el-submenu>.el-submenu__title,
  .sidebar-container .el-submenu .el-menu-item {
    min-width: 195px !important;
    background-color: $subMenuBg !important;
    &:hover {
      background-color: $menuHover !important;
    }
  }
  .el-menu--collapse .el-menu .el-submenu {
    min-width: 195px !important;
  }

  //适配移动端
  .mobile {
    .main-container {
      margin-left: 0px;
    }
    .sidebar-container {
      transition: transform .28s;
      width: 195px !important;
    }
    &.hideSidebar {
      .sidebar-container {
        transition-duration: 0.3s;
        transform: translate3d(-195px, 0, 0);
      }
    }
  }
  .withoutAnimation {
    .main-container,
    .sidebar-container {
      transition: none;
    }
  }
}

.el-menu--vertical{
  & >.el-menu{
    .svg-icon{
      margin-right: 16px;
    }
  }
}

3.修改route,加载登录页面

import Vue from 'vue'
import Router from 'vue-router'
import login from '@/views/login' //导入login组件

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'login', 
      component: login //组件
    }
  ]
})

4.编写login,新建views文件夹,添加login.vue,内容如下

<template>
  <div class="login">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" label-position="left" label-width="0px" class="login-form">
      <h3 class="title">EL-ADMIN 后台管理系统</h3>
      <el-form-item prop="username">
        <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon"/>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter.native="handleLogin">
          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/>
        </el-input>
      </el-form-item>
      <el-form-item prop="code">
        <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin">
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/>
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode">
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住我</el-checkbox>
      <el-form-item style="width:100%;">
        <el-button :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
      </el-form-item>
    </el-form>
    <!--  底部  -->
    <div id="el-login-footer">
      <span>© 2020 Zhou Yi</span>
      <span> ⋅ </span>
      <a href="http://www.beian.miit.gov.cn" target="_blank">渝ICP备18005431号</a>
    </div>
  </div>
</template>

<script>
import { encrypt } from '@/utils/rsaEncrypt' //加密
import Config from '@/config' //全局配置
import { getCodeImg } from '@/api/login' //api
import Cookies from 'js-cookie' //cookie
export default {
  name: 'Login',
  data() {
    return {
      codeUrl: '',
      cookiePass: '',
      loginForm: {
        username: 'admin',
        password: '123456',
        rememberMe: false,
        code: '',
        uuid: ''
      },
      loginRules: {
        username: [{ required: true, trigger: 'blur', message: '用户名不能为空' }],
        password: [{ required: true, trigger: 'blur', message: '密码不能为空' }],
        code: [{ required: true, trigger: 'change', message: '验证码不能为空' }]
      },
      loading: false,
      redirect: undefined
    }
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect
      },
      immediate: true
    }
  },
  created() {
    // this.getCode()
    // this.getCookie()
  },
  methods: {
    getCode() {
      getCodeImg().then(res => {
        this.codeUrl = res.img
        this.loginForm.uuid = res.uuid
      })
    },
    getCookie() {
      const username = Cookies.get('username')
      let password = Cookies.get('password')
      const rememberMe = Cookies.get('rememberMe')
      // 保存cookie里面的加密后的密码
      this.cookiePass = password === undefined ? '' : password
      password = password === undefined ? this.loginForm.password : password
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password: password,
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
        code: ''
      }
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        const user = {
          username: this.loginForm.username,
          password: this.loginForm.password,
          rememberMe: this.loginForm.rememberMe,
          code: this.loginForm.code,
          uuid: this.loginForm.uuid
        }
        if (user.password !== this.cookiePass) {
          user.password = encrypt(user.password)
        }
        if (valid) {
          this.loading = true
          if (user.rememberMe) {
            Cookies.set('username', user.username, { expires: Config.passCookieExpires })
            Cookies.set('password', user.password, { expires: Config.passCookieExpires })
            Cookies.set('rememberMe', user.rememberMe, { expires: Config.passCookieExpires })
          } else {
            Cookies.remove('username')
            Cookies.remove('password')
            Cookies.remove('rememberMe')
          }
          this.$store.dispatch('Login', user).then(() => {
            this.loading = false
            this.$router.push({ path: this.redirect || '/' })
          }).catch(() => {
            this.loading = false
            this.getCode()
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }
  }
}
</script>
//scss语法
<style rel="stylesheet/scss" lang="scss">
  .login {
    display: flex; //flex布局
    justify-content: center; //水平居中
    align-items: center; //上下居中
    height: 100%;
    background-image:url(http://api.neweb.top/bing.php?type=rand );
    background-size: cover;
  }
  .title {
    margin: 0px auto 30px auto;
    text-align: center;
    color: #707070;
  }

  .login-form {
    border-radius: 6px;
    background: #ffffff;
    width: 385px;
    padding: 25px 25px 5px 25px;
    .el-input {
      height: 38px;
      input {
        height: 38px;
      }
    }
    .input-icon{
      height: 39px;width: 14px;margin-left: 2px;
    }
  }
  .login-tip {
    font-size: 13px;
    text-align: center;
    color: #bfbfbf;
  }
  .login-code {
    width: 33%;
    display: inline-block;
    height: 38px;
    float: right;
    img{
      cursor: pointer;
      vertical-align:middle
    }
  }
  #el-login-footer {
    position: fixed;
    bottom: 0;
    width: 100%;
    height: 40px;
    line-height: 40px;
    text-align: center;
    color: #fff;
    font-family: Arial,serif;
    font-size: 12px;
    letter-spacing: 1px;
  }
  a{
    cursor: pointer;
    color: inherit;
    text-decoration: none;
  }
</style>

首先样式使用scss语法,布局采用flex布局
又引入了新的包,先解决js-cookie,注意:采用scss语法需要导入node-sass和sass-loader,直接在package.json中添加,npm i 命令导入

"dependencies": {
    "js-cookie": "2.2.0",
  },
 "devDependencies": {
    "node-sass": "^4.7.2",
    "sass-loader": "7.0.3",
 },

接下来新建utils文件夹,添加rsaEncrypt.js

import JSEncrypt from 'jsencrypt/bin/jsencrypt'

// 密钥对生成 http://web.chacuo.net/netrsakeypair

const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' +
  '2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ=='

const privateKey = 'MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8\n' +
  'mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9p\n' +
  'B6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue\n' +
  '/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZ\n' +
  'UBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6\n' +
  'vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha\n' +
  '4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3\n' +
  'tTbklZkD2A=='

// 加密
export function encrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey) // 设置公钥
  return encryptor.encrypt(txt) // 对需要加密的数据进行加密
}

// 解密
export function decrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPrivateKey(privateKey)
  return encryptor.decrypt(txt)
}

再新建config文件夹,添加index.js全局配置文件

/**
 * @description 系统全局配置
 */
export default {
    /**
     * @description 记住密码状态下的token在Cookie中存储的天数,默认1天
     */
    tokenCookieExpires: 1,
    /**
     * @description 记住密码状态下的密码在Cookie中存储的天数,默认1天
     */
    passCookieExpires: 1,
    /**
     * @description 此处修改网站名称
     */
    webName: 'EL-ADMIN',
    /**
     * @description 是否只保持一个子菜单的展开
     */
    uniqueOpened: true,
    /**
     * @description token key
     */
    TokenKey: 'EL-ADMIN-TOEKN',
  
    /**
     * @description 请求超时时间,毫秒(默认2分钟)
     */
    timeout: 1200000,
  
    /**
     * @description 是否显示 tagsView
     */
    tagsView: true,
  
    /**
     * @description 固定头部
     */
    fixedHeader: true,
  
    /**
     * @description 是否显示logo
     */
    sidebarLogo: true,
  
    /**
     * 是否显示设置的悬浮按钮
     */
    settingBtn: false,
  }

新建api文件夹,添加login.js

import request from '@/utils/request'

export function login(username, password, code, uuid) {
  return request({
    url: 'auth/login',
    method: 'post',
    data: {
      username,
      password,
      code,
      uuid
    }
  })
}

export function getInfo() {
  return request({
    url: 'auth/info',
    method: 'get'
  })
}

export function getCodeImg() {
  return request({
    url: 'auth/captcha',
    method: 'get'
  })
}

export function logout() {
  return request({
    url: 'auth/logout',
    method: 'delete'
  })
}

在utils文件夹下添加request.js

import axios from 'axios'
import Config from '@/config'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.NODE_ENV === 'production' ? process.env.BASE_API : '/', // api 的 base_url
  timeout: Config.timeout // 请求超时时间
})
export default service

此处需要引入axios

  "dependencies": {
    "axios": "^0.19.0",
  },

5.启动项目测试

npm run dev

启动成功


start.png

浏览器输入:http://localhost:8080

login.png

后面还有svg-icon、store、以及与后台交互测试。

模仿EL-ADMIN 后台管理系统

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

推荐阅读更多精彩内容