Bootstrap

react错误捕获

import {Component} from 'react';
export default class WeirwoodErrorBoundary extends Component {

    static getDerivedStateFromError(error) {
        return {error};
    }

    constructor(props) {
        super(props);
        this.state = {
            error: null
        };
    }

    componentDidCatch(error, errorInfo) {
        this.setState({
            error
        });
        if (window.__Weirwood) {
            window.__Weirwood.error.captureException(error);
        }
    }
    render() {
        if (this.state.error) {
            return <div>页面出错了!请稍后重试</div>;
        }
        return this.props.children;
    }
}

调用

Main.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Switch} from 'react-router';
import {Provider} from 'react-redux';
import ConnectedRouter from './common/connected-react-router';
import WeirwoodErrorBoundary from './common/components/WeirwoodErrorBoundary';
import './resource/style/main.less';
import InitRoutes from './router';
ReactDOM.render(
        <WeirwoodErrorBoundary>
            <Provider store={store}>
                <ConnectedRouter history={history}>
                    <Switch>
                        <InitRoutes store={store} history={history} />
                    </Switch>
                </ConnectedRouter>
            </Provider>
        </WeirwoodErrorBoundary>,
        document.getElementById('root')
    );

src/app/entry/action.js

/**
 * @file action
 * @author [email protected]
 *
 **/

import {get, noop, omit, pick, set, size} from 'lodash';
import {get as getCookie} from 'lib/storage/cookie';
import {set as setStorage, get as getStorage, remove as removeStorage} from 'lib/storage/localStorage';
import {updateRoute, getUrlParam} from 'common/util';
import {logoutAccount} from 'common/util/logout';
import {resetShopInfo} from 'app/shopInfo/action';
import {getBizCode} from 'common/util/platform';
import {NO_AUTH} from 'src/config';
import {
    SELECTED_APP_KEY,
    INIT_API_CACHE_KEY,
    STORAGE_LOGIN_TYPE,
    INIT_FOR_DUXIAODIAN,
    FROM_MERCHANT_KEY,
    PARENT_ID_KEY
} from 'common/config/storageKey';
import {COOKIE_NAME} from 'common/config/cookieConfig';
import {appInitIsloadingSelector} from 'common/selectors/platform';
import message from 'common/containers/wrapReduxFlowForm/formItemFactory/message';
import ACTIONS from './actionTypes';
import {HOME_GUIDE_LOCAL_STORAGE_NAME, SHOW_MENU} from './config';
import {loadCommissionInfo} from './service/loadCommissionInfo';
import {INIT_SESSION_ID} from 'common/config/logConfig';
import {isPassSelector} from '../../../src/selectors';
import {checkUserQulification} from '../assetCenter/service';
import {errorInformer} from '../assetCenter/util';
import {whichBizTypeSelector} from '../../common/selectors/bizType';
import baiduMonitor, {setUserProperty} from '../../common/util/baiduMonitor';
import {basicInfoAction} from './reducer/basicInfo';
import {userIdSelector, optIdSelector, isSonAccountSelector} from 'common/selectors/platform.js';
import {getShopRegisterPath} from 'src/common/util/getPath';
import {needGetHagList} from 'src/common/config/hagInfo';
import {initSurvey} from './util';
import * as api from './service';
import * as homeApi from 'app/home/apis';
import {onlineService} from 'src/common/util/quarkRequest';

const INIT_CACHE_TIME = 43200000; // 12小时
/**
 * 更新路由
 * @param {string} options.nav      一级导航path
 * @param {Object} options.match    match
 * @param {Object} options.isPurifyQuery    路由
 * @return {Function}               回调
 */
export const onUpdateRoute = (params = {}) => (dispatch, getState) => {
    const {nav, match} = params;
    dispatch({
        type: ACTIONS.MENU_CLICK_ROUTER_CHANGE,
        payload: {
            key: nav,
            match: match
        }
    });
    // 路由点击统一增加参数,功能:单独针对生活服务有子店路由全部拼接subShopId参数,兼容强制刷新选中子店逻辑
    updateRoute(params, dispatch, getState());
};

