react+webpack+mock+mobx全家桶react项目

Authored by Eirice

项目背景:特殊人群管控监测模型

涉及技术应用:react 11.6,mobx,webpack,mock,mapbox,v-block-lite

项目地址:https://github.com/Eirice01/exhuman

下载地址:https://github.com/Eirice01/exhuman.git

建议使用chrom70+,npm

webpack文件配置

const path = require('path');
const fse  = require('fs-extra');

const ROOT = (...args) => path.resolve.apply(null, [__dirname, '../'].concat(args ? args : []));
const SRC  = (...args) => path.resolve.apply(null, [__dirname, '../', 'src'].concat(args ? args : []));

const _src = SRC();
const _modules = SRC('modules');

const getDirectorys = base => fse.readdirSync(base).reduce((acc, name) => {
  const item = path.join(base, name);
  const stat = fse.statSync(item);
  return stat.isDirectory() && acc.push({ name, item }), acc;
}, []);

const FRAMESET = getDirectorys(_src);
const MODULEs  = getDirectorys(_modules);

const getFiles = (route, filter) => {
  if(!fse.pathExistsSync(route)) return null;

  return fse.readdirSync(route).filter(filter || (() => true)).map(file => path.join(route, file));
}

module.exports = {
  ROOT, SRC, getFiles, FRAMESET, MODULEs
};

webpack基础配置

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const { name } = require('../package.json');
const { ROOT } = require('./tools');
const { CONFIG, _src, _mocks } = require('./config-common');

const entry = { app: ROOT('src', 'index.js') };

const devServer = {
  host: '0.0.0.0',
  open: false,
  progress: true,
  compress: false,
  stats: 'errors-only',
  port: 3001,
  disableHostCheck: true,
  contentBase: [_src, _mocks],
  proxy: {

  }
};
const plugins = origin => [
  new webpack.EnvironmentPlugin({
    USE_MOCK_SERVICE: true
  }),
  ...origin,
  new HtmlWebpackPlugin({
    title: name,
    template: ROOT('public', 'index.html'),
    inject: 'body',
    chunks: ['app']
  })
];

const configs = CONFIG({
  mode: 'development',
  entry,
  devtool: "source-map",
  target: 'web',
  node:{fs:"empty"},
  devServer,
  plugins
});
module.exports = configs;

webpack config.js

const { ROOT, SRC, FRAMESET } = require('./tools');

const [_root, _src, _index, _dist, _mocks] = [ROOT(), SRC(), SRC('index.js'), ROOT('dist'), ROOT('mocks')];

const alias = FRAMESET.reduce((a, { name, item }) => (a[`@${name}`] = item, a) , {
  '@map-data': SRC('assets', 'map-data')
});
console.log(SRC('assets', 'svg-react'))
const rules = {
  _items_: {
    eslint: {
      test: /\.(js|conf)$/,
      enforce: 'pre',
      use: 'eslint-loader',
      exclude: /node_modules/
    },
    babel: {
      test: /\.(js|conf)$/,
      use: 'babel-loader',
      exclude: /node_modules/
    },
    css: {
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    },
    less: {
      test: /\.less$/,
      use: ['style-loader', { loader: 'css-loader', options: { sourceMap: false } }, 'less-loader']
    },
    png_jpg_etc: {
      test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
      use: [{ loader: 'url-loader', options: { limit: 100 } }],
      exclude: [SRC('assets', 'svg-react'),SRC('assets', 'map-img')]
    },
    map_img_etc: {
      test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
      use: [{ loader: 'url-loader', options: { limit: 8192 } }],
      include: SRC('assets', 'map-img')
    },
    svg: {
      test: /\.svg$/,
      include: SRC('assets', 'svg-react'),
      use: [ "babel-loader",
        {
          loader: "react-svg-loader",
          options: {
            svgo: {
              plugins: [
                { removeTitle: false }
              ],
              floatPrecision: 2
            }
          }
        }
      ]
    }
  },
  get: function(name) {
    return this._items_[name];
  },
  set: function(name, rule) {
    const raw = this.get(name) || {};
    if(!rule)
      delete this._items_[name];
    else
      this._items_[name] = { ...raw, ...rule };
  },
  toArray() {
    return Object.keys(this._items_).map(i => this._items_[i]);
  }
};

const externals = ['antd']

// WEBPACK COMMONS CONFIGURATIONS

const commons = {
  entry: ['@babel/runtime-corejs2', _index],
  output: {
    path: _dist,
    filename: '[name].js'
  },
  devtool: 'cheap-module-eval-source-map',
  resolve: {
    alias,
    symlinks: false,
    modules: [_src, 'node_modules'],
    extensions: ['*', '.js', '.json']
  },
  module: {
    rules: rules.toArray()
  },
  plugins: [ ],
  stats: {
    colors: true,
    entrypoints: false
  },
  externals
};

