记RN路由系统重构-react-navigation

记RN路由系统重构-react-navigation

简单说明

目前react-native项目中使用的路由是react-navigation,官方的路由只是简单的示例,页面过多后,App.tsx主页面需要复制粘贴许多RootStack.Screen,路由navigation需要逐层传递,当然也可以是实用useDispatchuseNavigationhooks获取,但为了方便管理和升级路由系统,封装了routers以及MRouters 管理类,部分完整代码直接拖到最后可以查看。

路由系统改造主要是包括路由配置src/router.js、路由管理src/framework/MRouter.ts、页面使用src/app.tsx三部分

路由配置src/router.js

1.路由动画枚举animationType

const animationType = {
  default: 'default',
  fade: 'fade',
  fade_from_bottom: 'fade_from_bottom',
  flip: 'flip',
  none: 'none',
  simple_push: 'simple_push',
  slide_from_bottom: 'slide_from_bottom',
  slide_from_right: 'slide_from_right',
  slide_from_left: 'slide_from_left'
}

2.路由屏幕屏幕方向枚举orientationType

const orientationType = {
  default: 'default',
  all: 'all',
  portrait: 'portrait',
  portrait_up: 'portrait_up',
  portrait_down: 'portrait_down',
  landscape: 'landscape',
  landscape_left: 'landscape_left',
  landscape_right: 'landscape_right'
}

3.默认路由配置screenConfig

const screenConfig: NativeStackNavigationOptions = {
  headerShown: true,
  headerShadowVisible: false,
  headerTitleAlign: 'center',
  headerBackTitle: '',
  headerBackVisible: false,
  headerBackTitleVisible: false,
  headerTintColor: '#3D3F43',
  animation: animationType.slide_from_right,
  orientation: orientationType.portrait,
  headerStyle: {
    backgroundColor: '#fff',
    // @ts-ignore
    borderBottomWidth: 0
  },
  headerTitleStyle: {
    fontSize: 18,
    fontWeight: '500',
    color: '#3D3F43'
  }
};

自定义HeaderTitlegetHeaderTitle

const getHeaderTitle = (text) => {
  return (
    <View style={{
      height: 28,
      marginBottom: 8,
      // backgroundColor: '#f00',
      justifyContent: 'center',
      alignItems: 'center'
    }}>
      <UIText style={[{
        fontSize: 18,
        fontWeight: '500'
      }, { color: '#3D3F43' }]}>{text}</UIText>
    </View>
  );
};

screen option生成getOptions

const getOptions = (param: NativeStackNavigationOptions) => {
  const options: NativeStackNavigationOptions = {
    ...screenConfig,
    ...param,
    headerLeft: () => <HeaderBackArrow />,
    headerTitle: () => getHeaderTitle(param.title || '')
  };
  return options;
};

路由配置对象routers

路由中包括nameoptionscompenent三部分

name代表当前路由名称

options代表当前路由的配置,主要包括title``anmimation``headerShown``gestureEnabled等配置项,具体有哪些配置项参考NativeStackNavigationOptions

component代表当前路由的组件

const routers = {
  Login: { name: 'Login', options: getOptions({ animation: animationType.slide_from_bottom, headerShown: false }), component: Login },
  Info: { name: 'Info', options: getOptions({}), component: Info },
  Tabbar: { name: 'Tabbar', options: getOptions({ animation: animationType.slide_from_right }), component: Tabbar },
  Agreement: { name: 'Agreement', options: getOptions({}), component: Agreement },
  ...
}

路由管理src/framework/MRouter.ts

1.路由ref设置

let _navigator: any;

/**
 * 设置路由ref
 * @param navigatorRef 路由ref
 */
function setNavigator(navigatorRef: any) {
  _navigator = navigatorRef;
  logInfo('setNavigator');
}

2.打开新页面

主要newPage参数使用,可开启新页面不会回跳旧页面

/**
 * 打开一个新页面
 * @param name 路由名称
 * @param params 路由参数
 * @param newPage 是否开启新页面 默认false
 */