export const getToken = state => {
    const isPass = isPassSelector(state);
    const ucToken = getCookie('CPTK__513') || getCookie('__cas__st__513') || '';
    const token = isPass
        ? ''
        : ucToken;
    return token;
};

export const changeSelectedBizAction = (appInfo = {}) => (dispatch, getState) => {
    const {appId, subAppId, appName} = appInfo;
    const shopId = get(appInfo, 'shopInfo.shopId');
    const fromMerchant = getStorage(FROM_MERCHANT_KEY);
    const monitorShopInfo = {
        appId,
        subAppId,
        appName,
        sessionId: INIT_SESSION_ID,
        fromMerchant: +fromMerchant || 0
    };
    shopId && (monitorShopInfo.shopId = shopId);
    appId && setUserProperty(monitorShopInfo);
    const bizCode = getBizCode(appInfo);
    // 存储选中的业务线到localStorage
    setStorage(SELECTED_APP_KEY, appInfo);
    dispatch({
        type: ACTIONS.SELECTED_BIZ_CHANGE,
        payload: {
            ...appInfo,
            bizCode
        }
    });
    dispatch(resetShopInfo(get(appInfo, 'shoInfo')));
};

export const initBasicInfo = (params = {}) => (dispatch, getState) => {
    dispatch({
        type: basicInfoAction['ON_BASIC_INFO_RESET']
    });
};

export const changeBasicInfo = (params = {}) => (dispatch, getState) => {
    dispatch({
        type: basicInfoAction['ON_BASIC_INFO_CHANGE'],
        payload: params
    });
    if (window.__Weirwood && window.__Weirwood.error && window.__Weirwood.error.setContext) {
        window.__Weirwood.error.setContext(params);
    }
    if (window.__Weirwood && window.__Weirwood.perf && window.__Weirwood.perf.addVar) {
        window.__Weirwood.perf.addVar(params);
    }
};

export const passBindUcAction = (params = {}) => (dispatch, getState) => {
    return dispatch(api.passBindUcService(params));
};

export const fetchAppListAction = (params = {}) => (dispatch, getState) => {
    return dispatch(api.fetchAppInfoListServer(params));
};

export const getMenuListAction = (params = {}) => (dispatch, getState) => {
    const bizCode = whichBizTypeSelector(getState());
    return dispatch(api.fetchMenuListServer(params, bizCode));
};

export const getUserHagInfo = (params = {}) => (dispatch, getState) => {
    return dispatch(api.fetchUserHagInfoServer(params));
};

export const appInitLoadingChangeAction = (payload = false) => (dispatch, getState) => {
    dispatch({
        type: ACTIONS.APP_INIT_IS_LOADING,
        payload
    });
};

export const getShopDetailAction = (params = {}) => (dispatch, getState) => {
    return dispatch(api.fetchShopDetailServer(params));
};

export const getShopContactAction = (params = {}) => (dispatch, getState) => {
    return dispatch(homeApi.getShopContactInfo(params));
};

export const checkHasRoleAction = (params = {}) => (dispatch, getState) => {
    return dispatch(api.fetchCheckHasRoleServer(params));
};

