mall 項目隨筆

項目介紹

技術棧

  • Vue2.0 (核心框架)
  • Vue-CLI 4.0 (Vue腳手架)
  • Vue-Router (SPA頁面路由)
  • Vuex (狀態管理)
  • Axios (網絡請求)
  • ES 6 (JavaScript 語言的下一代標準)
  • Less (CSS 預處理器)
  • Better-Scroll (讓移動端的滾動更為流暢)
  • FastClick (解決移動端點擊300ms延遲)
  • Vue-Lazyload (懶加載工具)
  • PostCss (css代碼轉化工具)

在線預覽

GitHub 地址

初始化項目

通過 Vue-CLI 4.2.3 創建項目

vue create mall

目錄劃分及相關配置

劃分目錄結構 (父級目錄為 src)

  • assets: 創建 img、css 文件夾
  • common: 存放一些公共的 JS 文件, 例如公共的常量方法工具類
  • components: 存放一些公共的組件, 這里還可以分成兩個文件: common 和 content
    • common: 存放一些完全公共的組件, 完全獨立的組件內容, 即使存放在下一個項目也能用的組件
    • content: 對本項目業務來說是公共的, 存放在下一個項目里時不能使用的組件
  • views: 主要存放一些視圖的相關業務和代碼
  • router: 存放一些路由相關的代碼
  • store: 存放一些 Vuex 公共狀態管理相關的內容
  • network: 存放一些網絡相關的代碼

引入兩個初始化 CSS 文件 (父級目錄為 assets/css)

  • 初始化 CSS 文件, 讓樣式在各大瀏覽器顯示統一的樣式
    • 創建一個 normalize.css 文件, 這里推薦使用 normalize
    • 也可以通過 npm install normalize.css 來進行下載
  • 創建一個 base.css 文件用來對項目進行統一初始化
    • 在這個文件里引用 normalize 文件, 然后再在 App.vue 文件內引入這個文件

base.css 文件

@import './normalize.css';

App.vue 文件

@import './assets/css/base.css';

路徑配置別名

項目根目錄下創建一個 vue.config.js 配置文件, 到時候會將這個文件和公共配置進行一個合并

module.exports = {
  configureWebpack: {   // 表明你要配置的是哪個配置文件
    resolve: {          // resolve 可以解決一些路徑相關的問題
      alias: {          // 配置別名
        // '@': 'src' 默認已經配置了這個別名
        'assets': '@/assets',
        'common': '@/common',
        'components': '@/components',
        'network': '@/network',
        'store': '@/store',
        'views': '@/views'
      }
    }
  }
}

統一代碼風格

項目根目錄下創建一個 .editorconfig 配置文件, 統一代碼風格

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

路徑問題

上面我們已經為路徑配置了別名, 但在使用時應注意以下幾點:

  • 在 JS 中使用可直接使用別名
    import 'components/HelloWorld.vue'
    
  • 含有 src、href 等路徑屬性時需在其別名前加上 ~
    <img src='~asstes/logo.png'>
    

公共組件

制作前要想好組件是否可復用, 是完全公共的組件還是僅項目公共組件

完全公共組件

tabbar : 頁面底部切換組件

image

navbar : 頂部導航

image
image

swiper : 輪播圖

image

toast : 提示框

image

scroll : better-scroll 組件

僅項目公共組件

mainTabBar : 使用 tabbar 插槽的組件

image

tabControl : 分類菜單

image

backTop : 回到頂部按鈕

image

goods : 商品展示

image