function open(name: string, params?: any, newPage?: boolean) {
  let index = indexOfRouteByName(name);
  let route = getRouteInfoByName(name);
  let item: any = {
    key: route?.key || name,
    name: name,
    path: name,
    params: params || null
  };
  console.log('newPage', newPage, index);
  if (newPage || index === -1) item.key = name + generateRandom();

  _navigator.dispatch(CommonActions.navigate(item));
  logInfo('open', name);
}

3.重置路由栈

/**
 * 将路由name重置到首页 并且清空路由栈
 * @param name 路由名称
 * @param params 路由参数
 */
function home(name: string, params?: object) {
  try {
    let item = {
      name: name,
      path: name,
      params: params,
      key: name + generateRandom()
    };

    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: [item]
      })
    );
  } catch (e) {
    errorHandler.noRoute(name);
  }
  logInfo('home', name);
}

4.路由替换

/**
 * 将栈顶的路由替换为${name}路由
 * @param name 路由名称
 * @param params 路由参数
 */
function replace(name: string, params?: any) {
  let routers = _navigator.getRootState().routes || [];
  let index = indexOfRouteByName(name);
  if (name && index > -1) {
    let item = {
      index,
      name: name,
      path: name,
      key: routers[index].key
    };
    if (index > -1) routers = routers.slice(0, index);
    routers.push(item);
    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: routers
      })
    );
  } else {
    routers.pop();
    let item = {
      name: name,
      params: params,
      path: name,
      key: name + generateRandom()
    };
    routers.push(item);
    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: routers
      })
    );
  }
  logInfo('replace', name);
}

路由返回

主要name参数,不传就默认往回跳转一页,传了就调用open方法跳转到原来就有的那一页

/**
 * 路由回退
 * @param name 路由名称
 * @param params 回调参数
 */
function back(name?: string, params?: any) {
  let index = -1;
  let route = null;
  if (name) {
    index = indexOfRouteByName(name);
    route = getRouteInfoByName(name);
  } else {
    route = _navigator.getRootState().routes[_navigator.getRootState().routes.length - 1];
  }

  if (index > -1) {
    open(route.name, params);
  } else {
    close();
  }

  params &&
    _navigator.dispatch(
      CommonActions.setParams({
        params: params,
        key: route && route.key,
        source: route && route.key
      })
    );

  logInfo('back', name);
}

关闭当前路由

/**
 * 关闭当前路由
 */
function close() {
  if (_navigator.canGoBack()) {
    _navigator.dispatch(CommonActions.goBack());
  }
  logInfo('close');
}

获取路由name在路由栈中当前的位置

查找的顺序是从后往前找

/**
 * 获取路由name在路由栈中当前的位置  从后往前查找
 * @param name 路由名称
 * @returns 返回index
 */
function indexOfRouteByName(name: string) {
  let routers = _navigator.getRootState().routes || [];
  let index = -1;
  let routeIndex = routers.length;
  console.log('indexOfRouteByName', name, routeIndex, routers);
  while (routeIndex > 0) {
    routeIndex--;
    let route = routers[routeIndex];
    if (route.name === name) {
      index = routeIndex;
      break;
    }
  }
  return index;
}

根据页面名字获取页面所在路由的信息

/**
 * 根据页面名字获取页面所在路由的信息
 * @param  {String} name 页面名字
 * @return {{name,key,path,params}}      页面所在路由,如果没有则返回 null
 */
function getRouteInfoByName(name: string) {
  let routers = _navigator.getRootState().routes || [];
  let routeIndex = routers.length;
  while (routeIndex) {
    routeIndex--;
    let route = routers[routeIndex];
    if (route.name === name) {
      return route;
    }
  }
  return routers[routers.length];
}

App.tsx配置

1.路由navigationRef对象获取传递

