React-smooth-dnd

前段時(shí)間,項(xiàng)目里有一個(gè)新的需求是關(guān)于三級拖拽的培己,我本身也在參與其它項(xiàng)目的開發(fā)索抓,并沒有時(shí)間做技術(shù)調(diào)研钧忽,慶幸同事有人做過相似需求的開發(fā),經(jīng)過同事的技術(shù)支持逼肯,在需求規(guī)定的時(shí)間內(nèi)完成了開發(fā)耸黑,在這里我要感謝我的同事的幫助,感謝團(tuán)隊(duì)里的每一個(gè)人篮幢,筆芯大刊。

再次回顧這次需求,時(shí)間上有明確的 deadline 三椿,功能和業(yè)務(wù)的復(fù)雜度超乎預(yù)期想象缺菌,而且沒有足夠的開發(fā)時(shí)間葫辐,在參加需求開發(fā)之前有其它的需求開發(fā)并沒有做技術(shù)調(diào)研,總結(jié)有以下幾個(gè)重要的點(diǎn):

  • 大的功能開發(fā)前如果有技術(shù)難點(diǎn)一定要做調(diào)研男翰。

  • 開發(fā)前必須要明確需求的點(diǎn)另患,包括 ui 交互。

  • 預(yù)估時(shí)間一定要預(yù)留足夠的 buffer 蛾绎。

現(xiàn)在我們來說說怎樣去實(shí)現(xiàn)一個(gè)三級拖拽昆箕。

首先,完成這次功能我們使用了一個(gè) react 庫租冠。

react-smooth-dnd.png
  1. 該庫是一個(gè)快速鹏倘、輕量、可排序的庫顽爹。
  2. 該庫使用 css 轉(zhuǎn)換來制作動(dòng)畫纤泵。
  3. 該庫是在基于 react-dnd 基礎(chǔ)上開發(fā)的 react 拖動(dòng)效果的組件。
  4. 該庫現(xiàn)在有 1.4k 星镜粤,使用效果還不錯(cuò)捏题。

其次,讓我們看看怎么使用 react-smooth-dnd肉渴。

import { Container, Draggable } from 'react-smooth-dnd';

<Container onDrop={onDrop}>
  {data.map((item) => (
    <Draggable>
      <div className={styles.item}>
        {item.label}
      </div>
    </Draggable>
  ))}
</Container>

Container 標(biāo)簽: 拖動(dòng)的容器公荧,即在 Container 內(nèi)可以拖動(dòng),里面可以包容若干個(gè) Draggable 同规。
Draggable 標(biāo)簽:拖動(dòng)的元素循狰,把要拖動(dòng)的內(nèi)容放在 Draggable 里就可以實(shí)現(xiàn)拖拽。

注意:Container 下面必須直接包含 Draggable券勺,否則會(huì)報(bào)錯(cuò)绪钥。

最后,讓我們了解一下 react-smooth-dnd 都有什么 api关炼。

一. Container api
1. groupName

拖動(dòng)容器的名稱程腹,當(dāng)有多個(gè) Container 時(shí),groupName 名稱相同時(shí)可以實(shí)現(xiàn)相互拖動(dòng)盗扒。

2. orientation

容器的方向跪楞。可選值:

  • horizontal (水平)
  • vertical (默認(rèn)侣灶,垂直)
3. behaviour

規(guī)定了拖動(dòng)元素的狀態(tài)甸祭,可選值有 4 個(gè):

  • move(默認(rèn),移動(dòng))
  • copy(復(fù)制)
  • drop-zone(跌落)
  • contain(包含)
4. lockAxis

限制當(dāng)前拖動(dòng)的方向褥影〕鼗В可選值有:x, y,表示 x, y 軸拖動(dòng)。

5. dragClass

拖動(dòng)元素被拖動(dòng)時(shí)的樣式校焦。

6. dropClass

拖動(dòng)元素被釋放時(shí)的樣式赊抖。

7. dropPlaceholder

拖動(dòng)元素拖走時(shí)或進(jìn)入其他位置時(shí),用于占位當(dāng)前元素的配置寨典。

