Vue开发旅游App

项目准备
  1. 在码云新建仓库travel
  2. 克隆到本地
  3. 在本地仓库所在目录执行
vue init webpack travel

选择y表示继续

样式重置

index.html

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
reset.css
  1. src/assets/styles目录下存放reset.css
@charset "utf-8";
html {
  background-color: #fff;
  color: #000;
  font-size: 12px;
}

body,
ul,
ol,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
figure,
form,
fieldset,
legend,
input,
textarea,
button,
p,
blockquote,
th,
td,
pre,
xmp {
  margin: 0;
  padding: 0;
}

body,
input,
textarea,
button,
select,
pre,
xmp,
tt,
code,
kbd,
samp {
  line-height: 1.5;
  font-family: tahoma, arial, 'Hiragino Sans GB', simsun, sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6,
small,
big,
input,
textarea,
button,
select {
  font-size: 100%;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: tahoma, arial, 'Hiragino Sans GB', '微软雅黑', simsun, sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6,
b,
strong {
  font-weight: normal;
}

address,
cite,
dfn,
em,
i,
optgroup,
var {
  font-style: normal;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
  text-align: left;
}

caption,
th {
  text-align: inherit;
}

ul,
ol,
menu {
  list-style: none;
}

fieldset,
img {
  border: 0;
}

img,
object,
input,
textarea,
button,
select {
  vertical-align: middle;
}

article,
aside,
footer,
header,
section,
nav,
figure,
figcaption,
hgroup,
details,
menu {
  display: block;
}

audio,
canvas,
video {
  display: inline-block;
  *display: inline;
  *zoom: 1;
}

blockquote:before,
blockquote:after,
q:before,
q:after {
  content: '\0020';
}

textarea {
  overflow: auto;
  resize: vertical;
}

input,
textarea,
button,
select,
a {
  outline: 0 none;
  border: none;
}

button::-moz-focus-inner,
input::-moz-focus-inner {
  padding: 0;
  border: 0;
}

mark {
  background-color: transparent;
}

a,
ins,
s,
u,
del {
  text-decoration: none;
}

sup,
sub {
  vertical-align: baseline;
}

html {
  overflow-x: hidden;
  height: 100%;
  font-size: 50px;
  -webkit-tap-highlight-color: transparent;
}

body {
  font-family: Arial, 'Microsoft Yahei', 'Helvetica Neue', Helvetica, sans-serif;
  color: #333;
  font-size: 0.28em;
  line-height: 1;
  -webkit-text-size-adjust: none;
}

hr {
  height: 0.02rem;
  margin: 0.1rem 0;
  border: medium none;
  border-top: 0.02rem solid #cacaca;
}

a {
  color: #25a4bb;
  text-decoration: none;
}

  1. 在mian.js文件引入
import './assets/styles/reset.css'
解决移动端1像素边框问题
  1. src/assets/styles目录下存放border.css
  2. 在mian.js文件引入
import './assets/styles/border.css'
解决移动端300ms点击延迟

安装fastclick

npm install fastclick --save

main.js

import fastClick from 'fastclick'
fastClick.attach(document.body)
字体图标

在iconfont新建项目

在项目中使用sass
npm install sass-loader node-sass --save-dev

注意sass-loader版本过高可能会报错

页面组件化

将一个页面拆分成多个组件
src/pages/home目录下新建components目录,然后新建Header.vue
引入
src/pages/home/home.vue

<template>
  <div><home-header></home-header></div>
</template>

<script>
import HomeHeader from './components/Header'
export default {
  name: 'Home',
  components: {
   //es6中键值相同可以省略值
    HomeHeader
  }
}
</script>
<style></style>

Vue自动完成HomeHeader和小写的<home-header>的关联

页面元素高度问题

由于移动端一般使用双倍像素,如果指定元素高度10px,实际显示为20px,所以实际指定高度应为设计图纸中的一半。在实际开发中,一般使用rem作为单位,我们可以指定html的font-size为50px,如果设计图上某个元素高度65px,转化为rem值为0.65rem(css中元素高度应为设计图上该元素高度的一半)

引入字体图标

在styles目录新建iconfont,把从Iconfont下载的字体图标放进去


iconfont.css在styles目录下,需要修改字体路径,如

src: url('./iconfont/iconfont.eot?t=1584759696965');
main.js
import './assets/styles/iconfont.css'
使用图标
<span class="iconfont iconfanhui"></span>
使用scss变量

src/assets/styles目录下新建_variables.scss,存放变量

// demo
$bgColor: #00bcd4;

在项目中引入
src/pages/home/components/Header.vue

<style lang="scss" scoped>
@import '~@/assets/styles/_variables';
...
</style>

注意@表示src目录,前面的~必须加上才不会报错

给路径添加别名

给路径添加别名好处是减少路径长度
build/webpack.base.conf.js

resolve: {
  extensions: ['.js', '.vue', '.json'],
  alias: {
    'vue$': 'vue/dist/vue.esm.js',
    '@': resolve('src'),
  }
},

alias用于添加别名,如果要让styles指向assets/styles,只需添加

styles: resolve('src/assets/styles')

main.js导入样式文件可以写成
main.js

import 'styles/reset.css'
import 'styles/border.css'
import 'styles/iconfont.css'
//import './assets/styles/iconfont.css'

组件中导入scss文件写成
src/pages/home/components/Header.vue

@import '~styles/_variables';

重启服务器后生效

新建项目分支

在实际项目开发过程中,每开发一个新功能都会创建一个新的分支,功能开发完成之后再合并到主分支

//提交主分支
git add .
git commit -m 'header finished'
git push
//创建新分支
git checkout -b index-swiper
使用轮播插件
npm install swiper vue-awesome-swiper --save

main.js

import VueAwesomeSwiper from 'vue-awesome-swiper'

// import style
import 'swiper/css/swiper.css'

Vue.use(VueAwesomeSwiper, /* { default options with global component } */)
解决自动轮播不生效问题
swiperOptions: {
  observer: true, //修改swiper自己或子元素时,自动初始化swiper
  observeParents: true, //修改swiper的父元素时,自动初始化swiper
  loop: true,
  autoplay: {
    delay: 3000
  },
  pagination: {
    el: '.pagination-home'
  }
  // Some Swiper option/callback...
},
解决页面抖动问题

加载页面时,图片在文字之后加载,会造成文字刚开始占用图片的位置,之后又被图片挤开。
解决办法:图片外面加一层div

<div class="wrapper">
...
</div>
.wrapper {
  overflow: hidden;
  width: 100%;
  height: 0;
  //padding 百分比相对于父元素的宽度,这里是屏幕宽度,31.25%是图片实际高度/图片宽度
  padding-bottom: 31.25%;
  .swiper-img {
    width: 100%;
  }
}
上传分支代码
git add .
git commit -m 'swiper finished'
//第一次上传分支
git push -u origin index-swiper
//git push
合并到主分支
//切换到主分支
git checkout master
//把线上分支合并到本地分支
git merge origin/index-swiper
//提交master分支
git push
文字超出部分显示省略号

assets/styles文件夹下新建_mixin.scss,该文件主要写重复使用的样式

@mixin ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

使用

@import '~styles/mixin';
.icon-desc {
  @include ellipsis;
}

注意:1. 文件名以下划线开头,但import时不带下划线。

  1. 如果是flex布局,且元素flex:1,加上min-width: 0;确保该子元素不超过外层容器
.item-info {
  flex: 1;
  padding: 0.1rem;
  min-width: 0;
}
发送ajax请求
npm install axios --save

一个页面可能有多个组件组成,如果每个组件都需要获取服务器的数据,这样效率比较低下,推荐的做法是在父组件发送请求。
Home.vue

import axios from 'axios'

created() {
  this.getHomeInfo()
},
methods: {
//模拟请求数据
  getHomeInfo() {
    axios.get('/api/index.json').then(this.getHomeInfoSucc)
  },
  getHomeInfoSucc(res) {
    console.log(res)
  }
}
mock

在没有后端支持情况下,需要axios模拟请求数据,这就要用到mock
在static文件夹下新建mock目录,所有请求的数据放在该文件夹下。
static/mock/index.json

{
  "status": "ok"
}

修改Home.vue

getHomeInfo() {
  axios.get('/static/mock/index.json').then(this.getHomeInfoSucc)
},

static目录下的文件可以在浏览器通过路径直接访问,一般我们不希望其发布到线上,可以为其添加gitignore
.gitignore

static/mock
配置转发

需求:经过上面的步骤,我们已经可以通过获得数据,但也引入另一个问题,即服务器端api地址和模拟请求的地址不一致,项目上线时需要考虑api地址变更,webpack提供了解决这个问题的办法。
config/index.js

  dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
   //修改部分
    proxyTable: {
      '/api': {
        target: 'http://localhost:8080',
        pathRewrite: {
          // 请求以/api开头的转发到/static/mock
          '^/api': '/static/mock'
        }
      }
    },
...
}

proxyTable下配置路由转发
现在已经可以通过/api/访问接口了

getHomeInfo() {
  axios.get('/api/index.json').then(this.getHomeInfoSucc)
}
解决组件渲染没有数据问题
<swiper :options="swiperOptions" v-if="swiperList.length">
  <swiper-slide v-for="item of swiperList" :key="item.id"><img class="swiper-img" :src="item.imgUrl" alt="" /> </swiper-slide>
  <div class="swiper-pagination" slot="pagination"></div>
</swiper>

swiper创建时数据还没有生成,这个时候需要加上v-if="swiperList.length"表示获得数据之后才会渲染这个组件
改进:虽然这样可以解决问题,但我们建议模板中尽可能减少逻辑代码的出现。使用computed来解决这个问题

computed: {
  showSwiper() {
    return this.swiperList.length
  }
}

修改tempate

<swiper :options="swiperOptions" v-if="showSwiper">
...
</swiper>

优化滑动
npm install better-scroll --save

使用该组件需要dom结构满足一定条件

<div class="list" ref="wrapper">
  <div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</div>
import BScroll from 'better-scroll'

mounted() {
  this.scroll = new BScroll(this.$refs.wrapper)
}
实现父子组件联动

gif

Alphabet.vue(子组件)

<template>
  <ul class="list">
    <li
      class="item"
      v-for="item of letters"
      :key="item"
      @click="handleLetterClick"
      @touchstart.stop.prevent="handleTouchStart"
      @touchmove.stop.prevent="handleTouchMove"
      @touchend.stop.prevent="handleTouchEnd"
      :ref="item"
    >
      {{ item }}
    </li>
  </ul>
</template>
  1. @click="handleLetterClick"监听点击了哪个letter,通知父组件letter改变,父组件再通知city-list组件letter改变(借助父组件实现兄弟组件通信)
  2. touchstart、touchmove、touchend监听触摸事件
updated() {
  // update在获取数据后被执行
  // 获取'A'元素距离其父元素上边缘的距离
  this.startY = this.$refs['A'][0].offsetTop
},
methods: {
  handleLetterClick(e) {
    this.$emit('change', e.target.innerText)
  },
  handleTouchStart() {
    this.touchStatus = true
  },
  handleTouchMove(e) {
    if (this.touchStatus) {
      // e.touches[0].clientY表示鼠标指针距屏幕顶部的距离
      // 79为导航栏的高度
      const touchY = e.touches[0].clientY - 79
      // 鼠标经过字符下标
      const index = Math.floor((touchY - this.startY) / 20)
      if (index >= 0 && index < this.letters.length) {
        this.$emit('change', this.letters[index])
      }
    }
  },
  handleTouchEnd() {
    this.touchStatus = false
  }
}

City.vue(父组件)

<city-list :cities="cities" :hotCities="hotCities" :letter="letter"></city-list>
<city-alphabet :cities="cities" @change="handleLetterChange"></city-alphabet>

handleLetterChange(letter) {
  this.letter = letter
}

List.vue(子组件)

watch: {
  letter(newVal, oldVal) {
    if (newVal) {
      const element = this.$refs[newVal][0]
      this.scroll.scrollToElement(element)
    }
  }
}

监听父组件传过来的letter的变化,滚动到屏幕相应位置
优化:手指移动的速度很快,可以通过设置定时函数延迟响应滑动事件

搜索

Search.vue

<template>
  <div>
    <div class="search">
      <input v-model="keyword" class="search-input" type="text" placeholder="输入城市名或拼音" />
    </div>
    <!-- keyword有值时显示 -->
    <div class="search-content" ref="search" v-show="keyword">
      <ul>
        <li class="serach-item" v-for="(item, index) of list" :key="index">{{ item.name }}</li>
        <!-- 没有查询结果时显示 -->
        <li class="serach-item" v-show="hasNoData">没有找到匹配数据</li>
      </ul>
    </div>
  </div>
</template>
computed: {
 //判断是否有数据
  hasNoData() {
    return !this.list.length
  }
},
watch: {
  keyword(newVal, oldVal) {
    if (this.timer) {
      clearTimeout(this.timer)
    }
    // 关键字为空,关闭搜索结果页
    if (!newVal) {
      this.list = []
      return
    }
    // 监听关键字变化,显示查询
    const result = []
    for (let i in this.cities) {
      this.cities[i].forEach(value => {
        if (value.spell.indexOf(newVal) > -1 || value.name.indexOf(newVal) > -1) {
          result.push(value)
        }
      })
    }
    this.list = result

    // this.timer = setTimeout(() => {
    //   const result = []
    //   for (let i in this.cities) {
    //     this.cities[i].forEach(value => {
    //       if (value.spell.indexOf(newVal) > -1 || value.name.indexOf(newVal) > -1) {
    //         result.push(value)
    //       }
    //     })
    //   }
    //   this.list = result
    // }, 100)
  }
},
没有共同父组件的组件通信Vuex
npm install vuex --save

main.js

//store
//import会自动寻找目录下的index.js
import store from './store'

new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    city: '北京'
  },
  // actions调用mutation改变数据
  actions: {
    changeCity(ctx, city) {
      // 调用mutations中changeCity的方法
      ctx.commit('changeCity', city)
    }
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
    }
  }
})

