vue-element-ui-Cascader 级联选择器支持多选---折腾记

重大更新

最新版本的element已经有级联多选功能了

前沿


吐槽一下,程序猿最不愿听到的话之一,(人家某某网站就做出来了,你怎么做不出来,简直丧心病狂)小编最近一直在开发基于vue-elementui的pc端项目,就碰到了来自产品的这句话,都有种拿起显示器了。不过吐槽归吐槽,项目还是要写的。。。。。。在本项目中产品提的一个需求,就是人家某某网站上有的,而element-ui上没有,那就是Cascader 级联选择器,element-ui只支持单选,于是就开始了折腾,再折腾了快一周的时间吧,还是没搞出来,最后由于项目着急上线,只能暂时先放弃,所以就先搁置了,后来幸得于空,于是乎又是开始折腾,毕竟也是自己的问题。哎,不说了,show time.

该多选菜单基于 element-ui 的Cascader 层级菜单, 但是在我的一番折腾下开发出一套支持多选的,有禁用状态,以及灵活控制选几个,适应产品的奇葩需求,Cascader 层级菜单。羞于一提的是,我折腾了整整3天,才搞出来。在这里把我的心路历程记录下来,里边的注释写的个人感觉都挺全的,有不明白的也可以与我交流,共同探讨,方便后续学习与扩展。
先上个效果图


微信图片_20181012184700.png

现附上该插件的菜单配置项,以方便后期维护

attributes属性说明

属性名 描述 类型 默认值
width 菜单选择面板的宽度 String 220px
height 菜单选择面板的高度 String 240px
options 选择器菜单数据配置项 Array []
inputValue 选择器输入框内显示的内容 String 220px
outputType 选中项输出的字段名,outputType 用于输出选中选择项对象的某一字段, 默认值: value, 当传入 outputType 为item时, 输出选中这一项的对象(不包括 children 属性); String value
disabledPair 互斥选项对儿,就是选择一个其他的就被禁用 Object --

事件名称

事件名称 说明 回调参数
on-selected 选择器中的某一项被选中的时候触发的事件 数组,数组内包含被选中的值

options 菜单配置,就是完全按照elementui Cascader 的options的格式

属性名 描述 类型
value 选项的值 String or Number
label 选项的名称 String
checked 该选项是否被选中 Boolean
children 如果存在下一级菜单,是属于该选项的下一级选项值, 非必须 Array
multiple 是否多选 true为多选,false为单选
disabled 是否禁用 true为禁用,false为不禁用

再简单介绍一下disabledPair属性

disabledPair 用于设置禁用对, 对象形式, 接收两个属性: thisPair thatPair:

disabledPair: {
thisPair: [1], //这里的1是value的值
thatPair: [2],
}
那么, 当值为 1 的选项被选中的时候, 值为 2 的选项将会被禁用掉, 反之亦然。但其他选项的值不会受到影响 除了传递单独的项之外, 还可以单独传入一个 all。

disabledPair: {
thisPair: [1],
thatPair: ["all"]
}

首先,先建一个公共的文件夹MulitileCascader,里边包含有三个自己封装的文件

一,index.vue 此页面为主要出口文件,会发射出一个得到选中后的item的方法以及数组。

<template>
  <span class="dropTreeLists">
    <span class="benchmark">基准&nbsp;:</span>
    <multiCascader :options="configOptions"
                   @on-selected="getSelected"
                   :inputValue="configTips"></multiCascader>
  </span>
</template>
   <script>