可配置值:

  • className 占位元素樣式
  • animationDuration 動(dòng)畫延時(shí)
  • showOnTop
8. dragBeginDelay

延時(shí)拖動(dòng)氛雪,時(shí)間為毫秒。用在防誤操作的情況耸成,或者在拖動(dòng)的元素上有其它的事件的情況报亩。

9. onDragStart

拖動(dòng)開始會(huì)觸發(fā)此事件

10. onDragEnd

拖動(dòng)結(jié)束會(huì)觸發(fā)此事件

11. onDrop

拖動(dòng)釋放會(huì)觸發(fā)此事件

12. getChildPayload

記錄當(dāng)前拖動(dòng)元素的信息,使用該函數(shù)返回一個(gè) payload 的值井氢。當(dāng) onDrop 觸發(fā)時(shí)弦追,會(huì)自動(dòng)帶入該函數(shù)返回的信息,用于做數(shù)據(jù)的處理花竞。

13. onDragEnter

拖動(dòng)進(jìn)入會(huì)觸發(fā)此事件

14. onDragLeave

拖動(dòng)離開會(huì)觸發(fā)此事件

15. getGhostParent

當(dāng)多層拖動(dòng)且容器名稱相同時(shí)劲件,下層元素向上拖動(dòng)會(huì)出現(xiàn)拖動(dòng)元素不可見,此時(shí)可以設(shè)置此函數(shù)约急。

二. Draggable api
1. render
<Draggable render={() => {
  return (
    <li>
      ...
    </li>
  )
}}/>

render 返回一個(gè) dom 元素零远。

當(dāng) render 存在時(shí)會(huì)忽略 Draggable 的 children。

做好準(zhǔn)備工作之后厌蔽,現(xiàn)在我們來簡單實(shí)現(xiàn)一下一層的排序功能:

import React, { useState } from 'react';
import { connect } from 'dva';
import { Container, Draggable } from 'react-smooth-dnd';
import styles from './IndexPage.css';

const list = [
  { label: '第一個(gè)數(shù)據(jù)', fieldName: 'data-a1', children: [] },
  { label: '第二個(gè)數(shù)據(jù)', fieldName: 'data-a2', children: [] },
  { label: '第三個(gè)數(shù)據(jù)', fieldName: 'data-a3', children: [] },
];

function IndexPage() {
  const [data, setData] = useState(list);

  const onDrag = (arr = [], dragResult) => {
    const { removedIndex, addedIndex, payload } = dragResult;
    if (removedIndex === null && addedIndex === null) {
      return arr;
    }
    const result = [...arr];
    let itemToAdd = payload;
    if (removedIndex !== null) {
      itemToAdd = result.splice(removedIndex, 1)[0];
    }
    if (addedIndex !== null) {
      result.splice(addedIndex, 0, itemToAdd);
    }
    return result;
  };

  const onDrop = (dropResult) => {
    const { removedIndex, addedIndex } = dropResult;
    if (removedIndex !== null || addedIndex !== null) {
      const list = onDrag(data, dropResult);
      setData(list);
    }
  }

  return (
    <div className={styles.container}>
      <Container onDrop={onDrop}>
        {data.map((item) => (
          <Draggable>
            <div className={styles.item}>
              {item.label}
            </div>
          </Draggable>
        ))}
      </Container>
    </div>
  );
}

export default connect()(IndexPage);

可以發(fā)現(xiàn)遍烦,實(shí)現(xiàn)一層的功能是很簡單的,首先是準(zhǔn)備好數(shù)據(jù)躺枕,寫好 dom 結(jié)構(gòu),最后寫好 onDrop 函數(shù)供填,一層排序的功能就做好了拐云。是不是 so easy ?

那么現(xiàn)在我們嘗試實(shí)現(xiàn)三層的功能。三層如何實(shí)現(xiàn)呢近她?我們可以假設(shè)每一個(gè)拖動(dòng)的元素同時(shí)又是一個(gè)容器叉瘩,即每一個(gè) Draggable 下面又有一個(gè) Container ,不就實(shí)現(xiàn)了嵌套嗎?現(xiàn)在我們實(shí)現(xiàn)一下:

第一步:準(zhǔn)備好數(shù)據(jù)粘捎。

const list = [
  { label: '第一個(gè)數(shù)據(jù)', fieldName: 'data-a1', children: [
    { label: '第二層', fieldName: 'data-b1', children: [
      { label: '第三層', fieldName: 'data-c1', children: [] },
      { label: '第三層2', fieldName: 'data-c2', children: [] },
    ] },
    { label: '第二層2', fieldName: 'data-b2', children: [] },
  ] },
  { label: '第二個(gè)數(shù)據(jù)', fieldName: 'data-a2', children: [] },
  { label: '第三個(gè)數(shù)據(jù)', fieldName: 'data-a3', children: [
    { label: '第二層3', fieldName: 'data-b3', children: [] },
  ] },
];

第二步:寫好 dom 結(jié)構(gòu)薇缅。

  // 利用遞歸實(shí)現(xiàn)列表的渲染
  const onDomRender = (renderData, parent, depth) => {
    if (depth === 4) return;
    return (
      <Container 
        groupName="col"
        onDrop={(value) => onDrop(value, parent, depth)}
        getChildPayload={(index) => renderData[index]}
      >
        {
          renderData.map((item) => {
            return (
              <Draggable key={item.fieldName}>
                <div className={styles.item} style={depth === 1 ? {marginBottom: '20px'} : {}}>
                  {item.label}
                  <div className={styles.box}>
                    {onDomRender(item.children, item, depth + 1)}
                  </div>
                </div>
              </Draggable>
            )
          })
        }
      </Container>
    )
  };

  ...這里調(diào)用 onDomRender 渲染界面
  <div className={styles.container}>
    {onDomRender(data, null, 1)}
  </div>