获取store中的数据

<div class="header-right">
  {{ this.$store.state.city }}
  <span class="iconfont iconjiantouxia arrow-icon"></span>
</div>

触发改变store中的数据的函数

<div class="button-wrapper" v-for="item of hotCities" :key="item.id" @click="handleCityClick(item.name)">


handleCityClick(city) {
  // 派发changeCity的Action
  this.$store.dispatch('changeCity', city)
  // 不经过action的写法
  this.$store.commit('changeCity', city)
}
页面跳转
  1. 链接式
<router-link to="/"></router-link>
  1. 编程式
this.$router.push('/')
router-link
<router-link :to="'/detail/' + item.id" tag="li" class="item" v-for="item of recommendList" :key="'recommend' + item.id">
...
</router-link>

<router-link>标签默认情况下渲染为一个a标签,可以通过增加tag属性指定希望渲染成的标签,如tag="li"会最终渲染为li标签

路由传递参数

router/index.js

export default new Router({
  routes: [
...
    {
      path: '/detail/:id',
      name: 'Detail',
      component: Detail
    }
  ]
})

Recommend.vue

<router-link :to="'/detail/' + item.id" tag="li" class="item" v-for="item of recommendList" :key="'recommend' + item.id">
...
</router-link>
全局组件

在src目录下新建common目录,用于存放全局公用组件
修改webpack.base.conf.js
增加common的别名