tabControl 的下拉吸頂效果

  1. 獲取到 tabControl 的 offsetTop
    • 必須知道滾動到多少時, 開始有吸頂效果, 這個時候就需要獲取距離頂部的距離是多少
    • 如果直接獲取到 tabControl 的 offsetTop 的值是不正確的, 因為圖片加載比較慢的原因
    • 監聽 HomeSwipper(輪播圖) 中的任意一個 img 的加載完成后發出自定義事件, 在 Home.vue 監聽事件后獲取正確的值 this.$refs.tabControl.$el.offsetTop
  2. 判斷滾動的距離為元素添加 fixed 樣式
    • 但是 better-scroll 是通過改變 translate 來實現滾動的, fixed 樣式依然會被滾到上面, 所以這個方法不管用
  3. 通過復制一個相同的組件, 放在 better-scroll 外面, 默認隱藏, 當組件重疊的時候顯示, 并設置 層級(z-index) 就可以了
  4. 這里有一個問題, 兩個組件的點擊事件是不同步的, 要解決這個問題只需要在點擊事件里讓這兩個組件的當前狀態的值一致就可以了

backTop

點擊回到頂部, 這里設置整個組件為點擊事件, 一般情況下直接為組件添加原生事件是不行的, 可以使用修飾符 .native 來實現綁定原生事件

<back-top @click="backClick" />  // 這樣是沒有效果的
<back-top @click.native="backClick" />  // 有效果

使用 better-scroll 對象里的方法 scrollTo(0,0) 來實現回到頁面的頂部

這里直接在滾動組件 Scroll.vue 里封裝了一個 scrollTo 方法

/**
 * 設置跳轉位置, 默認跳轉時間300ms
 */
scrollTo(x, y, time = 300) {
  this.scroll && this.scroll.scrollTo && this.scroll.scrollTo(x, y, time);
},

點擊事件

<scroll ref="scroll">
  滾動的組件
</scroll>
<back-top @click.native="backTop" v-show="isShowBackTop" />

/**
  * 回到頂部
  */
backTop() {
  this.$refs.scroll.scrollTo(0, 0);
},

/**
  * 監聽 better-scroll 的滾動事件
  * 1. 顯示/隱藏backTop
  * 2. 是否吸頂tabControl
  */
contentScroll(position) {
  // 判斷BackTop是否顯示
  this.listenerShowBackTop(position.y);

  // 決定tabControl是否吸頂(position: fixed)
  this.isTabFixed = Math.abs(position.y) >= this.tabOffsetTop;
}

/**
  * 顯示/隱藏BackTop
  */
listenerShowBackTop(positionY) {
  this.isShowBackTop = Math.abs(positionY) >= BACK_POSITION;
}

this.$refs.scroll 獲取的就是滾動組件里的 scroll 對象, 然后直接調用里面定義的方法就可以了

better-scroll

入門

這里使用的原生的滾動效果, 在手機上使用可能會有延遲感, 卡頓感, 給用戶的體驗并不是很好, 所以推薦使用 Better-Scroll

image

Better-Scroll 是作用在外層 wrapper 容器上的, 滾動的部分是 content 元素

注意

  • wrapper 必須定高, 并且設置 overflow: hidden
  • Better-Scroll 只處理容器(wrapper)的第一個子元素(content)的滾動, 其它的元素都會被忽略

某些情況下, 我們希望 wrapper 高度自適應, 例如本項目中 頂部導航欄和底部導航欄高度固定, 中間可滾動區域的 wrapper 高度自適應, 那么可以采取以下方案

/* .scroll-content的父元素 */
#home {
  position: relative;
  height: 100vh;
}

.scroll-content {
  position: absolute;
  top: 44px;
  bottom: 49px;
  left: 0;
  right: 0;
  overflow: hidden;
}

最簡單的初始化代碼如下

import BScroll from 'better-scroll'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper)

Better-Scroll 提供了一個類, 實例化的第一個參數是一個原生的 DOM 對象
當然, 如果傳遞的是一個字符串, Better-Scroll 內部會嘗試調用 querySelector 去獲取這個 DOM 對象

如果是在 Vue 中使用, 推薦使用 ref 的方式拿到 DOM 對象, 防止類名相同而拿不到對象

  • ref 如果是綁定在組件中的, 那么通過 this.$refs.refname 獲取到的是一個組件對象
  • ref 如果是綁定在普通的元素中, 那么通過 this.$refs.refname 獲取到的是一個元素對象

監聽事件

默認情況下 BScroll 是不可以實時的監聽滾動位置, 如果你想監聽滾動, 可以傳遞第二個參數