第三步:完成 onDrop 函數(shù)。

  // 處理拖拽的移除攒磨、添加
  const onDrag = (arr = [], dragResult) => {
    const { removedIndex, addedIndex, payload } = dragResult;
    if (removedIndex === null && addedIndex === null) {
      return arr;
    }
    const result = [...arr];
    let itemToAdd = payload;
    if (removedIndex !== null) {
      itemToAdd = result.splice(removedIndex, 1)[0];
    }
    if (addedIndex !== null) {
      result.splice(addedIndex, 0, itemToAdd);
    }
    return result;
  };

  // 獲取數(shù)據(jù)的索引
  const onGetIndex = (item, tempData, indexArr, lastIndex) => {
    tempData.length > 0 && tempData.forEach((ele, index) => {
      if (item.fieldName === ele.fieldName) {
        if (lastIndex !== undefined) {
          indexArr.push(lastIndex);
          indexArr.push(index);
        } else {
          indexArr.push(index);
        }
      } else if (ele.children.length && ele.children.length > 0) {
        onGetIndex(item, ele.children, indexArr, index);
      }
    })
    return indexArr;
  }

  // 處理跨層級的拖拽
  const onTreate = (current) => {
    const {
      removedIndex,
      addedIndex,
      removedDepth,
      addedDepth,
      removedParent,
      addedParent,
    } = current;
    if (removedIndex !== null && addedIndex !== null && removedIndex !== undefined && addedIndex !== undefined){
      let result = cloneDeep(data);
      // 添加
      if (addedDepth === 1) {
        result = onDrag(result, {...current, removedIndex: null});
      } else if (addedDepth === 2) {
        const tempData = onDrag(addedParent.children, {...current, removedIndex: null});
        const index = onGetIndex(addedParent, result, []);
        result[index[0]].children = tempData;
      } else if (addedDepth === 3) {
        const tempData = onDrag(addedParent.children, {...current, removedIndex: null});
        const index = onGetIndex(addedParent, result, []);
        result[index[0]].children[index[1]].children = tempData;
      }
      // 移除
      if (removedDepth === 1) {
        result = onDrag(result, {...current, addedIndex: null});
      } else if (removedDepth === 2) {
        const index = onGetIndex(removedParent, result, []);
        const parent = result[index[0]];
        const tempData = onDrag(parent.children, {...current, addedIndex: null});
        result[index[0]].children = tempData;
      } else if (removedDepth === 3) {
        const index = onGetIndex(removedParent, result, []);
        const parent = result[index[0]].children[index[1]];
        const tempData = onDrag(parent.children, {...current, addedIndex: null});
        result[index[0]].children[index[1]].children = tempData;
      }
      
      setData(result);
      setTemp({});
    }
  }

  // 拖拽釋放
  const onDrop = (dropResult, parent, depth) => {
    const { removedIndex, addedIndex, payload } = dropResult;
    if (removedIndex !== null || addedIndex !== null) {
      // 同層拖拽
      if (removedIndex !== null && addedIndex !== null) {
        let result = cloneDeep(data);
        if (depth === 1) {
          result = onDrag(data, dropResult);
        } else if (depth === 2) {
          const tempData = onDrag(parent.children, dropResult);
          const index = onGetIndex(parent, result, []);
          result[index[0]].children = tempData;
        } else if (depth === 3) {
          const tempData = onDrag(parent.children, dropResult);
          const index = onGetIndex(parent, result, []);
          result[index[0]].children[index[1]].children = tempData;
        }
        setData(result);
      // 跨層拖拽泳桦,第一次執(zhí)行
      } else if (temp.removedIndex === undefined && temp.addedIndex === undefined) {
        let flag = addedIndex !== null ? {addedDepth: depth, addedParent: parent} : {removedDepth: depth, removedParent: parent};
        setTemp({...dropResult, ...flag});
      // 跨層拖拽,非第一次執(zhí)行(第一次執(zhí)行了添加)
      } else if (temp.addedIndex !== null && temp.removedIndex === null && temp.payload.fieldName === payload.fieldName) {
        const current = { ...temp, removedIndex, removedDepth: depth, removedParent: parent };
        // 已有添加娩缰、移除操作灸撰,開始執(zhí)行跨層級拖拽
        onTreate(current);
      // 跨層拖拽,非第一次執(zhí)行(第一次執(zhí)行了移除)
      } else if (temp.removedIndex !== null && temp.addedIndex === null && temp.payload.fieldName === payload.fieldName) {
        const current ={ ...temp, addedIndex, addedDepth: depth, addedParent: parent };
        // 已有移除、添加操作浮毯,開始執(zhí)行跨層級拖拽
        onTreate(current);
      }
    }
  }

onDrop 函數(shù)處理相同層級的拖拽時(shí)完疫,根據(jù) depth 來決定處理幾層的數(shù)據(jù),然后更新 state 來重新渲染頁面债蓝;處理跨層級的拖拽時(shí)調(diào)用了 onTreate 函數(shù)壳鹤,這里為什么要加 temp 來存儲(chǔ)上次的操作呢?因?yàn)?react-smooth-dnd 在拖動(dòng)元素時(shí)無法保證先返回添加事件或者移除事件饰迹,所以我們將上次的操作(添加芳誓、移除)存儲(chǔ)在 temp 里,等到下個(gè)操作(移除蹦锋、添加)時(shí)再來處理拖動(dòng)事件兆沙,并把 temp 置 {}。

onTreate 函數(shù)負(fù)責(zé)處理跨層級的拖拽莉掂。先執(zhí)行添加操作葛圃,后執(zhí)行移除操作。在這里調(diào)用了 onDrag 函數(shù)來執(zhí)行添加憎妙、移除的操作库正,onGetIndex 函數(shù)來確定處理數(shù)據(jù)的索引。

注意:在執(zhí)行移除操作時(shí)要保證處理的是最新的數(shù)據(jù)厘唾。

最后褥符,我們看一下完整的代碼。

import React, { useState, useEffect } from 'react';
import { connect } from 'dva';
import { cloneDeep } from 'lodash';
import { Container, Draggable } from 'react-smooth-dnd';
import styles from './IndexPage.css';