resolve: {
  extensions: ['.js', '.vue', '.json'],
  alias: {
    vue$: 'vue/dist/vue.esm.js',
    '@': resolve('src'),
    styles: resolve('src/assets/styles'),
    common: resolve('src/common')
  }
},
样式覆盖

遇到组件不能覆盖另一组件的样式,就需要用样式穿透,在scss中可以用>>>或者/deep/

.container >>> .swiper-container {
  overflow: inherit;
}
props

props里的属性的默认值可以是函数

props: {
  imgs: {
    type: Array,
    default() {
      return ['a','b']
    }
  }
}
解决swiper插件显示问题

如图,swiper不能显示正确的下标,是因为渲染这个组件后其父元素是隐藏的,当它显示时就会出现这个问题,解决办法是在让swiper监听父元素变化

data() {
  return {
    swiperOptions: {
      pagination: {
        el: '.swiper-pagination',
        type: 'fraction'
      },
    // 监听父元素变化
      observeParents: true,
      observer: true,

      loop: true

      // Some Swiper option/callback...
    }
  }
}
Vue点击事件
实现效果

我们要实现点击黑色区域返回首页,而点击图片不做任何响应

<div class="container" @click.self.prevent="handleGalleryClick">
...
</div>

@click.self.prevent表示只监听元素自身的点击事件,@click.prevent.self刚好相反,点击自身无效,而点击其子元素会被监听到。