import BScroll from 'better-scroll'

let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper, {
  probeType: 3,
  pullUpLoad: true,
  click: true
})

scroll.on('scroll', (position) => {
  console.log(position) // 這里就可以打印監聽的滾動的位置了
})
          
scroll.on('pullingUp', () => {
  console.log('上拉加載更多')

  //scroll.finishPullUp()
  setTimeout(() => {
    scroll.finishPullUp()
  }, 2000)
})

probeType : 偵測類型

  • 這里可以傳遞的參數有 0 、1 、2 、3
  • 0 和 1 都是不偵測實時的位置
  • 2 是在手指滾動的過程中偵測, 手指離開后的慣性滾動過程中不偵測
  • 3 是只要是滾動都會偵測

pullUpLoad : 監聽滾動到底部事件

  • 默認只會觸發一次, 如果想多次觸發, 必須要在每次觸發事件后調用 scroll.finishPullUp() 來結束這次事件, 這樣就可以進行多次監聽滾動到底部事件了
  • 如果不想太過頻繁的觸發事件, 可以將調用包裹在一個定時器中

click : 監聽點擊事件

  • 如果滑動區域內有除了 button 按鈕以外的點擊事件, 要加上這個才能點擊, 否則點擊事件會失效
  • button 按鈕無論該屬性為 true | false 都會生效

封裝

這里用的是 @1.13.2 版本的, 如果是 @2.0 版本以上的要參考官方的方式

在 Vue 中使用的封裝

<template>
  <div class="wrapper" ref="wrapper">
    <div class="content">
      <slot></slot>
    </div>
  </div>
</template>

<script>
import BScroll from 'better-scroll'

export default {
  name: 'Scroll',
  props: {
    // 由使用者決定偵測類型和是否監聽滾動到底部事件
    probeType: {
      type: Number,
      default: 0
    },
    pullUpLoad: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return { 
      scroll: null 
    }
  },
  mounted() {
    // 創建 BScroll 對象
    this.scroll = new BScroll(this.$refs.wrapper, {
      click: true,
      probeType: this.probeType,
      pullUpLoad: this.pullUpLoad
    });
    // 監聽滾動的位置
    if (this.probeType == 2 || this.probeType == 3) {
      this.scroll.on("scroll", position => {
        this.$emit("scroll", position);
      })
    }

    // 監聽scroll滾動到底部
    if (this.pullUpLoad) {
      this.scroll.on("pullingUp", () => {
        this.$emit("pullingUp");
      })
    }
  },
  methods: {
    /**
     * 設置跳轉位置
     */
    scrollTo(x, y, time = 300) {
      this.scroll && this.scroll.scrollTo && this.scroll.scrollTo(x, y, time);
    },

    /**
     * 刷新底部上拉事件
     */
    finishPullUp() {
      this.scroll && this.scroll.finishPullUp && this.scroll.finishPullUp();
    },

    /**
     * 刷新scroll可滾動高度
     */
    refresh() {
      this.scroll && this.scroll.refresh && this.scroll.refresh();
    },

    /**
     * 獲取當前scroll的y值
     */
    getScrollY() {
      return this.scroll.y ? this.scroll.y : 0;
    }
  }
}
</script>

使用

使用時將封裝好的組件導入, 并將要滑動的區域用標簽包裹起來

<scroll 
  class="scroll-content" 
  ref="scroll" 
  :probe-type="3" 
  :pull-up-load="true" 
  @scroll="contentScroll" 
  @pullingUp="loadMore">
  <div>
     需要包裹的內容
  </div>
</scroll>

better-scroll 有時不能滾動 bug

image

better-scroll 對象的 scrollerHeight 方法里面記錄了可滾動內容的高度, 這個屬性是根據放在 content 中的子組件的高度來決定的, 但是在剛開始計算 scrollerHeight 屬性時, 由于圖片加載比較慢, 所以沒有將圖片高度計算在內, 所以得到的可滾動高度是錯誤的, 后面圖片加載進來之后高度被撐開了, 但是 scrollerHeight 屬性并沒有進行更新, 所以滾動出現了問題