export const changeSelectedBiz = (
    app = {},
    openedJumpPath,
    finallyCb = {}
) => (dispatch, getState) => {
    const appInitIsloading = appInitIsloadingSelector(getState());
    // 如果正在请求,不执行后续动作;
    if (appInitIsloading) {
        return Promise.resolve();
    }

    // 如果URL上存在fromMerchant字段,则代表来源为新登录页merchantLogin,存入redux。后续不置为false
    const urlFromMerchant = getUrlParam('fromMerchant', window.location.search) || 0;
    const urlParentid = getUrlParam('parentid', window.location.search) || 0;
    urlFromMerchant && setStorage(FROM_MERCHANT_KEY, parseInt(urlFromMerchant, 10));
    urlParentid && setStorage(PARENT_ID_KEY, parseInt(urlParentid, 10));

    const {
        initFinally,
        menuListFinally
    } = finallyCb;
    dispatch(changeSelectedBizAction(omit(app, ['mainUcId'])));
    const appId = get(app, 'appId');
    const subAppId = get(app, 'subAppId');
    // 当缓存中存在对应mainUcId,这里会传入mainUcId
    const mainUcId = get(app, 'mainUcId');
    const appParams = {
        appId,
        subAppId,
        shopId: null,
        mainUcId
    };
    dispatch(appInitLoadingChangeAction(true));
    const bizCode = whichBizTypeSelector(getState());
    const nowDate = new Date();
    const today = `${nowDate.getFullYear()}-${nowDate.getMonth() + 1}-${nowDate.getDate()}`;
    const cacheParams = {
        duxiaodian: {
            cacheKey: INIT_FOR_DUXIAODIAN,
            cacheType: 'local',
            // 获取对应日期对应用户缓存
            cacheGet: ({cacheLocalItem, paramsStr}) => {
                return get(cacheLocalItem, `${today}.${paramsStr}`, '');
            },
            // 设定缓存
            cacheSet: ({response, paramsStr}) => {
                // 仅在入驻完成后缓存
                if (get(response, 'data.shopInfo.showMenu', 0) !== SHOW_MENU) {
                    return;
                }
                // 取出缓存
                const allInit = getStorage(INIT_FOR_DUXIAODIAN) || {};
                // 追加本次缓存
                set(allInit, `${today}.${paramsStr}`, JSON.stringify(response));
                // 取当天缓存进行缓存set
                setStorage(INIT_FOR_DUXIAODIAN, pick(allInit, today));
            },
            delay: 1000 * 20
        },
        others: {
            cacheKey: INIT_API_CACHE_KEY.init,
            cacheType: 'local',
            cacheTime: INIT_CACHE_TIME,
            active: 'manual'
        }
    };
    // init接口获取ucId和passId,营销模块ucId必传,店铺保存接口必须passId,
    return dispatch(api.passBindUcService(appParams, cacheParams.others)).then(res => {
        if (res.redirect) {
            return;
        }
        // 存储账户类型
        setStorage(STORAGE_LOGIN_TYPE, getCookie(COOKIE_NAME.LOGIN_TYPE));
        dispatch(changeBasicInfo({
            userId: res.ucId,
            optId: res.sonUcId,
            userName: res.mainUcName,
            optName: res.ucName,
            hasHitSmallFlow: res.localSmallFlow || 0 // 0为未命中,1为命中
        }));
        // 多主账号子账号,跳转shopCenter
        // TODO 这里可能影响mcc需要重点回归 @wuxinru
        if (!mainUcId && res?.isSonAccount && size(res?.mainShopList) > 1) {
            dispatch(appInitLoadingChangeAction(false));
            dispatch(onUpdateRoute({nav: '/shopCenter', isReplace: true}));
            return;
        }
        // 存储账户类型
        const shopId = get(res, 'shopInfo.shopId');
        // 咨询状态改为上线
        if (shopId) {
            onlineService({
                serviceId: res.ucId,
                optId: res.sonUcId,
                shopId
            }).catch(noop);
        }
        const showMenu = get(res, 'shopInfo.showMenu');
        baiduMonitor('all_system_entry');
        dispatch(changeSelectedBizAction({
            ...app,
            shopInfo: get(res, 'shopInfo'),
            isInHag: res.isInHag,
            ...(res?.shopInfo
                ? {
                    appId: get(res, 'shopInfo.appId'),
                    subAppId: get(res, 'shopInfo.subAppId')
                }
                : {}
            )
        }));
        // 拉取当前用户的hag名单
        if (needGetHagList.length) {
            dispatch(api.fetchUserHagInfoServer({
                item: {
                    hagRoles: needGetHagList,
                    shopId
                }
            })).catch(noop);
        }
        dispatch(appInitLoadingChangeAction(false));
        const shopRegisterNav = getShopRegisterPath();
        const isSonAccount = isSonAccountSelector(getState());
        // 店铺ID存在并且showMenu为1
        if (shopId && showMenu) {
            // 请求菜单需要加菜单loading态
            dispatch(api.fetchMenuListServer({
                ...appParams,
                shopId
            }, isSonAccount ? {} : { // 子账号不缓存菜单
                cacheKey: INIT_API_CACHE_KEY.moduleList,
                cacheType: 'local',
                cacheTime: INIT_CACHE_TIME,
                delay: 60000
            })).then(() => {
                // 请求完新业务线的菜单,需要跳转首页
                if (openedJumpPath) {
                    if (openedJumpPath === shopRegisterNav) {
                        updateRoute({nav: '/home'}, dispatch, getState());
                    }
                    else {
                        updateRoute({nav: openedJumpPath}, dispatch, getState());
                    }
                }
            }).finally(() => {
                menuListFinally && menuListFinally();
            }).catch(noop);
            dispatch(api.fetchCheckHasRoleServer({roleName: 'ai_create_product'})).catch(noop);
        }
        // 未入驻成功,进入店铺入驻
        else {
            dispatch(onUpdateRoute({nav: shopRegisterNav, isReplace: true}));
        }

        if (!window.surveyApp) {
            initSurvey({
                userid: res.ucId,
                optid: res.sonUcId
            });
        }
        dispatch(authority());
    }).catch(err => {
        const errCode = +get(err, 'errors.0.code');
        let defaultMsg = '';
        if (errCode === NO_AUTH) {
            defaultMsg = '抱歉,您当前账号暂无开通业务权限。';
        }
        const errMsg = get(err, 'errors.0.message', defaultMsg);
        errMsg && message.error(errMsg);
    }).finally(re => {
        initFinally && initFinally();
    });
};

