├── .gitignore ├── README.md ├── config-overrides.js ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.js ├── App.test.js ├── components │ ├── ChartSettingBoard.jsx │ ├── ConfigDropBox.jsx │ ├── DragElement.jsx │ ├── DropConfigChart.jsx │ ├── DropElement.jsx │ ├── chartSettingBoard.css │ ├── configDropBox.css │ ├── dragElement.css │ ├── dropElement.css │ └── echartConfig.js ├── img │ └── view.gif ├── index.css ├── index.js └── registerServiceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.history 6 | 7 | 8 | # misc 9 | .DS_Store 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | .mp4 15 | 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Quick-eChart 2 | react快速配置echart图形 3 | 4 | 写了一个简单的可实现通过配置文件,对不同数据形态直接拖拽生成echart图表; 5 | 6 | ![img](https://github.com/xingxiaoyiyio/React-Quick-eChart/blob/master/src/img/view.gif) 7 | 8 | # 说明 9 | 10 | 引用的插件 11 | 1、react-dnd   拖拽 12 | 2、react-color   颜色选择 13 | 3、ant           布局框架(非必须) 14 | 4、echart        图标插件 15 | 16 | # 运行 17 | npm install  安装 18 | npm start    运行本地环境 19 | ps:组件没有做封装,代码有些乱,仅供参考 20 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const { injectBabelPlugin } = require('react-app-rewired'); 2 | 3 | module.exports = function override(config, env) { 4 | // do stuff with the webpack config... 5 | config = injectBabelPlugin( 6 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }], 7 | config, 8 | ); 9 | return config; 10 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antd-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "antd": "^3.9.1", 7 | "babel-plugin-import": "^1.8.0", 8 | "echarts": "^4.1.0", 9 | "echarts-for-react": "^2.0.14", 10 | "immutability-helper": "^2.7.1", 11 | "react": "^16.4.2", 12 | "react-app-rewired": "^1.6.2", 13 | "react-color": "^2.14.1", 14 | "react-dnd": "^5.0.0", 15 | "react-dnd-html5-backend": "^5.0.1", 16 | "react-dom": "^16.4.2", 17 | "react-scripts": "1.1.5" 18 | }, 19 | "scripts": { 20 | "start": "react-app-rewired start", 21 | "build": "react-app-rewired build", 22 | "test": "react-app-rewired test --env=jsdom", 23 | "eject": "react-scripts eject" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingxiaoyiyio/React-Quick-eChart/00685043651498750e95583da3e11364bd8134cb/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component} from 'react'; 2 | import ChartSettingBoard from './components/ChartSettingBoard' 3 | 4 | class App extends Component { 5 | render() { 6 | return (
7 | 8 |
9 | ); 10 | } 11 | } 12 | 13 | export default App; -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/ChartSettingBoard.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Row, Col, Select, Icon } from 'antd' 3 | import { DragDropContext } from 'react-dnd'; 4 | import HTML5Backend from 'react-dnd-html5-backend'; 5 | import DropConfigChart from './DropConfigChart'; 6 | import DropElement from './DropElement'; 7 | import update from 'immutability-helper'; 8 | import echartConfig from './echartConfig'; 9 | import './chartSettingBoard.css' 10 | 11 | import ConfigDropBox from './ConfigDropBox' 12 | import DragElement from './DragElement' 13 | 14 | const Option = Select.Option; 15 | const dragItem = 'item'; 16 | const colorSet = ['#9CC5B0', '#C9856B', '#6F9FA7', '#334553', '#B34038', '#7D9D85', '#C1883A'] 17 | 18 | const lineData = [ 19 | { name: '年销量', type: 'string', value: 'year', id: 0, data: ['2013', '2014', '2015', '2016', '2017', '2018'], color: '#9CC5B0', chart: 'line' }, 20 | { name: '华北', type: 'value', value: 'h', id: 1, data: [40, 80, 20, 120, 140, 50], color: '#C9856B', chart: 'line' }, 21 | { name: '华东', type: 'value', value: 'd', id: 2, data: [140, 180, 120, 40, 50, 150], color: '#6F9FA7', chart: 'line' }, 22 | { name: '华南', type: 'value', value: 'n', id: 3, data: [110, 143, 68, 90, 120, 130], color: '#334553', chart: 'line' } 23 | ]; 24 | const pieData = [ 25 | { value: 335, name: '京东', type: 'value', id: 0, color: '#9CC5B0' }, 26 | { value: 310, name: '菜鸟', type: 'value', id: 1, color: '#C9856B' }, 27 | { value: 234, name: '总部', type: 'value', id: 2, color: '#6F9FA7' }, 28 | { value: 135, name: '小电商', type: 'value', id: 3, color: '#334553' }, 29 | { value: 1548, name: '大电商', type: 'value', id: 4, color: '#B34038' } 30 | ] 31 | const chartType = [ 32 | { value: 'line', name: '折线图' }, 33 | { value: 'bar', name: '柱状图' }, 34 | { value: 'pie', name: '饼图' } 35 | ] 36 | 37 | export class ChartSettingBoard extends Component { 38 | 39 | constructor(props) { 40 | super(props); 41 | this.dragEleMove = this.dragEleMove.bind(this); 42 | this.beginDrag = this.beginDrag.bind(this) 43 | this.canDrop = this.canDrop.bind(this) 44 | this.endDrag = this.endDrag.bind(this) 45 | this.delItem = this.delItem.bind(this) 46 | this.changeItem = this.changeItem.bind(this) 47 | this.onSelectChartType = this.onSelectChartType.bind(this) 48 | } 49 | 50 | state = { 51 | activeId: '', 52 | activeDropId: '', 53 | itemList: lineData, 54 | chartType: 'line', 55 | dropConfig: echartConfig['line'] 56 | } 57 | 58 | dragEleMove(id) { 59 | this.setState({ activeDropId: id }) 60 | } 61 | 62 | beginDrag(id) { 63 | this.setState({ activeId: id }) 64 | } 65 | 66 | canDrop(id) { 67 | const { itemList, activeId, dropConfig } = this.state; 68 | if (itemList[activeId].type != dropConfig[id].type) { 69 | return false; 70 | } 71 | return true 72 | } 73 | 74 | endDrag() { 75 | const { itemList, activeId, dropConfig, activeDropId } = this.state; 76 | const ilist = update(itemList, { $splice: [[activeId, 1]] }) 77 | const dlist = update(dropConfig, { [activeDropId]: { items: { $push: [itemList[activeId]] } } }) 78 | this.setState({ itemList: ilist, dropConfig: dlist }) 79 | } 80 | 81 | delItem(item, pitem, pid) { 82 | const { itemList, dropConfig } = this.state; 83 | for (let i = 0; i < pitem.items.length; i++) { 84 | if (pitem.items[i].id === item.id) { 85 | pitem.items.splice(i, 1); 86 | break; 87 | } 88 | } 89 | const nlist = update(itemList, { $push: [item] }) 90 | const dropList = update(dropConfig, { [pid]: { $set: pitem } }) 91 | this.setState({ itemList: nlist, dropConfig: dropList }) 92 | } 93 | 94 | onSelectChartType(type) { 95 | if (type === 'pie') { 96 | this.setState({ itemList: pieData, dropConfig: echartConfig[type], chartType: type }) 97 | } else { 98 | const nlist = [...lineData]; 99 | for (let i = 0; i < nlist.length; i++) { 100 | nlist[i].chart = type; 101 | } 102 | this.setState({ itemList: nlist, dropConfig: echartConfig[type], chartType: type }) 103 | } 104 | } 105 | 106 | changeItem(value, key, id, pid) { 107 | const { dropConfig } = this.state; 108 | const nitem = { ...dropConfig[pid].items[id] } 109 | nitem[key] = value; 110 | const dropList = update(dropConfig, { [pid]: { items: { [id]: { $set: nitem } } } }) 111 | this.setState({ dropConfig: dropList }) 112 | } 113 | 114 | render() { 115 | const { itemList, dropConfig } = this.state 116 | const leftItems = itemList.map((item, idx) => { 117 | return ( 118 |
119 | 120 |
121 | ) 122 | }) 123 | 124 | const dropList = dropConfig.map((item, idx) => { 125 | const items = item.items.map((sitem, sid) => { 126 | return ( 127 |
128 | this.changeItem(value, key, sid, idx)} /> 129 |
130 | ) 131 | }) 132 | 133 | return ( 134 | 3 ? 'shortBox' : 'longBox'}> 135 | 136 | {items} 137 | 138 | 139 | ) 140 | }) 141 | return ( 142 |
143 |

可视化

144 | 145 | 146 |
147 | 165 |
166 |
167 | {leftItems} 168 |
169 | 170 | 171 | 172 | {dropList} 173 | 174 | 175 |
176 | 177 |
178 | ) 179 | } 180 | } 181 | 182 | 183 | export default DragDropContext(HTML5Backend)(ChartSettingBoard); -------------------------------------------------------------------------------- /src/components/ConfigDropBox.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { DropTarget } from 'react-dnd'; 3 | import { Icon, Popover } from 'antd' 4 | import './configDropBox.css' 5 | 6 | const ItemTypes = 'item' 7 | const itarget = { 8 | canDrop(props) { 9 | return props.canDrop(props.id); 10 | }, 11 | drop(props) { 12 | props.move(props.id) 13 | } 14 | }; 15 | 16 | function icollect(connect, monitor) { 17 | return { 18 | connectDropTarget: connect.dropTarget(), 19 | isOver: monitor.isOver(), 20 | canDrop: monitor.canDrop() 21 | }; 22 | } 23 | 24 | export class ConfigBox extends Component { 25 | 26 | render() { 27 | const { connectDropTarget, isOver, item, canDrop } = this.props; 28 | return connectDropTarget( 29 |
30 |
{item.title} 31 | 32 | {item.dec}
} trigger="hover" placement="bottom" > 33 | 34 | 35 | 36 |
37 |
38 | {this.props.children} 39 | {isOver && canDrop ? 40 |
: null 41 | } 42 | {isOver && !canDrop ? 43 | < div className='dropOver noDrop' /> : null 44 | } 45 |
46 |
47 | ) 48 | } 49 | } 50 | 51 | const ConfigDropBox = DropTarget(ItemTypes, itarget, icollect)(ConfigBox) 52 | 53 | export default ConfigDropBox 54 | -------------------------------------------------------------------------------- /src/components/DragElement.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './dragElement.css' 3 | import { Button } from 'antd' 4 | import { DragSource } from 'react-dnd'; 5 | 6 | const ItemTypes = 'item'; 7 | 8 | const ItemSource = { 9 | beginDrag(props, monitor, component) { 10 | props.beginDrag(props.id) 11 | return {}; 12 | }, 13 | 14 | endDrag(props, monitor, component) { 15 | if (!monitor.didDrop()) { 16 | return; 17 | } 18 | props.endDrag() 19 | } 20 | }; 21 | 22 | function collect(connect, monitor) { 23 | return { 24 | connectDragSource: connect.dragSource(), 25 | isDragging: monitor.isDragging() 26 | } 27 | } 28 | 29 | export class DragElement extends Component { 30 | 31 | render() { 32 | const { connectDragSource, isDragging, item } = this.props; 33 | return connectDragSource( 34 |
39 | {item.name} 40 | {item.type} 41 |
42 | ) 43 | } 44 | } 45 | 46 | const DragItem = DragSource(ItemTypes, ItemSource, collect)(DragElement); 47 | 48 | export default DragItem 49 | -------------------------------------------------------------------------------- /src/components/DropConfigChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactEcharts from 'echarts-for-react'; 3 | 4 | export class DropConfigChart extends Component { 5 | 6 | getOption() { 7 | const { dropConfig, chartType } = this.props; 8 | let legendData = []; 9 | const xTarget = dropConfig.filter((item) => item.key === 'xAxis')[0].items[0]; 10 | const xData = xTarget ? dropConfig.filter((item) => item.key === 'xAxis')[0].items[0].data : []; 11 | const yData = dropConfig.filter((item) => item.key === 'yAxis')[0].items.map((ist) => { 12 | legendData.push(ist.name || '') 13 | const vdata = { type: ist.chart, color: ist.color, data: ist.data, name: ist.name }; 14 | return vdata 15 | } 16 | ) 17 | 18 | const option = { 19 | xAxis: { 20 | type: 'category', 21 | data: xData 22 | }, 23 | grid: { 24 | left: '3%', 25 | right: '4%', 26 | bottom: '3%', 27 | containLabel: true 28 | }, 29 | yAxis: { 30 | type: 'value' 31 | }, 32 | legend: { 33 | x: 'right', 34 | data: legendData 35 | }, 36 | series: yData 37 | }; 38 | return option 39 | } 40 | 41 | getPieOption() { 42 | const dropConfig = this.props.dropConfig; 43 | let legendData = []; 44 | const target = dropConfig.filter((item) => item.key === 'series')[0]; 45 | const seriesData = target ? target.items.map((ist) => { 46 | legendData.push(ist.name || '') 47 | const vdata = { value: ist.value, name: ist.name }; 48 | return vdata 49 | } 50 | ) : []; 51 | const option = { 52 | tooltip: { 53 | trigger: 'item', 54 | formatter: "{b}: {c} ({d}%)" 55 | }, 56 | legend: { 57 | x: 'right', 58 | data: legendData 59 | }, 60 | series: [ 61 | { 62 | type: 'pie', 63 | data: seriesData 64 | } 65 | ] 66 | }; 67 | 68 | return option; 69 | } 70 | 71 | render() { 72 | const { chartType } = this.props 73 | return ( 74 |
75 | {chartType === 'line' || chartType === 'bar' ? < ReactEcharts option={this.getOption()} notMerge={true} lazyUpdate={true} /> : ''} 76 | {chartType === 'pie' ? : ''} 77 |
78 | ) 79 | } 80 | } 81 | 82 | export default DropConfigChart 83 | -------------------------------------------------------------------------------- /src/components/DropElement.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Icon, Row, Col, Select, Popover } from 'antd' 3 | import { SketchPicker } from 'react-color'; 4 | import './dropElement.css' 5 | 6 | const Option = Select.Option; 7 | 8 | const XItem = ({ item, delItem, pitem, pid }) => { 9 | return ( 10 |
11 | {item.name} 12 | delItem(item, pitem, pid)} /> 13 | {item.type} 14 |
15 | ) 16 | } 17 | 18 | const YItem = ({ item, delItem, pitem, pid, changeItem }) => { 19 | const content = ( changeItem(color.hex, 'color')} />) 20 | return ( 21 |
22 | 23 | {item.name} 24 | 25 | 35 | 36 | 37 | 38 | 颜色 39 | 40 | delItem(item, pitem, pid)} /> 41 | 42 | 43 |
44 | ) 45 | } 46 | 47 | export class DropElement extends Component { 48 | 49 | render() { 50 | const dropItem = this.props.pitem.dropItem; 51 | return ( 52 |
53 | {dropItem === 1 ? : null} 54 | {dropItem === 2 ? : null} 55 |
56 | ) 57 | } 58 | } 59 | 60 | export default DropElement 61 | -------------------------------------------------------------------------------- /src/components/chartSettingBoard.css: -------------------------------------------------------------------------------- 1 | .chartSettingBoard { 2 | margin: 30px; 3 | } 4 | .chartSettingBoard .leftBox { 5 | width: 100%; 6 | height: 230px; 7 | background-color: #fafafa; 8 | border-radius: 4px; 9 | margin-top: 10px; 10 | padding: 10px; 11 | border: solid 1px #f0f0f0; 12 | } 13 | .chartSettingBoard .icon { 14 | float: right; 15 | padding: 0 5px; 16 | cursor: pointer; 17 | line-height: 32px 18 | } 19 | 20 | .chartSettingBoard .chartTypeSelect .ant-select-selection--single { 21 | height: 50px; 22 | } 23 | .chartSettingBoard .chartTypeSelect .charIconBox { 24 | position: absolute; 25 | width: 30px; 26 | height: 30px; 27 | top: 10px; 28 | left: 0; 29 | display: block !important; 30 | font-size: 20px; 31 | color: #fff; 32 | border-radius: 3px; 33 | padding: 3px 5px; 34 | background: #0099cc 35 | } 36 | .chartSettingBoard .chartTypeSelect .ant-select-selection__rendered { 37 | line-height: 23px; 38 | padding-left: 40px 39 | } 40 | .chartSettingBoard .chartTypeSelect p { 41 | margin: 0; 42 | display: block !important 43 | } 44 | 45 | .chartSettingBoard .shortBox .configDropBox { 46 | height: 140px; 47 | margin-bottom: 10px; 48 | } 49 | -------------------------------------------------------------------------------- /src/components/configDropBox.css: -------------------------------------------------------------------------------- 1 | .configDropBox { 2 | height: 290px; 3 | width: 100%; 4 | position: relative; 5 | background-color: #fafafa; 6 | border-radius: 4px; 7 | border: solid 1px #f0f0f0; 8 | overflow: auto; 9 | } 10 | .configDropBox .topTitle { 11 | height: 32px; 12 | line-height: 32px; 13 | padding: 0 10px; 14 | background: #f6f6f6 15 | } 16 | .configDropBox .dropBox { 17 | width: 100%; 18 | padding: 10px; 19 | background-color: #fafafa; 20 | } 21 | .configDropBox .dropOver { 22 | position: absolute; 23 | top: 0; 24 | left: 0; 25 | height: 100%; 26 | width: 100%; 27 | z-index: 1; 28 | opacity: 0.5; 29 | background: yellow 30 | } 31 | .configDropBox .dropOver.noDrop { 32 | background: red 33 | } 34 | -------------------------------------------------------------------------------- /src/components/dragElement.css: -------------------------------------------------------------------------------- 1 | 2 | .dragElement { 3 | margin-top: 5px; 4 | width: 100%; 5 | padding: 0 8px; 6 | line-height: 32px; 7 | cursor: move; 8 | height: 32px; 9 | background-color: #e6fdff; 10 | border-radius: 4px; 11 | border: solid 1px #0099cc; 12 | } 13 | .dragElement .type { 14 | color: #0099cc; 15 | float: right 16 | } 17 | -------------------------------------------------------------------------------- /src/components/dropElement.css: -------------------------------------------------------------------------------- 1 | 2 | .dropElement .xItem { 3 | margin-top: 5px; 4 | overflow: hidden; 5 | width: 100%; 6 | padding: 0 8px; 7 | line-height: 32px; 8 | height: 32px; 9 | background-color: #fff; 10 | border-radius: 4px; 11 | border: solid 1px #0099cc; 12 | } 13 | .dropElement .yItem { 14 | margin-top: 5px; 15 | overflow: hidden; 16 | width: 100%; 17 | border: 1px solid #fff; 18 | padding-left: 5px; 19 | line-height: 32px; 20 | height: 32px; 21 | background-color: #fff; 22 | border-radius: 4px;} 23 | 24 | .dropElement .yItem .icon { 25 | padding-left: 0 26 | } 27 | 28 | .dropElement .typeSelect { 29 | width: 100%;} 30 | 31 | .dropElement .typeSelect .ant-select-arrow { 32 | right: 5px 33 | } 34 | 35 | .dropElement .ant-select-selection__rendered { 36 | margin-left: 5px; 37 | margin-right: 5px 38 | } 39 | .dropElement .ant-select-selection { 40 | border: none 41 | } 42 | .dropElement .anticon-close { 43 | margin-top: 8px; 44 | padding: 0 45 | } 46 | .dropElement .ant-select-selection-selected-value { 47 | padding: 0 48 | } 49 | .dropElement .ant-select-focused :focus { 50 | box-shadow: none !important; 51 | border: none 52 | } 53 | .dropElement .type { 54 | float: right; 55 | } 56 | .dropElement .colorLabel { 57 | position: absolute; 58 | display: block; 59 | width: 12px; 60 | left: 5px; 61 | height: 12px; 62 | top: 2px; 63 | } 64 | -------------------------------------------------------------------------------- /src/components/echartConfig.js: -------------------------------------------------------------------------------- 1 | const echartConfig = { 2 | line: [{ 3 | title: '值轴/X', 4 | key: 'xAxis', 5 | dropItem: 1, 6 | type: 'string', 7 | length: 1, 8 | dec: 'x轴,数据类型为字符串!', 9 | items: [] 10 | }, 11 | { 12 | title: '值轴/Y', 13 | key: 'yAxis', 14 | dropItem: 2, 15 | type: 'value', 16 | length: 10, 17 | dec: 'y轴,数据类型为数字!', 18 | chartSelectOpt: [{ 19 | name: '条形图', 20 | value: 'line' 21 | }, { 22 | name: '柱状图', 23 | value: 'bar' 24 | }], 25 | items: [] 26 | } 27 | ], 28 | bar: [{ 29 | title: '值轴/X', 30 | key: 'xAxis', 31 | dropItem: 1, 32 | dec: 'x轴,数据类型为字符串!', 33 | type: 'string', 34 | length: 1, 35 | items: [] 36 | }, 37 | { 38 | title: '值轴/Y', 39 | key: 'yAxis', 40 | dropItem: 2, 41 | type: 'value', 42 | dec: 'y轴,数据类型为字符串!', 43 | chartSelectOpt: [{ 44 | name: '条形图', 45 | value: 'line' 46 | }, { 47 | name: '柱状图', 48 | value: 'bar' 49 | }], 50 | length: 10, 51 | items: [] 52 | } 53 | ], 54 | pie: [{ 55 | title: '数据项', 56 | key: 'series', 57 | dropItem: 1, 58 | dec: '数据类型为数字!', 59 | type: 'value', 60 | length: 10, 61 | items: [] 62 | }] 63 | } 64 | 65 | export default echartConfig; -------------------------------------------------------------------------------- /src/img/view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingxiaoyiyio/React-Quick-eChart/00685043651498750e95583da3e11364bd8134cb/src/img/view.gif -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | --------------------------------------------------------------------------------