function App() {
  const navigationRef = useNavigationContainerRef();

  return (
    <SafeAreaProvider>
      <NavigationContainer ref={navigationRef}>
        <NavigatorFn navigationRef={navigationRef} />
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

2.MRouter初始化

useEffect(() => {
    MRouter.setNavigator(navigationRef);
    ...
  }, []);

3.路由动态生成

<RootStack.Navigator
        // initialRouteName="Root"
        // backBehavior="history"
        screenOptions={{
          headerShown: false,
          orientation: 'portrait'
        }}>
        {routers &&
          Object.keys(routers).map((key, index) => {
            return (
              <RootStack.Screen
                key={index}
                name={routers[key].name}
                options={routers[key].options}
                getComponent={() => routers[key].component}
              />
            );
          })}
 </RootStack.Navigator>

4.根据现有项目改造

useEffect(() => {
    AsyncStorage.getItem('token')
      .then(token => {
        dispatch(setToken({ token }));
        if (!token) {
          MRouter.home(routers.Login.name);
        }
      })
      .catch(err => {
        MRouter.home(routers.Login.name);
      });
  }, [token]);

  // if (isLoading) {
  //   return <></>;
  // }
  useEffect(() => {
    switch (status) {
      case 'login': // 进入登陆
        MRouter.home(routers.Login.name);
        break;
      default:
        MRouter.home(routers.Loading.name);
        break;
    }
  }, [status]);

MRouter使用补充说明

MRouter使用在任何页面和组件都可以直接使用,不用层层传递

MRouter提供了基本所有场景的使用方式

MRouter与原来navigationnavigatereplace等方法兼容,路由栈是同一个可以配合使用

MRouter使用说明

//MRouter打开新页面
//通过字符串Login
MRouter.open(‘Login’)   
//通过路由系统的name
MRouter.open(routers.Login.name)   
//通过第二参数传递params
MRouter.open(routers.Login.name, { name: 'abc', key: 1 })  
//通过第二参数传递params 通过第三参数确认是否开启新页面
//第三参数默认false,当前路由栈存在该页面时会自动跳转回存在的页面 设置为true,每次都开启新页面
MRouter.open(routers.Login.name, { name: 'abc', key: 1 }, true)  


//参数获取
//Component获取 直接从this.props中获取
const { params } = this.props;
const { name, key } = params;
//从hooks中获取
const route = useRoute();
const name = _.get(route, 'params.name', 'Demo Params');


//MRouter重置当前路由栈 并将页面为某个页面 同open可设置参数
MRouter.home(routers.Login.name)
MRouter.home(routers.Login.name, { key: 2, name: 'bcd' })


//MRouter替换路由栈顶的路由
MRouter.replace(routers.Login.name)
MRouter.replace(routers.Login.name, { key: 2, name: 'bcd' })


//MRouter关闭页面
//默认返回上一页
MRouter.back()
//返回路由名称为name的页面 可传递参数
//默认调用的是open方法 如果页面存在就返回到存在的页面 可传递参数
MRouter.back(routers.Login.name)
MRouter.back(routers.Login.name, { key: 2, name: 'bcd' })

部分代码示例

MRouter.ts

import { CommonActions } from '@react-navigation/native';
import errorHandler from './error/errorHandler';
import { routers } from 'router/router';
import { generateRandom } from 'utils';
import { NativeModules } from 'react-native';

let _navigator: any;

/**
 * 设置路由ref
 * @param navigatorRef 路由ref
 */
function setNavigator(navigatorRef: any) {
  _navigator = navigatorRef;
  logInfo('setNavigator');
}

/**
 * 将栈顶的路由替换为${name}路由
 * @param name 路由名称
 * @param params 路由参数
 */
function replace(name: string, params?: any) {
  let routers = _navigator.getRootState().routes || [];
  let index = indexOfRouteByName(name);
  if (name && index > -1) {
    let item = {
      index,
      name: name,
      path: name,
      params: params,
      key: name + generateRandom()
    };
    if (index > -1) routers = routers.slice(0, index);
    routers.push(item);
    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: routers
      })
    );
  } else {
    routers.pop();
    let item = {
      name: name,
      params: params,
      path: name,
      key: name + generateRandom()
    };
    routers.push(item);
    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: routers
      })
    );
  }
  logInfo('replace', name);
}