解決方案:

監聽每一張圖片是否加載完成, 只要有一張圖片加載完成, 就執行一次 refresh()

  • 原生的 JS 監聽圖片加載完成的方式: img.onload = function() {}
  • Vue 中監聽: @load=imageLoad, 這里是非父子組件通信
    1. 通過 Vuex 傳遞方法
    2. 通過 事件總線 $bus 的方式
      • 因為有多個頁面都用到 better-scroll, 為了方便管理, 這里使用事件總線 $bus 的方式傳遞方法
  1. 在 (main.js) Vue 原型上添加 $bus
    Vue.prototype.$bus = new Vue()
    
  2. 將方法發送到 $bus 中
    imageLoad() { this.$bus.$emit('itemImageLoad') }
    
  3. 通過 $bus 監聽圖片加載完成, 并調用 refresh
    this.$bus.$on('itemImageLoad', () => { 調用refresh })
    

$bus 取消事件監聽

this.$bus.$off('方法名', '對應的處理函數')

防抖

每張圖片加載完之后都會立刻調用一次 refresh, 這對于性能上來說無異于是負擔, 所以, 通過防抖對性能進行優化

/**
 * 防抖
 */
function debounce(func, delay = 100) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func && func.apply(this, args);
    }, delay);
  }
}

解決移動端 URL 欄 和 底部工具欄 顯示/隱藏 時高度 Bug

Bug 原因

移動端下瀏覽器對 100vh 的定義不考慮 URL 欄 和 底部工具欄 的高度(無論顯示還是隱藏), 可以用下面這張圖直觀地體現問題

image

當地址欄可見時, 由于移動瀏覽器不正確地將 100vh 設置為屏幕高度而沒有顯示地址欄, 因此屏幕底部被切斷
在上圖中, 應該在屏幕底部的按鈕被隱藏了
更糟糕的是, 當用戶第一次使用手機訪問網站時, 地址欄會顯示在頁面頂部, 因此用戶體驗是很糟糕的

設置 home 高度也不能直接使用 100%, 因為 100% 是相對與父元素, 而 home 的父元素的高度又沒有固定, 而是依賴與 home 的高度撐開, 所以百分比無效

解決方案 (window.innerHeight)

解決這個問題的一種方法是依賴 JavaScript 而不是 CSS, 當頁面加載時, 將高度設置為 window.innerHeight 將正確地將高度設置為窗口的可見部分

使用 window.innerHeight 動態設置高度
當窗口大小改變時重新設置高度為 window.innerHeight, 因為 window.innerHeight 的高度不包括地址欄和工具欄

  • 如果地址欄是可見的, 那么 window.innerHeight 將是屏幕可見部分的高度, 正如你所期望的那樣
  • 如果地址欄是隱藏的, 那么 window.innerHeight 是全屏的高度
<template>
  <div id="home" :style="{ height: homeHeight }"><div>
</template>

<script>
export default {
  data() {
    return {
      homeHeight: window.innerHeight + 'px'
    }
  }
  mounted() {
    window.addEventListener("resize", () => {
      this.homeHeight = window.innerHeight + "px"
    })
  }
}
</script>

<style>
  #home {
    position: relative;
    /* height: 100vh */
  }
</style>

讓 Home 不銷毀(destroyed), 并在路由來回切換后回到離開時的位置

讓 home 不要隨意銷毀掉

添加 keep-alive 就可以了

讓 home 中的內容保持原來的位置

data() {
  return {
    saveY: 0
  }
},
activated() {
  // 當路由處于活躍狀態時, 將頁面回到離開時的位置, 且刷新一次 scroll 的高度
  this.$refs.scroll.scrollTo(0, this.saveY, 0)
  this.$refs.scroll.refresh()
},
deactivated() {
  // 當路由處于不活躍狀態時, 保存 scroll 的 y 值
  this.saveY = this.$refs.scroll.getScrollY()

  // 取消該路由的圖片加載事件監聽
  this.$bus.$off("itemImageLoad", this.itemImageListener);
}

