Knowledge required:

  1. React + TSX Family bucket
  2. qiankun
  3. DataV
  4. plop
  5. scp2
  6. Mock.js simulates back-end requests

So much for generating projects and getting straight to business!

Project directory

├ ─ ─ a mock / / / mock services end data simulation ├ ─ ─ public / └ ─ ─ SRC / ├ ─ ─ the setting / / / the plop automatically generated document template configuration ├ ─ ─ the API request / / / interface to store ├ ─ ─ common / / / public interface ├ ─ ─ the components / / / public directory components ├ ─ ─ the module / / / base class and global context location ├ ─ ─ the static / / / static resource management ├ ─ ─ store / / / state management ├ ─ ─ function directory utils / / / tools └ ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── Automatic upload FTP ├── prettier.config.js // Save automatic Formatting ├─ tsconfig.json // TypeScript Config file ├─ config-overrides └ ─ ─ package. JsonCopy the code

This article is a bit long, please be patient.Project Git repositoryWelcome to Light up the little star 🌟🌟

Building the underlying architecture

Qiankun article

1. Installation and configuration qiankun

npm i qiankun
Copy the code

When the package is loaded, we can configure the project

Create the config-overrides. Js file in the root directory of the main project and write the following configuration

Create the config-overrides. Js config-overrides-proxy.js file in the subproject directory and write the following configuration

1.config-overrides.js

2.config-overrides-proxy.js

The configuration of app. TSX in the main project is as follows:

According to the above configuration can run qiankun

Axios interceptor section

2. Install and configure axiOS

 npm i axios
Copy the code

Create a new HTTP directory under the utils directory and create the model.ts interface file and the request.ts configuration file respectively

Main response interception and request interception

Request to packaging

Just throw the request at the end

2. Model.ts This is the interface that requests encapsulation

Now how do we use it

Encapsulate a request function whose return value is a Promise, and define generics to control the type hint of the return value

This way we can pass in a type to the caller and prompt for the return type

Scp2 configuration article

3. Install and configure SCP2

npm i scp2 -D

Copy the code

See another scP2 configuration article I wrote for detailsScp2 allows you to package deployment servers with one click

Plop template generation

4. Install and configure PLOP

npm i plop -D

Copy the code

See another scP2 configuration article I wrote for detailsPlop, let you own your own scaffolding

Redux article

5. Install and configure redux dependencies

npm i react-redux redux-devtools-extension redux-thunk

Copy the code

The configuration is as follows:

  1. index.ts

  1. ActiveTypes. Ts active constants

3. Shorten the basic configuration

Get down to business: code

1. Route guard

Since monitoring-callbacks such as onEnter were abandoned after the react-Router 4.0 release, higher-order functions were used to guard the route, with the following code:

Import React from 'React' import {IMenuItem} from '.. /common/model/IMenuItem' import Login from '.. /pages/login/login' import PageOne from '.. /pages/pageOne' import PageTwo from '.. /pages/pageTwo' export const ASSETS_MENUS: IMenuItem[] = [{path: '/page-one', title: 'page-one', exact: False, isShowTitle: true, render: () => <PageOne />}, {path: '/ PageOne ', title: 'PageOne ', exact: false, isShowTitle: true, isShowTitle: () => <PageOne />}, {path: '/ PageOne ', title:' PageOne ', exact: false, isShowTitle: () => <PageOne />}, True, render: () => <PageTwo />}, {path: '/login', title: 'login', exact: false, isShowTitle: false, render: () => <PageTwo />}, () => <Login />}] import React from 'React' import {BrowserRouter, Switch, Route, Redirect, NavLink, RouteComponentProps } from 'react-router-dom' import { createBrowserHistory } from 'history' import { ASSETS_MENUS } from '.. /.. /module/routerLink' import { IMenuItem } from '.. /.. /common/model/IMenuItem' import './routeLink.scss' import { EchartContext, InitEchartContext, InterInitEchartContxt } from '.. /.. /module/echarts' import { connect } from 'react-redux' import { InterUser, InterUserInfo } from '.. /.. /store/model/IUser' import { ICombinedState } from '.. /.. /store/reducers' import Cookie from 'js-cookie' import { Dispatch } from 'redux' import { SET_TOKEN, SET_USERINFO } from '.. /.. /store/activeTypes' import { getUserInfo } from '.. /.. /api/userApi' interface IRouterProps extends RouteComponentProps { token: string setToken: (token: string) => void setUserInfo: (userInfo: InterUserInfo) => void [key: string]: any } interface IRouterState { menus? : IMenuItem[] } class RouterLink extends React.Component<IRouterProps, IRouterState> { state: IRouterState = {} componentDidMount() { this.setState({ menus: ASSETS_MENUS }) } routerList = () => { const menus = this.state.menus return ( <div className='nav dispaly-content-center mt-1'> { menus? .map(i => i.isShowTitle && <NavLink key={ i.path } to={ i.path } className='nav-item' activeClassName='nav-item-active'>{ i.title }</NavLink> ) } </div> ) } render() { const echartContext: InterInitEchartContxt = new EchartContext() return ( <div> <InitEchartContext.Provider value={ echartContext } > <BrowserRouter> { this.props.token && this.routerList() } <Switch> {/* { this.state.menus? .map(i => ( // <Route path={ i.path } exact={ i.exact } render={i.render} key={ i.path } /> )) } */} <RouterGuard menus={ this.state.menus } token={ this.props.token } setToken={ this.props.setToken } setUserInfo={ this.props.setUserInfo } location={this.props.location} match={this.props.match} history={this.props.history} /> {/* <Route path='*' exact render={props => <RouterGuard routeInfo={ props } menus={ this.state.menus } token={ this.props.token } setToken={ this.props.setToken } setUserInfo={ this.props.setUserInfo } />} /> */} </Switch> </BrowserRouter> </InitEchartContext.Provider> </div> ) } } interface RouterGuardProps extends IRouterProps { menus? : IMenuItem[] [key: string]: Any} class RouterGuard extends React.Component<RouterGuardProps, {}> { async componentDidMount() { const { token: reduxToken } = this.props const token = Cookie.get('USER_TOKEN') if ( token && ! reduxToken ) { this.props.setToken(token as string) await getUserInfo<InterUserInfo>().then(res => { this.props.setUserInfo(res) }) } } render() { const { menus, token: reduxToken, location } = this.props const history = createBrowserHistory() let toPath = location.pathname if (! reduxToken && toPath ! == '/ menus ') {toPath = '/login' history.replace(toPath)} // Get the current route object const component: IMenuItem = menus? .find(i => i.path === toPath) as IMenuItem if (! Return <Route path={toPath} exact={component.exact} return <Route path={toPath} exact={component.exact} Render ={component.render} key={component.path} />} // login and jump to login page, It returns at the next higher level if (reduxToken && toPath = = = '/ login') {history. GoBack () return null} / / a redirect page if (toPath = = = '/' | |! menus? .some(c => toPath ! == c.path) ) { return <Redirect to='/page-one' /> } // return this.props.token ? <Redirect to={ props.match.url } /> : <Redirect to='/login' /> If (reduxToken) {return <Route path={component.path} exact={component.exact} render={component.render} key={ component.path } /> } else { return <Redirect to='/login' /> } } } const mapStateToProps  = (state: ICombinedState): InterUser => state.user const mapDispatchToProps = (dispatch: Dispatch) => ({ setToken: (token: string) => dispatch({ type: SET_TOKEN, token }), setUserInfo: (userInfo: InterUserInfo) => dispatch({ type: SET_USERINFO, userInfo }) }) export default connect( mapStateToProps, mapDispatchToProps )(RouterLink)Copy the code

2. Global base classes

Used to write some global public methods

import React from 'react' import Cookie from 'js-cookie' import Message, { InterMessagesProps } from '.. /components/messages/messages' export interface IOptions { msg: any, isShowLoading: boolean } export interface InterAbstractComponent { closeLoadingShow? : () => void setLoadingState? : (loadingInfo: IOptions) => void message? : (options: InterMessagesProps) => void setToken? : (token: string) => void getToken? : () => string } export class AbstractComponent< P extends InterAbstractComponent, S, SS = any > extends React.PureComponent<P, S, SS> { private USER_TOKEN = 'USER_TOKEN' message(options: InterMessagesProps) { // eslint-disable-next-line no-new new Message(options) } closeLoadingShow() { this.props.setLoadingState? .({ msg: '', isShowLoading: false }) } setToken(token: string) { Cookie.set(this.USER_TOKEN, token) } getToken() { return Cookie.get(this.USER_TOKEN) } removeToken() { return Cookie.remove(this.USER_TOKEN) } }Copy the code

3. Global Echart context

Unified management of ECHART configuration and structure logic, packaging and reuse of the same chart

import React from 'react'
import echarts from 'echarts/lib/echarts'
// import * as gexf from 'echarts/extension-src/dataTool/gexf'

import 'echarts/lib/chart/line'
import 'echarts/lib/chart/bar'
import 'echarts/lib/chart/scatter'
import 'echarts/lib/chart/graph'

import 'echarts/lib/component/legend'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/toolbox'
import 'echarts/lib/component/graphic'
import 'echarts/lib/component/legendScroll'

import { EChartsFullOption } from 'echarts/lib/option'
import { ScatterSeriesOption } from 'echarts/lib/chart/scatter/ScatterSeries'
import { IEcharts, ILineSeries, IScatterSeries } from '../pages/pageOne/model'
import { ILinks, ICategories, INodes } from '../common/model/IGrah'

export interface InterInitEchartContxt {
  initEchart: (id: string, type: EChartsFullOption) => void
  getDocumentElementId: (id: string) => HTMLElement

  lineDataFormat: (echartData: IEcharts) => EChartsFullOption
  lineSeriesDataFormat: (ser: ILineSeries[]) => ILineSeries[]

  scatterDataFormat: (echartData: IEcharts) => EChartsFullOption
  scatterSeriesObj: () => ScatterSeriesOption
  scatterSeriesTitleFormat: (srcData: IScatterSeries[]) => string[]
  scatterSeriesDataFormat: (srcData: IScatterSeries[]) => ScatterSeriesOption[]

  graphOptionsFormat: (echartData: any) => EChartsFullOption
  graphDataFormat: (echartData: any) => { links: any[], nodes: any[], categories: ICategories[] }
  grahNodeDataFormat: (nodes: any) => INodes[]
  grahLinksDataFormat: (links: any) => ILinks[]
}

export class EchartContext implements InterInitEchartContxt {
  getDocumentElementId(id: string): HTMLElement {
    return document.getElementById(id) as HTMLElement
  }

  initEchart(id: string, data: EChartsFullOption) {
    echarts.init(this.getDocumentElementId(id) as HTMLElement).setOption(data)
  }

  lineDataFormat(echartData: IEcharts): EChartsFullOption {
    return {
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'cross',
          crossStyle: {
            color: '#616E80'
          }
        }
      },
      legend: {
        data: echartData.titleArr,
        textStyle: {
          color: '#6D7988'
        },
        right: 'left'
      },
      grid: {
        top: '20%',
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
      },
      xAxis: [
        {
          type: 'category',
          data: echartData.xAxisArr,
          axisPointer: {
            type: 'shadow'
          }
        }
      ],
      yAxis: [
        {
          type: 'value',
          min: 0,
          max: 80,
          interval: 20,
          axisLabel: {
            formatter: '{value}',
            color: '#728FAA'
          },
          splitLine: {
            lineStyle: {
              type: 'dashed',
              color: '#6D7988'
            }
          }
        },
        {
          type: 'value',
          min: 0,
          max: 100,
          interval: 25,
          axisLabel: {
            formatter: '{value}%',
            color: '#728FAA'
          },
          splitLine: {
            lineStyle: {
              type: 'dashed',
              color: '#6D7988'
            }
          }
        }
      ],
      series: this.lineSeriesDataFormat(echartData.seriesArr as ILineSeries[])
    }
  }

  lineSeriesDataFormat(ser: ILineSeries[]): ILineSeries[] {
    ser.map(i => {
      if (i.type === 'line') {
        i.yAxisIndex = 1
        i.lineStyle = {
          color: '#1EBCA1'
        }
        i.itemStyle = {
          color: '#1EBCA1'
        }
      }

      return i
    })
    return ser
  }

  scatterSeriesTitleFormat(srcData: IScatterSeries[]): string[] {
    let titleArr: string[] = []
    srcData.forEach(i => {
      titleArr = [...titleArr, i.d1 as string]
    })
    return titleArr
  }

  scatterSeriesObj(): ScatterSeriesOption {
    return {
      name: '',
      data: [],
      type: 'scatter',
      symbolSize: function(data) {
        return Math.sqrt(data[0]) / 5 // 球球大小
      },
      emphasis: {
        label: {
          show: true,
          formatter: function(param: { data: any[]; }) {
            return param.data[2]
          },
          position: 'top'
        }
      },
      itemStyle: {
        shadowBlur: 10,
        shadowColor: 'rgba(120, 36, 50, 0.5)',
        shadowOffsetY: 5
      }
    }
  }

  scatterSeriesDataFormat(srcData: IScatterSeries[]): ScatterSeriesOption[] {
    let seriesArr: ScatterSeriesOption[] = []
    let curObj = {}
    srcData.forEach(i => {
      curObj = {
        data: [[i.d3, i.d2, i.d1]],
        name: i.d1
      }
      seriesArr = [
        ...seriesArr,
        {
          ...this.scatterSeriesObj(),
          ...curObj
        }
      ]
    })
    return seriesArr
  }

  scatterDataFormat(echartData: IEcharts): EChartsFullOption {
    return {
      legend: {
        type: 'scroll',
        top: 10,
        data: echartData.data && this.scatterSeriesTitleFormat(echartData.data),
        textStyle: {
          color: '#6D7988'
        },
        pageButtonPosition: 'end'
      },
      xAxis: {
        axisLabel: {
          color: '#728FAA'
        },
        splitLine: {
          lineStyle: {
            type: 'solid',
            color: 'transparent'
          }
        }
      },
      yAxis: {
        axisLabel: {
          color: '#728FAA'
        },
        splitLine: {
          lineStyle: {
            type: 'dashed',
            color: '#6D7988'
          }
        },
        scale: true
      },
      grid: {
        top: '20%',
        left: '3%',
        right: '12%',
        bottom: '3%',
        containLabel: true
      },
      series: echartData.data && this.scatterSeriesDataFormat(echartData.data)
    }
  }

  graphOptionsFormat(echartData: any): EChartsFullOption {
    const { links, nodes, categories } = this.graphDataFormat(echartData)
    return {
      animationDurationUpdate: 1500,
      animationEasingUpdate: 'quinticInOut',
      series: [
        {
          name: 'Les Miserables',
          type: 'graph',
          layout: 'circular',
          circular: {
            rotateLabel: true
          },
          data: nodes,
          links: links,
          categories: categories,
          roam: true,
          label: {
            position: 'right'
          },
          lineStyle: {
            color: 'source',
            curveness: 0.3
          }
        }
      ]
    }
  }

  graphDataFormat(echartData: any): { links: any[], nodes: any[], categories: ICategories[] } {
    const nodes = echartData.gexf.graph[0].nodes[0].node
    const categories = []
    for (let i = 0; i < 9; i++) {
      categories[i] = {
        name: '类目' + i
      }
    }
    return {
      links: this.grahLinksDataFormat(echartData.gexf.graph[0].edges[0].edge),
      nodes: this.grahNodeDataFormat(nodes),
      categories
    }
  }

  grahNodeDataFormat(nodes: any): INodes[] {
    let newNodes: any[] = []
    nodes.forEach((c:any, i: number) => {
      newNodes = [...newNodes, {
        attributes: { [c.attvalues[0].attvalue[0].$.for]: Number(c.attvalues[0].attvalue[0].$.value) },
        id: c.$.id,
        name: c.$.label,
        category: Number(c.attvalues[0].attvalue[0].$.value),
        itemStyle: null,
        symbolSize: Number(c['viz:size'][0].$.value) / 3,
        label: { normal: { show: (Number(c['viz:size'][0].$.value) / 3) > 8, textStyle: {
          color: '#5FEBF2',
          fontWeight: 700
        }}},
        value: Number(c['viz:size'][0].$.value),
        x: Number(c['viz:position'][0].$.x),
        y: Number(c['viz:position'][0].$.y)
      }]
    })

    return newNodes
  }

  grahLinksDataFormat(links: any): ILinks[] {
    let newLinks: any[] = []
    links.forEach((c:any) => {
      newLinks = [...newLinks, {
        ...c.$,
        lineStyle: { normal: {}},
        name: ''
      }]
    })
    return newLinks
  }
}

export const InitEchartContext = React.createContext<InterInitEchartContxt>(null as any)

Copy the code

4. The Layout of the project

<div className='App'> <FullScreenContainer style={{ background: ' radial-gradient(ellipse closest-side, #125886, #000e25)' }}> { isShowLoading ? <Loading>{ msg }</Loading> : Null} {this.props.user.token && (<HeaderTop headerTitle=' ProposeDTemplate 'currentTime={new Date().getTime()} userInfo={this.props.user.userInfo} logout={() => this.props.logout()} /> ) } <RouterLink /> </FullScreenContainer> </div>Copy the code

Project configuration and architecture is the above, specific details or toUnder the clone projectWatch carefully, the small star of the project needs each guest officer to light up oh, thank you! , if you need to reprint, articles, projects belong to the original, please contact the fundamental author