const CONFIG = (options) => options ? Object.keys(options).reduce((commons, name) => {
  const common = commons[name];
  const custom = options[name];

  if(typeof custom === 'function') {
    commons[name] = custom(common);
    return commons;
  }

  if(custom) {
    commons[name] = custom;
    return commons;
  }

  return commons;
}, commons) : commons

module.exports = {
  CONFIG,
  _root, _src, _index, _dist, _mocks,
  rules
};

mock-service

// For better Performance and Compatibility, write code with ES5, Please!
// /abc/dkas/kdla
/* eslint-disable */

const MOCK_SERVICE_BASE = new URL(location).searchParams.get('mock-service-base');

const version = "great-adventure" + ' T_' + (Number(Date.now()).toString(36));
const prefetch = {
	name: version + ' - prefetch',
	list: []
};
const cachePatterns = null;

const useMocksServer = true;
const mockRequestPatterns = [];

const fetchOptions = {};
const REG_REFERRER = /^http(s)?:\/\/[^\/]+\//;
const mockFetchOptions  = {
  mode: 'no-cors',
  headers: {
    "Accept": "mock-server/data, */*"
  }
};

self.addEventListener('install', function(evt) {
	if (typeof self.skipWaiting === 'function') {
		evt.waitUntil(self.skipWaiting());
	}
	evt.waitUntil(precache(prefetch.name, prefetch.list));
});

self.addEventListener('activate', function(evt) {
	if(self.clients && (typeof self.clients.claim === 'function')) {
		evt.waitUntil(self.clients.claim());
	}
	evt.waitUntil(updateCache([prefetch.name]));
});

self.addEventListener('fetch', function(evt) {
	if(useMocksServer && matchMockRequests(evt.request)) {
		evt.respondWith(fromMockService(evt.request));
	}
});

function precache() {
	return caches.open(prefetch.name).then(function(cache) {
		return cache.addAll(prefetch.list);
	});
}

function updateCache(profileNames) {
	return caches.keys().then((cacheNames) => {
		return Promise.all(cacheNames.map(cahceName => {
			if(profileNames.indexOf(cahceName) === -1) {
				return caches.delete(cahceName);
			}
		}));
	});
}

function retrieves(request) {
  if(useMocksServer && matchMockRequests(request)) {
    return fromMockService(request);
	}
	return fromCache(request).then(matching => matching).catch(() => fromNetwork(request));
}

function fromCache(request) {
	return caches.open(prefetch.name).then(cache => {
    return cache.match(request.clone()).then(function (matching) {
      return matching || Promise.reject('no-match');
    });
	});
}

function fromNetwork(request) {
	return fetch(request.clone(), fetchOptions).then(response => {
		if(response.ok) {
			cacheFetch(request.clone(), response.clone());
		}
		return response || Promise.reject('network-error');
	});
}

function cacheFetch(request, response) {
	if(matchFile(request.url)) {
		caches.open(prefetch.name).then(function(cache) {
			return cache.put(request, response);
		})
	}
}

function matchFile(url) {
	return cachePatterns && cachePatterns.some(p => p.test(url));
}

function matchRacers(url) {
	return racerPatterns && racerPatterns.some(p => p.test(url));
}

function matchMockRequests(request) {

	const url = request.url;
  const headers = request.headers;

  return headers.get('use-mock-service') === 'true' ? true : false;
}

function fromMockService(request) {

	const url    = request.url.split(request.referrer);
	const method = request.method.toLowerCase();
	const type   = '.json';

	if(url.length !== 2) {
		return Promise.reject('mock-service-error');
	}

	const origin = url.pop();
	const routes = origin.split('/');
	const max_id = routes.length - 1;

	let target = '/';
	routes.some((r, i) => i === max_id ? (target += `${method}.${r}`, target) : (target += `${r}/`, false));

  let resource = (target + type).replace(/\?/g, '.').replace(/&/g, '.').replace(/=[\w%\d]+/g, '');

  console.warn(`\t[USE-MOCK-SERVICE]\t
  \t[${method.toUpperCase()}] /${decodeURIComponent(origin)}
  \t[${'>'.repeat(method.length)}] ${decodeURIComponent(resource)}`);

  resource = MOCK_SERVICE_BASE === 'undefined' ? resource : MOCK_SERVICE_BASE + resource;

	return fetch(new URL(resource, request.referrer), mockFetchOptions).then(response => response || Promise.reject('mock-service-error'));
}

mobx 对当前store作为特例说明

import { observable, action, runInAction} from 'mobx'
import { fetchMapStyle } from '@services/people.js'
class PersonStore {

  @observable
  personInfo={
                fuullName:'',
                name:'',
                tel:'',
                percent:''
           };
  