const list = [
  { label: '第一個(gè)數(shù)據(jù)', fieldName: 'data-a1', children: [
    { label: '第二層', fieldName: 'data-b1', children: [
      { label: '第三層', fieldName: 'data-c1', children: [] },
      { label: '第三層2', fieldName: 'data-c2', children: [] },
    ] },
    { label: '第二層2', fieldName: 'data-b2', children: [] },
  ] },
  { label: '第二個(gè)數(shù)據(jù)', fieldName: 'data-a2', children: [] },
  { label: '第三個(gè)數(shù)據(jù)', fieldName: 'data-a3', children: [
    { label: '第二層3', fieldName: 'data-b3', children: [] },
  ] },
];

function IndexPage() {
  const [data, setData] = useState([]);
  const [temp, setTemp] = useState({});

  useEffect(() => {
    setData(list);
  }, []);

  const onDrag = (arr = [], dragResult) => {
    const { removedIndex, addedIndex, payload } = dragResult;
    if (removedIndex === null && addedIndex === null) {
      return arr;
    }
    const result = [...arr];
    let itemToAdd = payload;
    if (removedIndex !== null) {
      itemToAdd = result.splice(removedIndex, 1)[0];
    }
    if (addedIndex !== null) {
      result.splice(addedIndex, 0, itemToAdd);
    }
    return result;
  };

  const onGetIndex = (item, tempData, indexArr, lastIndex) => {
    tempData.length > 0 && tempData.forEach((ele, index) => {
      if (item.fieldName === ele.fieldName) {
        if (lastIndex !== undefined) {
          indexArr.push(lastIndex);
          indexArr.push(index);
        } else {
          indexArr.push(index);
        }
      } else if (ele.children.length && ele.children.length > 0) {
        onGetIndex(item, ele.children, indexArr, index);
      }
    })
    return indexArr;
  }

  const onTreate = (current) => {
    const {
      removedIndex,
      addedIndex,
      removedDepth,
      addedDepth,
      removedParent,
      addedParent,
    } = current;
    if (removedIndex !== null && addedIndex !== null && removedIndex !== undefined && addedIndex !== undefined){
      let result = cloneDeep(data);
      if (addedDepth === 1) {
        result = onDrag(result, {...current, removedIndex: null});
      } else if (addedDepth === 2) {
        const tempData = onDrag(addedParent.children, {...current, removedIndex: null});
        const index = onGetIndex(addedParent, result, []);
        result[index[0]].children = tempData;
      } else if (addedDepth === 3) {
        const tempData = onDrag(addedParent.children, {...current, removedIndex: null});
        const index = onGetIndex(addedParent, result, []);
        result[index[0]].children[index[1]].children = tempData;
      }
      if (removedDepth === 1) {
        result = onDrag(result, {...current, addedIndex: null});
      } else if (removedDepth === 2) {
        const index = onGetIndex(removedParent, result, []);
        const parent = result[index[0]];
        const tempData = onDrag(parent.children, {...current, addedIndex: null});
        result[index[0]].children = tempData;
      } else if (removedDepth === 3) {
        const index = onGetIndex(removedParent, result, []);
        const parent = result[index[0]].children[index[1]];
        const tempData = onDrag(parent.children, {...current, addedIndex: null});
        result[index[0]].children[index[1]].children = tempData;
      }
      
      setData(result);
      setTemp({});
    }
  }

  const onDrop = (dropResult, parent, depth) => {
    const { removedIndex, addedIndex, payload } = dropResult;
    if (removedIndex !== null || addedIndex !== null) {
      if (removedIndex !== null && addedIndex !== null) {
        let result = cloneDeep(data);
        if (depth === 1) {
          result = onDrag(data, dropResult);
        } else if (depth === 2) {
          const tempData = onDrag(parent.children, dropResult);
          const index = onGetIndex(parent, result, []);
          result[index[0]].children = tempData;
        } else if (depth === 3) {
          const tempData = onDrag(parent.children, dropResult);
          const index = onGetIndex(parent, result, []);
          result[index[0]].children[index[1]].children = tempData;
        }
        setData(result);
      } else if (temp.removedIndex === undefined && temp.addedIndex === undefined) {
        let flag = addedIndex !== null ? {addedDepth: depth, addedParent: parent} : {removedDepth: depth, removedParent: parent};
        setTemp({...dropResult, ...flag});
      } else if (temp.addedIndex !== null && temp.removedIndex === null && temp.payload.fieldName === payload.fieldName) {
        const current = { ...temp, removedIndex, removedDepth: depth, removedParent: parent };
        onTreate(current);
      } else if (temp.removedIndex !== null && temp.addedIndex === null && temp.payload.fieldName === payload.fieldName) {
        const current ={ ...temp, addedIndex, addedDepth: depth, addedParent: parent };
        onTreate(current);
      }
    }
  }

  // 利用遞歸實(shí)現(xiàn)列表的渲染
  const onDomRender = (renderData, parent, depth) => {
    if (depth === 4) return;
    return (
      <Container 
        groupName="col"
        onDrop={(value) => onDrop(value, parent, depth)}
        getChildPayload={(index) => renderData[index]}
      >
        {
          renderData.map((item) => {
            return (
              <Draggable key={item.fieldName}>
                <div className={styles.item} style={depth === 1 ? {marginBottom: '20px'} : {}}>
                  {item.label}
                  <div className={styles.box}>
                    {onDomRender(item.children, item, depth + 1)}
                  </div>
                </div>
              </Draggable>
            )
          })
        }
      </Container>
    )
  };

  return (
    <div className={styles.container}>
      {onDomRender(data, null, 1)}
    </div>
  );
}