监听屏幕滚动事件
methods: {
  handleScroll() {
    // 滚动的距离
    const top = document.documentElement.scrollTop

    // 距离顶部60开始隐藏
    if (top > 60) {
      let opacity = top / 140
      opacity = opacity > 1 ? 1 : opacity
      this.showAbs = false
      this.opacityStyle = {
        opacity
      }
    } else {
      this.showAbs = true
    }
  }
},
mounted() {
  window.addEventListener('scroll', this.handleScroll)
}

监听屏幕事件必须在mounted()方法中写window.addEventListener('scroll', this.handleScroll)表示监听屏幕滚动,并指定处理函数

对全局事件的解绑(重要)

在我们编写代码的过程中,如果没有对事件进行及时的解绑,可能影响程序性能或造成错误。如下所示,我们在一个组件监听window对象,当这个组件销毁后,这个监听仍在继续。这显然是不合理的。

mounted() {
  window.addEventListener('scroll', this.handleScroll)
}

改进:在组件销毁前解绑事件

mounted() {
  window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
  window.removeEventListener('scroll', this.handleScroll)
}
使用递归组件

在组件内部调用组件自身
使用递归组件必须满足一个条件,即组件需定义好了name属性,根据name可以调用自身

<template>
  <div>
    <div class="item" v-for="(item, index) of list" :key="index">
      <div class="item-title">
        <span class="item-title-icon"></span>
        {{ item.title }}
      </div>
      <div v-if="item.children">
        <detail-list :list="item.children"></detail-list>
      </div>
    </div>
  </div>