  @observable.ref mapStyleJSON = null;
  constructor() {
    this.fetchMapJSON();
    const infoData = sessionStorage.getItem('infoData');
    if (infoData) {
      this.personInfo = JSON.parse(infoData)
    } else {
      this.personInfo={
        fuullName:'',
        name:'',
        tel:'',
        percent:''
      };
    }
  }
  @action getPersonInfo(data){
    this.personInfo.name=data.name
    this.personInfo.tel=data.tel
    this.personInfo.percent=data.percent
    this.personInfo.fuullName=data.fuullName
    sessionStorage.setItem('infoData',JSON.stringify(data))
  }
  @action
  async fetchMapJSON(){
    this.mapStyleJSON = {};
    try{
      const mapJSON = await fetchMapStyle();
      runInAction(
        () => {
          this.mapStyleJSON = mapJSON;
        }
      )
    } catch (err) {
      console.log(err)
    }
  }
}

const store = new PersonStore();

export default store;

mobx使用可以对照redux或者vux

  @observable   //监视器
  构建监听的实体属性;或者数据对象;类似于前两者的store;
  constructor //构造器   对@observable 的深度处理
   @action   //对对@observable计算处理  
   这里要特别注意的是@observable 会对当前构造对象的每一个实体都进行监听;但和react及vue一样它在页面刷新时也会丢失数据所以可以结合sessionStorage持久化处理;
   @observable监听的对象常常是默认深度监听;举个例子:fetchMapJSON()是我们通过服务要获取的一个地图实体对象data{.....};如何使用传统的定义方式;那么拿到的返回值会被监听后解析成poxy:{{h},{d},{d}},对对象的每一个实体都会监听从而破话我们真正想要的数据格式;故在定义的时候要使用:  

> @observable.ref mapStyleJSON = null;  改变其监听的模式;

import { HGroup, VGroup } from 'v-block.lite/layout'   这里是采用fix布局会使用的高效的ui布局插件
HGroup  //水平
VGroup  //垂直     所有的页面均可由这两个标签结合简单的基础标签完成 摆脱传动布局繁重的css及dom编写

react 路由

import './app.less'
import React from 'react'
import { HashRouter as Router, Switch, Route} from 'react-router-dom'
import { HGroup, VGroup } from 'v-block.lite/layout'
import Statics from './statics'
import Details from './details';
import MapBox from './mapbox';
import HeaderView from '../modules/header'
import Find from '../modules/popFind'
import Control from '../modules/popControl'
import Person from '../modules/person'
import Result from '../modules/result'
const Header = ({ title }) => (
  <HGroup className="header" verticalAlign="center" padding="5px 15px">
    <HeaderView></HeaderView>
  </HGroup>
);

const Main = ({ children }) => (
  <HGroup className="main" verticalAlign="stretch" gap={5} flex style={{height:"calc(100% - 90px)",overflow:"hidden"}}>
    <Statics/>
    <MapBox/>
    <Details/>
  </HGroup>
);
const App = () => (
  <Router>
    <VGroup height="100%" horizontalAlign="stretch" gap={5}>
      <Header/>
      <Switch>
        <Route path="/"  component={Main} exact/>
        <Route path="/find" component={Find}/>
        <Route path="/control" component={Control}/>
        <Route path="/person" component={Person}/>
        <Route path="/result/:type" component={Result}/>
      </Switch>
    </VGroup>
  </Router>
);
export default App

mapbox 高性能地图 这里使用的是模块化的init方式,其实还可以使用reactmapbox因为个人习惯里这种方式故而才用的是init方式

import React, { Component } from "react";
import { HGroup,} from 'v-block.lite/layout'
import mapBoxGl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css';
export default class MapContent extends Component{
  constructor(props){
    super(props)
    this.state={
      modalInfo:props.stateData,
      linData:[],
    }
  }

  changeMyMapData=(data,line)=>{
    let  linData=[];
    const datas=this.props.MapStyleData
    const domId=this.props.domId
    this.setState({modalInfo:data},()=>{
    })
    this.setState({linData:line}, ()=> {
    })
    this.initMap(domId,datas,data,linData);
  }
  changeLineData=()=>{
    const linData=this.state.linData;
    const data=this.props.MapStyleData
    const domId=this.props.domId
    const stateData=this.state.modalInfo
    this.initMap(domId,data,stateData,linData);

  }
  componentDidMount() {
    const linData=this.state.linData;
    const data=this.props.MapStyleData
    const domId=this.props.domId
    const stateData=this.state.modalInfo
    this.initMap(domId,data,stateData,linData);
  }