export default connect()(IndexPage);

現(xiàn)在我們實(shí)現(xiàn)了三層拖拽抚垃,在完成的時(shí)候發(fā)現(xiàn) getChildPayload 必須要明確給出喷楣,否則在拖拽完成后 onDrop 事件無法得到 payload 的值。

那還有其它的問題嗎鹤树?

在拖拽的過程當(dāng)中铣焊,我們也發(fā)現(xiàn)了子元素向上拖拽時(shí)會(huì)隱藏,這時(shí)我們想到了 getGhostParent 屬性罕伯,我們再看一下效果曲伊,這回子元素向上拖拽時(shí)不會(huì)再出現(xiàn)隱藏的情況了。

<Container 
  groupName="col"
  onDrop={(value) => onDrop(value, parent, depth)}
  getChildPayload={(index) => renderData[index]}
  getGhostParent={() => document.body}
>
...
</Container>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末追他,一起剝皮案震驚了整個(gè)濱河市坟募,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邑狸,老刑警劉巖懈糯,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異推溃,居然都是意外死亡昂利,警方通過查閱死者的電腦和手機(jī)届腐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜂奸,“玉大人犁苏,你說我怎么就攤上這事±┧” “怎么了围详?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長祖屏。 經(jīng)常有香客問我伴榔,道長赦政,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮切黔,結(jié)果婚禮上砸喻,老公的妹妹穿的比我還像新娘欢瞪。我一直安慰自己敌完,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布钝荡。 她就那樣靜靜地躺著街立,像睡著了一般。 火紅的嫁衣襯著肌膚如雪埠通。 梳的紋絲不亂的頭發(fā)上赎离,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音端辱,去河邊找鬼梁剔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛舞蔽,可吹牛的內(nèi)容都是我干的憾朴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼喷鸽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了灸拍?” 一聲冷哼從身側(cè)響起做祝,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸡岗,沒想到半個(gè)月后混槐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轩性,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年声登,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悯嗓,死狀恐怖件舵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脯厨,我是刑警寧澤铅祸,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站合武,受9級特大地震影響临梗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稼跳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一盟庞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汤善,春花似錦什猖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锉屈,卻和暖如春荤傲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颈渊。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工遂黍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俊嗽。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓雾家,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绍豁。 傳聞我的和親對象是個(gè)殘疾皇子芯咧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355