</template>
export default {
  name: 'DetailList',
  props: {
    list: {
      type: Array
    }
  }
}
组件缓存

如果我们不希望某些页面被缓存下来,而是每次进入都重新获取数据

<template>
  <div id="app">
    <keep-alive exclude="Detail">
      <router-view />
    </keep-alive>
  </div>
</template>

exclude="Detail"name=Detail的组件排除在缓存之外

小结:组件name的三个用途
  1. 用于递归组件
  2. 用于Vue调试工具
  3. 用于keep-alive排除组件缓存
解决打开页面时页面停留在底部

router/index.js
scrollBehavior函数会在每次切换页面时执行

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
...
  ],
  // 当进行页面切换时,x、y轴初始是0
  scrollBehavior(to, form, savedPosition) {
    return { x: 0, y: 0 }
  }
})
添加动画

common目录下新建fade目录
common/fade/Fade.vue

<template>
  <transition>
    <slot></slot>
  </transition>
</template>

<script>
export default {
  name: 'Fade'
}
</script>

<style lang="scss" scoped>
.v-enter,
.v-leave-to {
  opacity: 0;
}
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s;
}
</style>

<transition>包裹需要动画效果的元素,Vue会自动添加一些类名
使用

<fade-animation>
  <common-gallery :imgs="bannerImgs" v-show="showGallery" @close="handleGalleryClose"></common-gallery>