// 鉴权
export const authority = () => (dispatch, getState) => {
    const userId = userIdSelector(getState());
    const optId = optIdSelector(getState());
    const changeAuth = payload => dispatch({
        type: ACTIONS.ON_AUTH_INFO_CHANGE,
        payload
    });
    changeAuth({userId, optId});
};

// 发起登出
export const logoutUcAndPass = () => (dispatch, getState) => {
    // 手动点击退出登录
    logoutAccount();
};


export const getCommissionInfo = params => dispatch => {
    return dispatch(loadCommissionInfo(params));
};

export const checkQulification = () => dispatch => {
    dispatch(checkUserQulification({})).catch(e => {
        errorInformer(e.error);
    });
};

export const setGuideStorage = () => dispatch => {
    const {KEY, OPTIONS: {VISIBLE}} = HOME_GUIDE_LOCAL_STORAGE_NAME;
    // 用户登录时不存在localStorage:KEY时,添加KEY
    !getStorage(KEY) && setStorage(KEY, VISIBLE);
};

router/index.js

import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as actionCreators from 'src/app/entry/action';
import {
    menuMapSelector,
    getShopShowMenuRes,
    getRouterPathname,
    getUrlSearch,
    isShopRegisterPage,
    querySubShopIdSelector
} from 'common/selectors/platform';

import {
    isServiceEcommerceBizSelector
} from 'common/selectors/bizType';
import Main from './Main.js';

const mapStateToProps = state => {
    const menuMap = menuMapSelector(state);
    const isShopRegister = isShopRegisterPage(state);
    const isService = isServiceEcommerceBizSelector(state);
    return {
        menuMap,
        showMenu: getShopShowMenuRes(state),
        pathname: getRouterPathname(state),
        search: getUrlSearch(state),
        isShopRegister,
        isService,
        querySubShopId: querySubShopIdSelector(state)
    };
};

const mapDispatchToProps = dispatch => {
    return bindActionCreators(actionCreators, dispatch);
};

export default connect(mapStateToProps, mapDispatchToProps)(Main);

;