import multiCascader from "./MulCheckCascader.vue";
//这个也是我们项目的接口,不必纠结,倒是换位自己的接口就好了
import { getlistBenchmark } from "@/api/basicManage";
export default {
  components: {
    multiCascader
  },
  data() {
    return {
      configTips: "请选择基准",
      //模板勿删
      configOptions: [
        {
          value: "1",
          label: "一级菜单",
          checked: false,  //控制是否默认选中
          multiple: false,   //是否多选   false为该一级菜单不多选,true表示多选
          children: [
            {
              value: 11,
              checked: false,
              multiple: false,
              disabled:true,    //是否禁用
               label: "二级菜单",
              children: [
                {
                  value: "21",
                  checked: false,
                  multiple: false,   //是否多选   false为该一级菜单不多选,true表示多选
                  disabled :true,    //是否禁用
                  label: "三级菜单1"
                },
                {
                  value: "22",
                  checked: false,
                  label: "三级菜单2"
                }
              ]
            },
            {
              value: "12",
              checked: false,
              multiple: false,
              label: "二级菜单",
              children: [
                {
                  value: "399300",
                  checked: true,
                  label: "三级菜单复制"
                },
                {
                  value: "399300",
                  checked: false,
                  label: "三级菜单"
                }
              ]
            }
          ]
        }
      ],
      commonLength: ""
    };
  },
  mounted() {
    this.MulitGetlistBenchmark(); //多选
  },
  methods: {
    // 点击每一个item的时候的操作   在这个方法内灵活判断多选的状态以及禁用状态
    getSelected(val) {
      let strnum = val.length;
      console.log(val);
      // 当选中的指数大于1并且小于10的时候让所有的指数都可以选择(没有禁用状态)
      if (val.length > 1 && val.length < 10) {
        this.LessThanThen(this.configOptions);
      }
      // 必须保留一个选中的
      if (val.length == 1) {
        let moreOne = val[0];
        this.LessThanMoreOne(this.configOptions, moreOne);
      }
      // 当选中的指数大于10的时候让除选中的之外的指数都变为禁用状态
      if (val.length >= 10) {
        let moreOne = val;
        this.LessThanMoreTen(this.configOptions, moreOne);
      }
      if (strnum !== this.commonLength) {
//将选中后的数组暴漏出去,在需要的页面使用
        this.$emit("CheckedsIndexCodes", val);
      }
      this.commonLength = val.length;
      // 勿删后期需求改变会用
      // this.selectGroups = val;
      // this.configTips = `已选择${val.length}个分组`;
    },
    // 此递归为当选中的指数大于10的时候让除选中的之外的指数都变为禁用状态
    LessThanMoreTen(datas, moreOne) {
      for (var i in datas) {
        if (datas[i].multiple !== false) {
          // console.log(datas[i]);
          datas[i].disabled = true;
          for (let d = 0; d < moreOne.length; d++) {
            if (datas[i].value == moreOne[d]) {
              datas[i].disabled = false;
            }
          }
        } else {
          this.LessThanMoreTen(datas[i].children, moreOne);
        }
      }
    },
    // 此递归为当选中的为选中的只剩下一个的时候禁止取消,也就是必须保留一个选中的
    LessThanMoreOne(datas, moreOne) {
      for (var i in datas) {
        if (datas[i].multiple !== false) {
          // console.log(datas[i]);
          if (datas[i].value == moreOne) {
            datas[i].disabled = true;
          }
        } else {
          this.LessThanMoreOne(datas[i].children, moreOne);
        }
      }
    },
    // 此递归为当选中的为  满足该条件时(val.length > 1 && val.length < 10)  所有的item的都可以选则
    LessThanThen(datas) {
      for (var i in datas) {
        if (datas[i].multiple !== false) {
          // console.log(datas[i]);
          datas[i].disabled = false;
        } else {
          this.LessThanThen(datas[i].children);
        }
      }
    },
    // 此递归为初始化时默认选中沪深300,由于只有一个所以禁用沪深300
    getArrayList(datas) {
      for (var i in datas) {
        if (datas[i].multiple !== false) {
          // console.log(datas[i]);
          datas[i].disabled = false;
          if (datas[i].value === "399300") {
            datas[i].disabled = true;
            datas[i].checked = true;
          }
        } else {
          // console.log(datas[i]);
          //每次在传入父节点的childreg去查找,自己调用自己的方法
          this.getArrayList(datas[i].children);
        }
      }
    },
    MulitGetlistBenchmark() {
//此接口为我们项目中的接口,上边有数据模板,可根据数据模板来写数据。
      getlistBenchmark({}).then(response => {
        this.configOptions = response.data.data;
        this.getArrayList(this.configOptions);
      });
    }
  }
};
</script>
   
   <style lang="scss" scoped>