</fade-animation>
Vue项目的接口联调

config/index.js

  dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {
      '/api': {
        // target: 'http://localhost:8080',
        target: 'http://localhost',
        // pathRewrite: {
          // 请求以/api开头的转发到/static/mock
          // '^/api': '/static/mock'
        // }
      }
    },
...
}

'/api':开头的请求会被转发到本地80端口,localhost在上线阶段应更改为服务器地址或域名

Vue项目的测试

ifconfig

inet 192.168.1.103

该地址是本机在内网的ip地址
假设我们的Vue项目运行在8080端口,当访问192.168.1.103:8080时,会访问失败,因为webpack屏蔽了通过ip地址访问,如果要解除这个限制,修改package.json文件,加上--host 0.0.0.0

"scripts": {
  "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
  "start": "npm run dev",
  "lint": "eslint --ext .js,.vue src",
  "build": "node build/build.js"
},

这个时候,如果手机和电脑在同一局域网下,就可以通过192.168.1.103:8080访问页面了

解决拖动字母屏幕跟着滑动

Alphabet.vue

@touchstart.prevent="handleTouchStart"

.prevent可以解决这个问题

解决浏览器不支持promise
npm install babel-polyfill --save

main.js

import 'babel-polyfill'
Vue项目的打包上线
  1. 运行打包命令
npm run build

在项目根目录会自动生成dist文件夹,该文件夹的内容是项目最终上线放到服务器的内容
如果不想生成map文件,修改config/index.js

productionSourceMap: false,

重新打包即可
2 . 将dist文件夹下内容上传至服务器
将dist文件夹下的内容放到服务器网站目录根路径下


目录结构
  1. api是后端代码
  2. staticindex.html是打包后dist文件夹的内容

这个时候,网站可以通过localhost(本地配置了php或nginx)或服务器ip访问

通过ip加访问路径访问

我们希望通过ip地址/project访问服务器,需要做如下修改

  1. 修改webpack配置assetsPublicPath: '/project',
    config/index.js
build: {
  // Template for index.html
  index: path.resolve(__dirname, '../dist/index.html'),

  // Paths
  assetsRoot: path.resolve(__dirname, '../dist'),
  assetsSubDirectory: 'static',
  // assetsPublicPath: '/',
  assetsPublicPath: '/project',
...
}
  1. 网站根目录下新建project目录
  2. 重新打包,把dist文件夹下的文件放到刚才新建的project目录
异步组件

打包生成的app.js包含所有页面的业务逻辑代码,默认情况下它会在浏览器第一次请求页面时全部加载,显然这样不是很合理,我们希望访问某个页面时,只加载这个页面的逻辑

  1. router/index.js
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import('@/pages/home/Home')
    },
    {
      path: '/city',
      name: 'City',
      component: () => import('@/pages/city/City')
    },
    {
      path: '/detail/:id',
      name: 'Detail',
      component: () => import('@/pages/detail/Detail')
    }
  ],
  // 当进行页面切换时,x、y轴初始是0
  scrollBehavior(to, form, savedPosition) {
    return { x: 0, y: 0 }
  }
})
  1. 组件中异步加载其他组件
components: {
  HomeHeader: () => import('./components/Header'),
...
},

component: () => import('@/pages/home/Home')解决了按需加载问题
使用异步组件需要考虑的问题:

  1. 异步组件可以减少首次加载时间,但在app.js文件本身较小时效果不明显
  2. 异步组件缺点是会增加网络请求

考虑以上因素,在app.js文件较小时不使用异步组件

后续

Vue插件、自定义指令
vue-router路由守卫等
vuex
Vue服务器端渲染
Vue插件
官方
第三方整理

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

推荐阅读更多精彩内容