/**
 * 打开一个新页面
 * @param name 路由名称
 * @param params 路由参数
 * @param newPage 是否开启新页面 默认false
 */
function open(name: string, params?: object, newPage?: boolean) {
  let index = indexOfRouteByName(name);
  let route = getRouteInfoByName(name);
  let item: any = {
    key: route?.key || name,
    name: name,
    path: name,
    params: params || null
  };
  // console.log('newPage', newPage, index);
  if (newPage || index === -1) item.key = name + generateRandom();

  _navigator.dispatch(CommonActions.navigate(item));
  logInfo('open', name);
}

/**
 * 将路由name重置到首页 并且清空路由栈
 * @param name 路由名称
 * @param params 路由参数
 */
function home(name: string, params?: object) {
  try {
    let item = {
      name: name,
      path: name,
      params: params,
      key: name + generateRandom()
    };

    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: [item]
      })
    );
  } catch (e) {
    errorHandler.noRoute(name);
  }
  logInfo('home', name);
}

/**
 * 路由回退
 * @param name 路由名称
 * @param params 回调参数
 */
function back(name?: string, params?: any) {
  let index = -1;
  let route = null;
  if (name) {
    index = indexOfRouteByName(name);
    route = getRouteInfoByName(name);
  } else {
    route = _navigator.getRootState().routes[_navigator.getRootState().routes.length - 1];
  }

  if (index > -1) {
    open(route.name, params || route.params);
  } else {
    close();
  }

  params &&
    _navigator.dispatch(
      CommonActions.setParams({
        params: params,
        key: route && route.key,
        source: route && route.key
      })
    );

  logInfo('back', name);
}

//顶部路由名称
function topPath() {
  return _navigator.getRootState().routes[0] || {};
}

//顶部路由名称
function isTopPath(name: string) {
  const topRoute = _navigator.getRootState().routes[0] || {};
  return topRoute.name === name;
}

/**
 * 关闭当前路由
 */
function close() {
  if (_navigator.canGoBack()) {
    _navigator.dispatch(CommonActions.goBack());
  }
  logInfo('close');
}

/**
 * 获取路由name在路由栈中当前的位置  从后往前查找
 * @param name 路由名称
 * @returns 返回index
 */
function indexOfRouteByName(name: string, isPrev?: boolean) {
  let routers = _navigator.getRootState().routes || [];
  let index = -1;
  if (isPrev) {
    return routers.findIndex((item: any) => item.name === name);
  } else {
    let routeIndex = routers.length;
    // console.log('indexOfRouteByName', name, routeIndex, routers);
    while (routeIndex > 0) {
      routeIndex--;
      let route = routers[routeIndex];
      if (route.name === name) {
        index = routeIndex;
        break;
      }
    }
  }

  return index;
}

/**
 * 根据页面名字获取页面所在路由的信息
 * @param  {String} name 页面名字
 * @return {{name,key,path,params}}      页面所在路由,如果没有则返回 null
 */
function getRouteInfoByName(name: string, isPrev?: boolean) {
  let routers = _navigator.getRootState().routes || [];
  let routeIndex = routers.length;
  if (isPrev) {
    return routers.find((item: any) => item.name === name);
  }
  while (routeIndex) {
    routeIndex--;
    let route = routers[routeIndex];
    if (route.name === name) {
      return route;
    }
  }
  return routers[routers.length];
}

/**
 * 获取当前的路由栈
 * @returns 返回路由栈
 */
function getRouters() {
  return _navigator?.getRootState()?.routes;
}
/**
 * 打开android app设置页面
 */
function openAppSetting() {
  return new Promise((resolve, reject) => {
    NativeModules.NativeRouter.openAppSetting();
  });
}

/**
 * 重新生成路由栈-只保留栈顶的路由和${name}路由
 * @param name 路由名称
 * @param params 路由参数
 */