.benchmark {
  font-size: 14px;
}
</style>

二,MulCheckCascader.vue //此页面为基础模板,会在该页面引用递归出来的多选的item的字模板,并且该页面会接受引用页面传过来的数据,方便灵活控制尺寸,数据,是否禁用等的状态。

<template lang='html'>
    <div class='multil-cascader'>
        <el-popover placement="top-start" popper-class="multi-cascader-popover" :visible-arrow="showArrow" trigger="click" @hide="whenPopoverHide" @show="whenPopoverShow">
            <muContent
                :height="height"
                :width="width"
                :option="options"
                @handleOutPut="whenOutPut"
                :selectedValues="selectedValues"
                :outputType="outputType"
                :disabledPair="disabledPair">
            </muContent>
            <el-input popper-class="slect-panel" v-if="activeItem[0] && activeItem[0].level === 0"  v-model="inputValue" readonly slot="reference" :suffix-icon="inputArrow"/>
        </el-popover>
    </div>
</template>

<script>
import muContent from "./multiContent";
export default {
    name: "multiCascader",
    props: {
        options: {
            type: Array,
            default() {
                return [];
            }
        },
        width: {
            type: String,
            default: ""
        },
        height: {
            type: String,
            default: ""
        },
        inputValue: {
            type: String,
            default() {
                return "";
            }
        },
        // 输出值的类型
        outputType: {
            type: String,
            default() {
                return "value";
            }
        },
        // 互斥对儿
        disabledPair: {
            type: Object,
            default() {
                return {};
            }
        }
    },
    data() {
        return {
            // 被选中的值
            selectedValues: [],
            showArrow: true,
            activeItem: [],
            outputValue: [],
            optionDicts: [],
            inputArrow: "el-icon-arrow-down",
            popoverWidth: "",
            // 展开之后的数组, 将每一个children 展开
            flatOptions: []
        };
    },
    watch: {
        "options": function () {
            this.initData();
        }
    },
    components: {
        muContent
    },
    created() {
        this.initData();
        this.setOptionDicts(this.options);
        this.toFlatOption(this.options);
    },
    methods: {
        whenPopoverHide() {
            this.inputArrow = "el-icon-arrow-down";
        },
        whenPopoverShow() {
            this.inputArrow = "el-icon-arrow-up";
        },
        // 初始化数据 对于每一项 options 添加相关字段并且获取到当前被点击到的元素
        initData() {
            this.setLevel();
            const { width, height } = this;
            const checkedValues = [];
            let childrenValues = [];
            const getChecked = (item) => {
                const { checked, value, children, level, siblingValues } = item;
                if (siblingValues) {
                    const tempValues = [...siblingValues];
                    item.siblingValues = tempValues;
                }
                childrenValues.push(value);
                if (children && children.length > 0) {
                    children.forEach(child => {
                        getChecked(child);
                    });
                } else {
                    if (checked && item[this.outputType]) checkedValues.push(item[this.outputType]);
                }
            };
            this.activeItem = this.options;            
            this.options.forEach(child => {
                getChecked(child);
                // 设置当前item 的 childrenValues, 包含当前item 下的所有值的 value
                child.childrenValues = [...childrenValues];
                childrenValues = [];
            });
            this.selectedValues = checkedValues;
            this.whenOutPut(this.selectedValues);
        },
        getTypeOptions(values, outputType) {
            const outputValues = [...values];
            const finalOutputArr = [];
            return this.flatOptions.reduce((pev, cur) => {
                const { value: curVal } = cur;
                if (outputType === "item") {
                    if (outputValues.includes(curVal)) pev.push(cur);
                } else {
                    if (outputValues.includes(curVal) && cur[outputType]) pev.push(cur[outputType]);
                }
                return pev;
            }, []);
        },
        // 展开配置中的各项, [{}], 排除 children 属性
        toFlatOption(option) {
            const getItems = (arr, cur) => {
                const keys = Object.keys(cur);
                const newObj = {};
                const curChild = cur.children;
                const hasChild = curChild && curChild.length > 0;          
                keys.forEach(key => key !== "children" && (newObj[key] = cur[key]));
                arr.push(newObj);
                return (hasChild ? curChild.reduce(getItems, arr) : arr);
            };
            this.flatOptions = option.reduce(getItems, []);
        },
        // 设置配置的字典
        setOptionDicts(options) {
            if (!Array.isArray(options)) {
                const { label, value } = options;
                this.optionDicts.push({ value, label });
                const children = options.children;
                if (children) {
                    this.setOptionDicts(children);
                }
            } else {
                options.forEach(opt => {
                    this.setOptionDicts(opt);
                });
            }
        },
        // 触发 on-selected 事件
        whenOutPut(value) {
            // 根据选中的值数组 value 输出特定 outputType 类型
            if (this.outputType !== "value") {
                this.outputValue = this.getTypeOptions(value, this.outputType);
            } else {
                this.outputValue = value;
            }
            this.$emit("on-selected", this.outputValue);
        },
        // 设定层级
        setLevel() {
            const siblingValues = [];
            let tempLevel = 0;
            if (this.options.length) {
                const addLevel = option => {
                    const optChild = option.children;
                    if (option.level === tempLevel) {
                        siblingValues.push(option.value);
                    }
                    if (optChild) {
                        optChild.forEach(opt => {
                            opt.level = option.level + 1;
                            addLevel(opt);
                        });
                    }
                };
                this.options.forEach(option => {
                    if (!option.level) {
                        option.level = 0;
                        tempLevel = option.level;
                    }
                    addLevel(option);
                    option.siblingValues = siblingValues;
                });
            }
        },
        showSecondLevel(item) {
            this.activeItem = item;
        }
    }
};
</script>
<style lang='scss' scoped>
.vk-menu-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    list-style-type: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
    outline: none;
    padding: 8px 20px;
    font-size: 14px;
    width: 100%;
    &:hover {
        background-color: rgba(125,139,169,.1);
    }
}
.multil-cascader{
    width: 155px;
    display: inline;
}
.multil-cascader:hover{
    cursor: pointer;
}