詳情頁

this.$nextTick(() => {})created 中這個函數意思是: 等模板渲染完后就執行這個函數, 從這里就可以拿到一些數據, 這個時候對應的 DOM 已經報備渲染出來了, 但是圖片依然是沒有加載完

image

一定要將詳情頁銷毀

<keep-alive exclude="Detail">
  <router-view />
</keep-alive>

如何判斷一個對象是不是一個空的對象

const obj = {}
Object.keys(obj).length === 0

混入(mixin)的使用

創建混入對象: const mixin = {}

組件中導入: mixins: [mixin]

點擊標題,滾動到對應的主題

  • 獲取標題的 offsetTop
  • 在哪里才能獲取到正確的 offsetTop ?
    1. created 肯定不行, DOM 還沒渲染
    2. mounted 也不行, 圖片數據還沒有加載完
    3. nextTick 也不行, 雖然 DOM 改變觸發 nextTick 鉤子, 但圖片不一定加載完, 導致offsetTop是錯誤的值

方案一

created 中事先通過防抖獲得處理函數, 等待圖片加載完畢之后再調用該函數

created() {
  /**
  * 通過防抖獲得 getThemeTopY 函數, 等待圖片加載完之后再調用
  */
  this.getThemeTopY = debounce(() => {
    this.$nextTick(() => {
      this.themeTopYs = [];
      this.themeTopYs.push(0);
      this.themeTopYs.push(this.$refs.params.$el.offsetTop);
      this.themeTopYs.push(this.$refs.comment.$el.offsetTop);
      this.themeTopYs.push(this.$refs.recommend.$el.offsetTop);
    })
  }, 100);
},
methods: {
  /**
   * 刷新scroll高度, 且獲得各個標題的 offsetTop
   */
  detailImageLoad() {
    this.refresh();
    this.getThemeTopY();
  }
}

方案二

等待所有圖片加載完畢

methods: {
  detailImageLoad() {
    // 判斷所有的圖片都加載完了, 進行一次回調
    if (++this.counter === this.imageLength) {
      this.refresh();
      this.$emit("detailImageLoad");
    }
  }
}

vuex

mutations 唯一的目的就是修改 state 中狀態, 最好是其中的每個方法盡可能完成得事件比較單一一點, 否則每次執行的時候執行的方法名字一樣, 不知道到底執行的是哪個

如果有邏輯判斷推薦放到 actions 里, 執行的方法可以放到 mutations 里, 這樣就可以跟蹤每個想要調試的點

const store = new Vuex.Store({
  state: {
    cartList: []
  },
  mutations: {
    addCount(state, payload) {
      payload.count++
    },
    addToCart(state, payload) {
      state.cartList.unshift(payload)
    }
  },
  actions: {
    addToCart({ state, commit }, payload) {
      return new Promise((resolve, reject) => {
        let oldProduct = state.cartList.find(item => item.iid === payload.iid)

        if (oldProduct) {
          commit('addCount', oldProduct)
          resolve("當前商品已被添加到購物車+1")
        } else {
          payload.count = 1
          payload.checked = true
          commit('addToCart', payload)
          resolve("已添加至購物車")
        }
      })
    }
  }
})

目錄結構

建議分類成一個一個的文件, 這樣方便管理, 還可以封裝常量文件

index.js

import Vue from "vue";
import Vuex from "vuex";
import mutations from "./mutations";
import actions from "./actions";
import getters from "./getters"

Vue.use(Vuex);

const state = {
  cartList: []
}

export default new Vuex.Store({
  state,
  mutations,
  actions,
  getters
})

toast 插件封裝

components/common/toast 文件夾下新建兩個文件

  • index.js
  • Toast.vue

index.js

import Toast from "./Toast.vue"

export default {
  install(Vue) {
    const toastConstructor = Vue.extend(Toast);
    const toast = new toastConstructor();
    toast.$mount(document.createElement("div"));
    document.body.appendChild(toast.$el);
    Vue.prototype.$toast = toast;
  }
}