function replaceOthers(name: string, params?: object) {
  let routers = _navigator.getRootState().routes || [];

  const newRouters = routers.slice(0, 1);
  try {
    let item = {
      name: name,
      path: name,
      params: params,
      key: name + generateRandom()
    };

    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: [...newRouters, item]
      })
    );
  } catch (e) {
    errorHandler.noRoute(name);
  }
  logInfo('replaceOthers', name);
}
/**
 * 回到路由栈中的第一个,并关闭所有其他路由
 */
function popToTop() {
  let routers = _navigator.getRootState().routes || [];
  const newRouters = routers.slice(0, 1);
  try {
    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: [...newRouters]
      })
    );
  } catch (e) {
    errorHandler.noRoute('popToTop');
  }
  logInfo('popToTop');
}

const logInfo = (type:string, msg?:string) => {
  console.log('msg', type, msg)
}


export default {
  home,
  open,
  replace,
  back,
  close,
  setNavigator,
  topPath,
  getRouters,
  openAppSetting,
  isTopPath,
  replaceOthers,
  popToTop,
};

ErrorHandler.js

const noop = (e:Error,isFatal:Boolean) => { };

export const setJSExceptionHandler = (customHandler = noop, allowedInDevMode = false) => {
    if (typeof allowedInDevMode !== "boolean" || typeof customHandler !== "function") {
        return;
    }
    const allowed = allowedInDevMode ? true : !__DEV__;
    if (allowed) {
        // !!! 关键代码
        // 设置错误处理函数
        global.ErrorUtils.setGlobalHandler(customHandler);
        // 改写 console.error,保证报错能被 ErrorUtils 捕获并调用错误处理函数处理
        console.error = (message, error) => global.ErrorUtils.reportError(error);
    }
};

export const getJSExceptionHandler = () => global.ErrorUtils.getGlobalHandler();

export default {
    setJSExceptionHandler,
    getJSExceptionHandler,
};

generateRandom方法

export const generateRandom = () => {
  return Math.random().toString(16).slice(2);
};

router.tsx

import React from 'react';
import { NativeStackNavigationOptions } from '@react-navigation/native-stack';
import Login from 'pages/login/index';
import HeaderBackArrow from 'components/HeaderBackArrow';
import { UITitleText } from 'components/Text/Texts';
import Loading from 'pages/loading';
import { GestureResponderEvent } from 'react-native-modal';
import MRouter from 'framework/MRouter';

//屏幕动画枚举
const animationType: any = {
  default: 'default',
  fade: 'fade',
  fade_from_bottom: 'fade_from_bottom',
  flip: 'flip',
  none: 'none',
  simple_push: 'simple_push',
  slide_from_bottom: 'slide_from_bottom',
  slide_from_right: 'slide_from_right',
  slide_from_left: 'slide_from_left'
};

//屏幕方向枚举
const orientationType = {
  default: 'default',
  all: 'all',
  portrait: 'portrait',
  portrait_up: 'portrait_up',
  portrait_down: 'portrait_down',
  landscape: 'landscape',
  landscape_left: 'landscape_left',
  landscape_right: 'landscape_right'
};

//屏幕默认config
// const screenConfig: NativeStackNavigationOptions = {
const screenConfig: any = {
  headerShown: true,
  headerShadowVisible: false,
  headerTitleAlign: 'center',
  headerBackTitle: '',
  headerBackVisible: false,
  headerBackTitleVisible: false,
  headerTintColor: '#3D3F43',
  animation: animationType.slide_from_right,
  orientation: orientationType.portrait,
  headerStyle: {
    backgroundColor: '#fff',
    // @ts-ignore
    borderBottomWidth: 0
  },
  headerTitleStyle: {
    fontSize: 18,
    fontWeight: '500',
    color: '#3D3F43'
  }
};
let tempTime = 0;
let clickNum = 0;
const onWelcome = (e: GestureResponderEvent) => {
  // console.log('onWelcome', e.timeStamp);
  const time = e.timeStamp;
  if (tempTime === 0) {
    tempTime = e.timeStamp;
  } else {
    if (time - tempTime > 300) {
      tempTime = time;
      clickNum = 0;
      return;
    }
    clickNum++;
    tempTime = time;

    if (clickNum === 11) {
      // Toast.info('连续点击8次,进入隐藏页面');
      // if (REACT_NATIVE_ENV !== 'PRODUCTION') {
      // }
      MRouter.open(routers.Login.name);
      clickNum = 0;
      tempTime = 0;
      return;
    }
  }
};