</style>

三,multiContent.vue 该页面为递归的所有children的Li的显示,以及选中点击事件

<template lang="html">
    <div class="popver-content">
        <div class="multiCascader-multil-content" :style="contentStyle">
            <ul class="multiCascader-multi-menu">
                <li v-for="(item, index) of option"
                    :key="index"
                    style="border:1px solid transparent;"
                    :class="[ 'multiCascader-menu-item', { 'item-disabled': item.disabled }]"
                    @click="showNextLevel(item)">
                    <el-checkbox v-if="item.multiple != false" :disabled="item.disabled" v-model="item.checked" @change="checkChange(item)">{{ item.label }}</el-checkbox>
                    <span v-else>{{ item.label }}</span>
                    <i class="el-icon-arrow-right" v-show="item.children && item.children.length > 0"></i>
                </li>
            </ul>
        </div>
        <!-- 递归调用自身组件 -->
        <muContent
            @handleSelect="whenSelected"
            :height="height"
            :width="width"
            v-if="(activeItem && activeItem.children) && (activeItem.children.length > 0)"
            :selectedValues="selectedValues"
            @handleOutPut="whenOutPut"
            :disabledPair="disabledPair"
            :option="activeItem.children" >
        </muContent>
    </div>
</template>

<script>
const vm = this;
import Vue from "vue";
export default {
  name: "muContent",
  props: {
    option: {
      type: Array,
      default() {
        return [];
      }
    },
    // 被选中的值
    selectedValues: {
      type: Array,
      default() {
        return [];
      }
    },
    // 设置的宽度
    width: {
      type: String,
      default: ""
    },
    height: {
      type: String,
      default: ""
    },
    // 禁用字段
    disabledPair: {
      type: Object,
      default() {
        return {};
      }
    }
  },
  data() {
    return {
      activeItem: "",
      tempActiveItem: "",
      contentStyle: {
        width: "",
        height: ""
      },
      checkArr: [],
      checkDisabled: false
    };
  },
  created() {
    this.initData();
    this.whenOutPut(this.selectedValues);
  },
  methods: {
    // 逐级上传
    whenOutPut(val) {
      this.$emit("handleOutPut", val);
    },
    initData() {
      const { width, height } = this;
      this.contentStyle = Object.assign({}, this.contentStyle, {
        width,
        height
      });
    },
    // 获取到选中的值
    checkChange(item) {
      const getCheckedItems = item => {
        const { value, checked, level } = item;
        if (checked && level) {
          this.selectedValues.push(value);
        } else if (!checked) {
          item.disabled = false;
          if (this.selectedValues.includes(value)) {
            this.selectedValues.splice(
              this.selectedValues.findIndex(slectVal => slectVal === value),
              1
            );
          }
        }
        const itemChild = item.children;
        if (itemChild) {
          itemChild.forEach(child => (child.checked = checked));
        }
      };

      this.recursiveFn(item, getCheckedItems);
      this.disabeldAction(item);
      this.activeItem = item;
      this.$emit("handleSelect", this.option);
      this.$emit("handleOutPut", this.selectedValues);
    },
    // 当二级菜单改变的时候
    whenSelected(val) {
      let allChildCancelChecked = true;
      if (Array.isArray(val) && val.length > 0) {
        allChildCancelChecked = val.every(child => child.checked === false);
      }
      this.activeItem.checked = !allChildCancelChecked;
      this.disabeldAction(this.activeItem);
      this.$emit("handleSelect", this.option);
    },
    // 递归函数
    recursiveFn(curItem, cb) {
      cb(curItem);
      const children = curItem.children;
      if (children && children.length > 0) {
        children.forEach(item => {
          this.recursiveFn(item, cb);
        });
      }
    },
    // 设置 disabled 值 values: 互斥的另一方数组, curItem 当前选中的值
    setDisabled(exceptValues, curItem, values) {
      const {
        checked: curChecked,
        childrenValues,
        value: curValue,
        siblingValues
      } = curItem;
      this.checkArr = [];
      if (values.includes("all")) {
        if (siblingValues) {
          this.checkArr = new Array(
            siblingValues.length - exceptValues.length
          ).fill(true);
        }
      } else {
        this.checkArr = new Array(values.length).fill(true);
      }
      const getCheckArr = item => {
        const { value, checked } = item;
        if (!exceptValues.includes(value)) return;
        this.checkArr.push(checked);
        this.checkArr.shift();
      };
      const resetDistable = child => {
        if (!values.includes(child.value)) return;
        child.disabled = this.checkArr.some(val => val === true);
      };
      this.option.forEach(opt => {
        this.recursiveFn(opt, getCheckArr);
      });
      this.option.forEach(opt => {
        this.recursiveFn(opt, resetDistable);
      });
    },
    // disabled action
    // 根据选中的值进行设置是否可选
    disabeldAction(item) {
      const { thatPair, thisPair } = this.disabledPair;
      if (!thatPair || !thisPair) {
        return;
      }
      const pairs = [...thatPair, ...thisPair];
      const { value: itemVal } = item;
      const belongPair = pairs.includes(itemVal) || pairs.includes("all");
      let distableValues = [];
      let ableValues = [];
      if (!belongPair) return;
      if (
        thisPair.includes(item.value) ||
        (thisPair.includes("all") && !thatPair.includes(item.value))
      ) {
        this.setDisabled(thisPair, item, thatPair);
        return;
      }
      if (
        thatPair.includes(item.value) ||
        (thatPair.includes("all") && !thisPair.includes(item.value))
      ) {
        this.setDisabled(thatPair, item, thisPair);
      }
      this.$emit("handleSelect", this.option);
      this.disabeldAction(this.activeItem);
    },
    // 设置 disabled 值 values: 互斥的另一方数组, curItem 当前选中的值
    setDisabled(exceptValues, curItem, values) {
      const {
        checked: curChecked,
        childrenValues,
        value: curValue,
        siblingValues
      } = curItem;
      this.checkArr = [];
      if (values.includes("all")) {
        if (siblingValues) {
          this.checkArr = new Array(
            siblingValues.length - exceptValues.length
          ).fill(true);
        }
      } else {
        this.checkArr = new Array(values.length).fill(true);
      }
      const toDisabled = item => {
        const { value, checked } = item;
        if (
          values.includes(value) ||
          (values.includes("all") && !exceptValues.includes(value))
        ) {
          if (siblingValues && siblingValues.includes(value)) {
            this.checkArr.push(checked);
            this.checkArr.shift();
          }
        }
        const itemChild = item.children;
        if (itemChild && itemChild.length > 0) {
          itemChild.forEach(child => {
            toDisabled(child);
          });
        }
      };
      this.option.forEach(child => {
        toDisabled(child);
      });
      this.option.forEach(child => {
        if (
          exceptValues.includes(child.value) ||
          (exceptValues.includes("all") && !values.includes(child.value))
        ) {
          child.disabled = this.checkArr.some(val => val === true);
        }
      });
    },
    // disabled action
    // 根据选中的值进行设置是否可选
    disabeldAction(item) {
      const { thatPair, thisPair } = this.disabledPair;
      if (!thatPair || !thisPair) {
        return;
      }
      const pairs = [...thatPair, ...thisPair];
      if (pairs.includes(item.value) || pairs.includes("all")) {
        if (
          thisPair.includes(item.value) ||
          (thisPair.includes("all") && !thatPair.includes(item.value))
        ) {
          this.setDisabled(thatPair, item, thisPair);
          return;
        }
        if (
          thatPair.includes(item.value) ||
          (thatPair.includes("all") && !thisPair.includes(item.value))
        ) {
          this.setDisabled(thisPair, item, thatPair);
        }
      }
    },
    //点击每一个列表的操作并且给下一个列表赋值
    showNextLevel(item) {
        //先清空,后赋值,否则会导致多级列表同时存在
      this.activeItem = "";
      if (item.disabled) return;
      setTimeout(() => {
        this.activeItem = item;
      }, 10);
    }
  }
};
</script>
<style lang='scss' scoped>
.popver-content {
  display: flex;
  justify-content: space-between;
}
.multiCascader-multil-content {
  display: inline-block;
  max-height: 250px;
  overflow-y: auto;
  // border-right: 1px solid red;

}
.multiCascader-menu-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  list-style-type: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  outline: none;
  padding: 8px 20px;
  font-size: 14px;
  &:hover {
    background-color: rgba(125, 139, 169, 0.1);
  }
}
.item-disabled {
  color: #c0c4cc;
  cursor: not-allowed;
}
</style>

接下来就到需要引用的页面了。

<template>
  <div class="performanceBox">
          <!-- 级联选择器多选 -->
          <choiceindex v-on:CheckedsIndexCodes="FromTreeCheckeds"></choiceindex>
  </div>
</template>
<script>
引用上边创建的MultipleChoice文件夹下的index出口文件就好了。
import choiceindex from "@/components/MultipleChoice/index"; //级联选择多选 完成
export default {
  components: {
    choiceindex,
  },
  data() {
    return {
      SaveCascadeIndexCodes: [], //保存级联选择器多选的基准code
      SaveJiZhunParams: [], //保存业绩表现需要的参数

    };
  },
  methods: {
    //多选选择基准时的code
    FromTreeCheckeds(IndexCodes) {
//IndexCodes就是选中的item的数组,操作他就好了
      // console.log(IndexCodes);
      this.SaveCascadeIndexCodes = IndexCodes;
    },
  }
};
</script>
<style  rel="stylesheet/scss" lang="scss">
</style>

结束语


这个插件到此也就完成了,终于解决了这个深坑,希望能帮助到小伙伴们,有什么不足的大家多多提出宝贵的意见,共同探讨,进步。

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

推荐阅读更多精彩内容