  initMap(domId,data,stateData,linData){

    const map = new mapBoxGl.Map({
      container: document.getElementById(domId),
      style: data,
      zoom: 13.5,
      center:stateData.centers
    });
    mapBoxGl.accessToken = "";
    if(linData.length==0){
      map.on("load", function() {
        // ["accommodation","bus-station","finance","government","hospital","house","life-service","oil-station","railway-station","school","travel"].map(v => {
        //
        //   if(poiStyle2[v]) map.addLayer(poiStyle2[v])
        // })
      });
    }else {
      map.on("load", function() {
        // ["accommodation","bus-station","finance","government","hospital","house","life-service","oil-station","railway-station","school","travel"].map(v => {
        //
        //   if(poiStyle2[v]) map.addLayer(poiStyle2[v])
        // })
        var coordinates=linData.features[0].geometry.coordinates;
        linData.features[0].geometry.coordinates=[coordinates[0]]
        map.addSource('trace-source',{
          "type":"geojson",
          "data":linData
        })
        map.addLayer({
          "id":"trace",
          "type":"line",
          "source":"trace-source",
          "paint":{
            "line-color":"#e5ab3c",
            "line-opacity":1,
            "line-width":2
          }
        })
        map.jumpTo({'center':coordinates[0],'zoom':13})
        map.setPitch(30)
        var i=0
        var timer=window.setInterval(function () {
          if(i<coordinates.length){
            linData.features[0].geometry.coordinates.push(coordinates[i]);
            map.getSource('trace-source').setData(linData)
            map.panTo(coordinates[i]);
            i++;
          }else {
            window.clearInterval(timer)
          }
        },1000)
      });
    }
    //add markers to map
    stateData.features.forEach(function(marker) {
    var popup= new mapBoxGl.Popup({
        closeButton:false,
        closeClick:false,
        offset:25
      })
// create a DOM element for the marker
      var partBox=document.createElement('div');
      partBox.style.display='flex';
      partBox.style.flexDirection='column';
      partBox.style.justifyContent='center';
      partBox.style.alignItems="center"
      var pEl=document.createElement('p');
      var el = document.createElement('div');
      el.className = 'marker';
      el.style.backgroundImage = `url(${require("../../assets/images/"+marker.properties.icon)})`;
      el.style.backgroundSize='100% 100%'
      el.style.width = marker.properties.iconSize[0] + 'px';
      el.style.height = marker.properties.iconSize[1] + 'px';
      pEl.innerHTML=marker.properties.description
      pEl.style.marginTop="5px";
      pEl.style.padding="2px 5px";
      pEl.style.backgroundColor="#015c97"
      pEl.style.borderRadius='5px'
      pEl.style.textAlign='center'
      partBox.appendChild(el)
      partBox.appendChild(pEl)
      partBox.addEventListener('mouseenter',(e) => {
        e.stopPropagation()
        var infoBox=document.createElement('div');
        var el1=document.createElement('p');
        var el2 = document.createElement('p');
        var el3=document.createElement('p');
        el1.innerHTML='开始时间:'+marker.properties.message.startTime;
        el2.innerHTML='结束时间:'+marker.properties.message.endTime
        el3.innerHTML='地址:'+marker.properties.message.address
        infoBox.style.padding='15px 10px';
        infoBox.style.backgroundColor='#033d66';
        infoBox.style.opacity='0.5';
        infoBox.appendChild(el1);
        infoBox.appendChild(el2);
        infoBox.appendChild(el3);
        popup.setLngLat(marker.geometry.coordinates)
        popup.setDOMContent(infoBox)
        popup.addTo(map);
      })
      partBox.addEventListener('mouseleave',(e)=>{
        e.stopPropagation()
        popup.remove()
      })
      new mapBoxGl.Marker(partBox)
        .setLngLat(marker.geometry.coordinates)
        .addTo(map);

    });
  }
  render(){
    return(
      <div>
        <HGroup className="btns"><span style={{display:'block',padding:'2px 5px',color:'#4eddc0',borderRadius:'3px',background:'#034e83'}}onClick={()=>this.changeLineData()}>轨迹回放</span></HGroup>
      </div>)
  }
}

这里是对地图的渲染,及数据跟新路径操作,mark标记操作,轨迹回放操作
注意:

    container: document.getElementById(domId),  //实例地图的容器
      style: data,   //核心这是是加载地图的样式数据可在官网查看具体也可以查看src/services/mapBoxSyle/ 该目录下的jinan.json;使用这个数据也可以渲染
      zoom: 13.5,   //初始化时的缩放
      center:stateData.centers  //地图的中心坐标
具体其他参数可以自行度娘 mapbox

该项目为模型类型无后台支撑纯前端构成;数据采用按需模块定制手动加载;后期改后台服务只需改掉service下请求方式

 emotionalState(type){
    let baseUrl="./msgciyun/"
    return   require(baseUrl+type+'-msg_state.json')
  },
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值