Toast.vue

<template>
  <div v-show="isShow" class="toast">
    <div>{{message}}</div>
  </div>
</template>

<script>
  export default {
    name: "Toast",
    data() {
      return {
        message: "",
        isShow: false
      }
    },
    methods: {
      show(message, duration = 2000) {
        this.isShow = true;
        this.message = message;

        setTimeout(() => {
          this.isShow = false;
          this.message = "";
        }, duration);
      }
    }
  };
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .toast {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background: rgba(0, 0, 0, .7);
    padding: 8px 10px;
    color: #fff;
    text-align: center;
    border-radius: 8px;
    z-index: 9999;
  }
</style>

main.js

import toast from './components/common/toast/index';

Vue.use(toast) // 這里會去執行 index.js 里的 install 方法

使用的時候, 只需要: this.$toast.show("需要顯示的文字", 2000) 就可以了

細節處理

FastClick

使用 FastClick 解決移動端點擊 300ms 的延遲

安裝

npm install fastclick --save

使用 (在 main.js 中安裝插件)

import FastClick from 'fastclick'

FastClick.attach(document.body)

圖片懶加載

圖片需要顯示在屏幕上時再加載

安裝

npm install vue-lazyload --save

使用 (在 main.js 中安裝插件)

import VueLazyLoad from 'vue-lazyload'

Vue.use(VueLazyLoad, {
  // 顯示占位圖
  loading: require('./assets/img/common/placeholder.jpg')
})
// 修改組件中 img 的屬性 :src => v-lazy

快捷修改 CSS 單位(適配不同設備)

項目直接是使用的 px 單位進行開發的, 這里改成 vm 單位

使用插件, 有很多類似的插件, 這里使用的 postcss-px-to-viewport, 這是開發時依賴

安裝

npm install postcss-px-to-viewport --save-dev

配置(在項目根目錄下創建 postcss.config.js 配置文件)

module.exports = {
  plugins: {
    autoprefixer: {},
    "postcss-px-to-viewport": {
      viewportWidth: 375,   // 視口寬度, 對應的是設計稿寬度
      viewportHeight: 667,  // 視口高度, 對應的是設計稿的高度
      unitPrecision: 5,     // 指定'px'轉換為視口單位值的小數位數(保留5位小數)
      viewportUnit: "vw",   // 指定需要轉換成的視口單位, 建議使用vw
      selectorBlackList: ["ignore"],   // 指定不需要轉換的類
      minPixelValue: 1,     // 小于或等于'1px'不轉換為視口單位
      mediaQuery: false,    // 允許在媒體查詢中轉換'px'
      exclude: [/TabMenu\.vue/] // 排除文件名包含 TabBar 的文件,必須是正則來匹配文件
    }
  }
}

這樣項目中所有的 px 單位就會變成 vm 單位

項目部署到遠程服務器

使用 webpack 打包項目

npm run build

使用服務器軟件: tomcat、nginx, 這里使用 nginx

將 build 文件中的所有文件、文件夾、圖片拷貝到站點根目錄下

刷新頁面 404

問題
將項目部署到遠程服務器上后, 在頁面中一旦刷新, 會出現 404

原因

使用 history 模式時, 還需要后臺配置支持
因為我們的應用是個單頁客戶端應用, 如果后臺沒有正確的配置, 當直接訪問 http://mall.coderlion.com/home 就會報 404 的錯誤

所以需要在服務端增加一個覆蓋所有情況的候選資源: 如果 URL 匹配不到任何靜態資源, 則應該返回同一個 index.html 頁面, 這個頁面就是 home 頁面

解決方案

為 nginx 服務器添加重定向配置

location / {
  try_files $uri $uri/ /index.html;
}

其他服務器配置參照官方文檔

Vue 響應式原理

  1. 當數據發生修改時, Vue 內部是如何監聽 message 數據的改變
    • Object.defineProperty -> 監聽對象屬性的改變
  2. 當數據發生改變, Vue 是如何知道要通知那些人, 界面發生刷新
    • 發布訂閱者模式
image
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容