//路由option
const getOptions = (param: NativeStackNavigationOptions) => {
  const options: NativeStackNavigationOptions = {
    ...screenConfig,
    ...param,
    headerLeft: () => <HeaderBackArrow />,
    headerTitle: () => (
      <UITitleText onPress={onWelcome} suppressHighlighting={true}>
        {param.title || ''}
      </UITitleText>
    )
  };
  return options;
};

//在这里可以填写我们所需要的路由(页面) options可以配置我们对应的navigation配置
const routers = {
  Loading: { name: 'Loading', options: getOptions({ headerShown: false }), component: Loading },
  Login: { name: 'Login', options: getOptions({ title: '关于我们' }), component: Login },
};

//导出路由
export { routers };

App.tsx

// In App.js in a new project

import React, { useEffect} from 'react';
import { useAppSelector, useAppDispatch } from 'store/hook';
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import MyStyleSheet from 'components/MyStyleSheet';
import MRouter from 'framework/MRouter';
import { routers } from 'router/router';

const RootStack = createNativeStackNavigator();

const NavigatorFn: React.FC<{ navigationRef: any }> = ({ navigationRef }) => {
  const dispatch = useAppDispatch();
  const { action, action_params } = useAppSelector(state => state.account);

  const openPage = (pageName: string) => {
    const params = action_params ? action_params : {};
    MRouter.home(pageName, params);
  };

  useEffect(() => {
    MRouter.setNavigator(navigationRef?.current);
  }, []);


  useEffect(() => {
    console.log('action', action);
    switch (action) {
      case 'login':
        openPage(routers.Tabbar.name);
        break;
      default:
        openPage(routers.Loading.name);
        // openPage(routers.Tabbar.name);
        break;
    }
  }, [action]);

  return (
    <>
      <RootStack.Navigator
        // initialRouteName="Root"
        // backBehavior="history"
        screenOptions={{
          headerShown: false,
          orientation: 'portrait'
        }}>
        {routers &&
          Object.keys(routers).map((key, index) => {
            return (
              <RootStack.Screen
                key={index}
                name={routers[key].name}
                options={routers[key].options}
                getComponent={() => routers[key].component}
              />
            );
          })}
      </RootStack.Navigator>
    </>
  );
};

function App() {
  const navigationRef = useNavigationContainerRef();
  const dispatch = useAppDispatch();

  return (
    <SafeAreaProvider>
      <NavigationContainer
        ref={navigationRef}
        onStateChange={state => {
          // 全局路由埋点
          // if (state) {
          //   const { routes } = state;
          //   if (routes?.length > 0) {
          //     let data: any = {};
          //     if (routes.length > 1) {
          //       const { name = '', key = '' } = routes[1];
          //       const { name: preName = '', key: preKey = '' } = routes[0];
          //       data.page_from = preName;
          //       data.page = name;
          //       data.page_from_key = preKey;
          //       data.page_to = name;
          //       data.page_to_key = key;
          //     } else {
          //       const { name, key } = routes[0];
          //       data.page_to = name;
          //       data.page_key_to = key;
          //     }
          //     TrackEventManager.trackEvent(TrackEventEnum.page_jump, {
          //       ...data,
          //       event: TrackEventEnum.page_jump,
          //       event_desc: '页面跳转',
          //       page: '页面'
          //     });
          //   }
          // }
        }}>
        <NavigatorFn navigationRef={navigationRef} />
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

const styles = MyStyleSheet.create({
  header: {
    height: 28,
    marginBottom: 8,
    // backgroundColor: '#f00',
    justifyContent: 'center',
    alignItems: 'center'
  },
  headerTitle: {
    fontSize: 18,
    fontWeight: '500'
  },
});

export default App;

结束语

懂的都懂

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

推荐阅读更多精彩内容