├── README.md ├── gulpfile.js ├── images └── ex1.png ├── mock ├── modules │ ├── index.js │ ├── login │ │ ├── login.js │ │ └── users.json │ └── util │ │ └── UUID.js └── server.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── App.js ├── Store.js ├── assets ├── css │ └── common.css ├── i18n │ ├── en.json │ ├── parts │ │ ├── en.json │ │ ├── en_login.json │ │ ├── en_overview.json │ │ ├── en_topo.json │ │ ├── zh.json │ │ ├── zh_login.json │ │ ├── zh_overview.json │ │ └── zh_topo.json │ └── zh.json └── images │ └── logo.svg ├── components ├── header │ ├── header.module.css │ ├── index.js │ └── view.js ├── layout │ ├── index.js │ └── view.js ├── loader │ ├── index.js │ ├── loader.module.css │ └── view.js ├── loading │ ├── actionTypes.js │ ├── actions.js │ ├── index.js │ ├── loading.module.css │ ├── reducer.js │ └── view.js └── sidebar │ ├── actionTypes.js │ ├── actions.js │ ├── data.json │ ├── index.js │ ├── reducer.js │ ├── sidebar.module.css │ └── view.js ├── enhancer └── reset.js ├── index.js ├── login ├── actions.js ├── index.js ├── login.module.css └── view.js ├── pages ├── home.module.css ├── index.js ├── overview │ ├── dashboard.module.css │ ├── index.js │ └── view.js ├── topo │ ├── index.js │ └── view.js └── view.js ├── plugins ├── bar-chart │ └── view.js ├── index.js ├── line-chart │ └── view.js └── pie-chart │ └── view.js ├── serviceWorker.js └── util ├── fetch.js └── localstorage.js /README.md: -------------------------------------------------------------------------------- 1 | ## React Dev Dnd 2 | A demo to develop the UI page just by drag & drop plugins。 3 | ## Installation 4 | ``` 5 | git clone https://github.com/sunnut/react-dev-dnd.git 6 | cd react-dev-dnd 7 | npm install 8 | npm run mock 9 | npm start 10 | ``` 11 | ## Examples 12 | #### demo 13 | ![Screenshot](https://github.com/sunnut/react-dev-dnd/blob/master/images/ex1.png?raw=true "demo") -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var runSequence =require('run-sequence'); 3 | var rimraf = require('gulp-rimraf'); 4 | var merge = require('gulp-merge-json'); 5 | 6 | //监听词条文件变更 7 | gulp.task('i18n-json-merge-watch',function(){ 8 | gulp.watch('./src/assets/i18n/parts/*.json',function () { 9 | runSequence( 10 | 'i18n-json-merge' 11 | ) 12 | }); 13 | }); 14 | 15 | //合并词条 16 | gulp.task('i18n-json-merge',function(){ 17 | runSequence( 18 | 'i18n-json-clean', 19 | 'i18n-zh-json-merge', 20 | 'i18n-en-json-merge' 21 | ); 22 | }); 23 | 24 | gulp.task('i18n-json-clean', function() { 25 | return gulp.src('./src/assets/i18n/*.json', { read: false }) 26 | .pipe(rimraf({ force: true })); 27 | }); 28 | 29 | gulp.task('i18n-zh-json-merge',function(){ 30 | gulp.src('./src/assets/i18n/parts/zh*.json') 31 | .pipe(merge({fileName: 'zh.json'})) 32 | .pipe(gulp.dest('./src/assets/i18n/')); 33 | }); 34 | 35 | gulp.task('i18n-en-json-merge',function(){ 36 | gulp.src('./src/assets/i18n/parts/en*.json') 37 | .pipe(merge({fileName: 'en.json'})) 38 | .pipe(gulp.dest('./src/assets/i18n/')); 39 | }); -------------------------------------------------------------------------------- /images/ex1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunnut/react-dev-dnd/45c34707233b739682059b9532d532b1e494d906/images/ex1.png -------------------------------------------------------------------------------- /mock/modules/index.js: -------------------------------------------------------------------------------- 1 | var login = require('./login/login'); 2 | 3 | module.exports = function (server) { 4 | login(server); 5 | }; -------------------------------------------------------------------------------- /mock/modules/login/login.js: -------------------------------------------------------------------------------- 1 | var userList = require('./users.json'); 2 | 3 | module.exports = function (server) { 4 | server.post('/api/login', function (req, res) { 5 | req.body = {}; 6 | res.send({ 7 | data: req.body, 8 | message: null, 9 | success: true 10 | }); 11 | }); 12 | }; -------------------------------------------------------------------------------- /mock/modules/login/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "name": "admin", 5 | "password": "123456" 6 | }, 7 | { 8 | "id": "2", 9 | "name": "user", 10 | "password": "123456" 11 | } 12 | ] -------------------------------------------------------------------------------- /mock/modules/util/UUID.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | var s = []; 3 | var hexDigits = "0123456789abcdef"; 4 | for (var i = 0; i < 36; i++) { 5 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); 6 | } 7 | s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010 8 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 9 | s[8] = s[13] = s[18] = s[23] = "-"; 10 | 11 | var uuid = s.join(""); 12 | return uuid; 13 | }; -------------------------------------------------------------------------------- /mock/server.js: -------------------------------------------------------------------------------- 1 | var restify = require('restify'); 2 | var partition = require('./modules/index'); 3 | var server = restify.createServer(); 4 | 5 | server.listen(8889, function () { 6 | console.log(''); 7 | console.log(''); 8 | console.log('Mock Server is Started at 8889!!!'); 9 | console.log(''); 10 | console.log(''); 11 | }); 12 | partition(server); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-easy-start", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "ant-design-pro": "^2.3.2", 7 | "antd": "^3.10.8", 8 | "gulp": "^3.9.1", 9 | "gulp-merge-json": "^1.3.1", 10 | "gulp-rimraf": "^0.2.2", 11 | "json-dup-key-validator": "^1.0.2", 12 | "moment": "^2.24.0", 13 | "react": "^16.7.0-alpha", 14 | "react-dom": "^16.7.0-alpha", 15 | "react-grid-layout": "^0.16.6", 16 | "react-intl": "^2.7.2", 17 | "react-loadable": "^5.5.0", 18 | "react-redux": "^5.1.1", 19 | "react-resizable": "^1.8.0", 20 | "react-router-dom": "^4.3.1", 21 | "react-router-redux": "^4.0.8", 22 | "react-scripts": "2.1.1", 23 | "react-sizeme": "^2.6.7", 24 | "redux": "^4.0.1", 25 | "redux-immutable-state-invariant": "^2.1.0", 26 | "redux-thunk": "^2.3.0", 27 | "restify": "^7.2.3", 28 | "run-sequence": "^2.2.1" 29 | }, 30 | "scripts": { 31 | "start": "npm run i18n && react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject", 35 | "mock": "node mock/server.js", 36 | "i18n": "gulp i18n-json-merge" 37 | }, 38 | "eslintConfig": { 39 | "extends": "react-app" 40 | }, 41 | "browserslist": [ 42 | ">0.2%", 43 | "not dead", 44 | "not ie <= 11", 45 | "not op_mini all" 46 | ], 47 | "proxy": "http://localhost:8889" 48 | } 49 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunnut/react-dev-dnd/45c34707233b739682059b9532d532b1e494d906/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React Dev Dnd 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": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router-dom'; 3 | import { view as Loading } from './components/loading'; 4 | import { view as Login } from './login'; 5 | import { view as Home } from './pages'; 6 | 7 | const App = () => { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default App; -------------------------------------------------------------------------------- /src/Store.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, createStore, combineReducers, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { routerReducer } from 'react-router-redux'; 4 | import resetEnhancer from './enhancer/reset.js'; 5 | import { reducer as loadingReducer } from './components/loading'; 6 | import { reducer as sidebarReducer } from './components/sidebar'; 7 | import data from './components/sidebar/data'; 8 | 9 | const originalReducers = { 10 | loading: loadingReducer, 11 | sidebar: sidebarReducer, 12 | routing: routerReducer 13 | }; 14 | const reducer = combineReducers(originalReducers); 15 | const win = window; 16 | const middlewares = [thunk]; 17 | 18 | if (process.env.NODE_ENV !== 'production') { 19 | middlewares.push(require('redux-immutable-state-invariant').default()); 20 | } 21 | 22 | const storeEnhancers = compose( 23 | resetEnhancer, 24 | applyMiddleware(...middlewares), 25 | (win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f 26 | ); 27 | 28 | const initialState = {sidebar: data}; 29 | const store = createStore(reducer, initialState, storeEnhancers); 30 | store._reducers = originalReducers; 31 | export default store; -------------------------------------------------------------------------------- /src/assets/css/common.css: -------------------------------------------------------------------------------- 1 | a, abbr, acronym, address, applet, article, aside, audio, b, big, blockquote, body, canvas, caption, center, cite, 2 | code, dd, del, details, dfn, div, dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, 3 | h5, h6, header, hgroup, html, i, iframe, img, ins, kbd, label, legend, li, mark, menu, nav, object, ol, output, p, 4 | pre, q, ruby, s, samp, section, small, span, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, 5 | time, tr, tt, u, ul, var, video { 6 | border: 0; 7 | font: inherit; 8 | font-size: 100%; 9 | margin: 0; 10 | padding: 0; 11 | vertical-align: baseline; 12 | } 13 | 14 | *, :after, :before { 15 | box-sizing: border-box; 16 | } 17 | 18 | body, html, #root { 19 | width: 100%; 20 | height: 100%; 21 | } 22 | 23 | html { 24 | font-family: sans-serif; 25 | } 26 | 27 | body { 28 | font-weight: 400; 29 | font-size: 1em; 30 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif; 31 | } 32 | 33 | a { 34 | color: #1890ff; 35 | background-color: transparent; 36 | text-decoration: none; 37 | outline: none; 38 | cursor: pointer; 39 | -webkit-transition: color .3s; 40 | transition: color .3s; 41 | -webkit-text-decoration-skip: objects; 42 | } 43 | 44 | a:hover, a:focus { 45 | text-decoration: none; 46 | } 47 | 48 | img { 49 | vertical-align: middle; 50 | border-style: none; 51 | box-sizing: content-box; 52 | max-width: 100%; 53 | } 54 | 55 | h1, h2, h3, h4, h5, h6 { 56 | color: inherit; 57 | font-weight: 600; 58 | line-height: 1.25; 59 | margin-bottom: 16px; 60 | margin-top: 1.5em; 61 | } 62 | 63 | h2 { 64 | font-size: 24px; 65 | } 66 | 67 | ol, ul { 68 | margin-bottom: 1em; 69 | margin-top: 0; 70 | padding-left: 2em; 71 | } 72 | 73 | .ant-dropdown-menu-item { 74 | min-width: 160px; 75 | } 76 | 77 | .antd-pro-charts-timeline-chart-timelineChart { 78 | margin-top: -22px; 79 | } -------------------------------------------------------------------------------- /src/assets/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Name", 3 | "username": "User name", 4 | "overview.title": "Overview", 5 | "topo.title": "Topo" 6 | } -------------------------------------------------------------------------------- /src/assets/i18n/parts/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Name" 3 | } -------------------------------------------------------------------------------- /src/assets/i18n/parts/en_login.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "User name" 3 | } -------------------------------------------------------------------------------- /src/assets/i18n/parts/en_overview.json: -------------------------------------------------------------------------------- 1 | { 2 | "overview.title": "Overview" 3 | } -------------------------------------------------------------------------------- /src/assets/i18n/parts/en_topo.json: -------------------------------------------------------------------------------- 1 | { 2 | "topo.title": "Topo" 3 | } -------------------------------------------------------------------------------- /src/assets/i18n/parts/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "名称" 3 | } -------------------------------------------------------------------------------- /src/assets/i18n/parts/zh_login.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "用户名" 3 | } -------------------------------------------------------------------------------- /src/assets/i18n/parts/zh_overview.json: -------------------------------------------------------------------------------- 1 | { 2 | "overview.title": "总览" 3 | } -------------------------------------------------------------------------------- /src/assets/i18n/parts/zh_topo.json: -------------------------------------------------------------------------------- 1 | { 2 | "topo.title": "拓扑" 3 | } -------------------------------------------------------------------------------- /src/assets/i18n/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "名称", 3 | "username": "用户名", 4 | "overview.title": "总览", 5 | "topo.title": "拓扑" 6 | } -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/header/header.module.css: -------------------------------------------------------------------------------- 1 | .header-wrapper { 2 | position: relative; 3 | padding: 0; 4 | height: 64px; 5 | background: #fff; 6 | box-shadow: 0 1px 4px rgba(0,21,41,.08); 7 | } 8 | 9 | .header-collapsed { 10 | padding: 22px 24px; 11 | height: 64px; 12 | cursor: pointer; 13 | transition: all .3s,padding 0s; 14 | font-size: 20px; 15 | } 16 | 17 | .header-collapsed:hover, .header-dropdown-link:hover { 18 | background-color: rgba(0, 0, 0, .025); 19 | } 20 | 21 | .header-user-info { 22 | float: right; 23 | margin-right: 20px; 24 | } 25 | 26 | .header-dropdown-link { 27 | display: inline-block; 28 | padding: 0 12px; 29 | height: 100%; 30 | text-decoration: none; 31 | color: rgba(0, 0, 0, .65); 32 | transition: all .3s; 33 | cursor: pointer; 34 | } 35 | 36 | .header-dropdown-link i:first-child { 37 | font-size: 20px !important; 38 | } -------------------------------------------------------------------------------- /src/components/header/index.js: -------------------------------------------------------------------------------- 1 | import view from './view'; 2 | 3 | export {view}; -------------------------------------------------------------------------------- /src/components/header/view.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Dropdown, Icon, Menu, Tooltip } from 'antd'; 4 | import { Link } from 'react-router-dom'; 5 | import * as LocalStorage from '../../util/localstorage'; 6 | import { actions } from '../sidebar'; 7 | import styles from './header.module.css'; 8 | 9 | const menu = ( 10 | 11 | 12 | 13 |  偏好设置 14 | 15 | 16 | 17 | 18 | 19 |  退出登录 20 | 21 | 22 | 23 | ); 24 | 25 | const Header = ({collapsed, setCollapsed, addMenu}) => { 26 | return ( 27 |
28 | 29 | setCollapsed(!collapsed)}> 30 | 31 | 32 | 33 | 34 | addMenu({ 35 | "icon": "area-chart", 36 | "key": "menu-new-" + new Date().getTime(), 37 | "label": "New Menu", 38 | "url": "/home/overview/test" 39 | })}> 40 | 41 | 42 | 43 |
44 | 45 | 46 | {LocalStorage.get('TA-username') } 47 | 48 | 49 |
50 |
51 | ); 52 | }; 53 | 54 | const mapDispatchToProps = (dispatch, props) => ({ 55 | collapsed: props.collapsed, 56 | setCollapsed: props.setCollapsed, 57 | addMenu: (menuItem) => { 58 | dispatch(actions.addMenu(menuItem)); 59 | } 60 | }); 61 | 62 | export default connect(null, mapDispatchToProps)(Header); -------------------------------------------------------------------------------- /src/components/layout/index.js: -------------------------------------------------------------------------------- 1 | import view from './view'; 2 | 3 | export {view}; -------------------------------------------------------------------------------- /src/components/layout/view.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactGridLayout from 'react-grid-layout'; 3 | import sizeMe from 'react-sizeme'; 4 | 5 | let lastGridWidth = 1200; 6 | 7 | const GridWrapper = ({ 8 | size, 9 | layout, 10 | onLayoutChange, 11 | children, 12 | onDragStop, 13 | onResize, 14 | onResizeStop, 15 | onWidthChange, 16 | className, 17 | isResizable, 18 | isDraggable, 19 | }) => { 20 | const width = size.width > 0 ? size.width : lastGridWidth; 21 | 22 | if (width !== lastGridWidth) { 23 | lastGridWidth = width; 24 | } 25 | 26 | return ( 27 | 44 | {children} 45 | 46 | ); 47 | }; 48 | 49 | export default sizeMe({ monitorWidth: true })(GridWrapper); -------------------------------------------------------------------------------- /src/components/loader/index.js: -------------------------------------------------------------------------------- 1 | import view from './view'; 2 | 3 | export {view}; -------------------------------------------------------------------------------- /src/components/loader/loader.module.css: -------------------------------------------------------------------------------- 1 | .loader-wrapper { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | min-height: 30px; 6 | } 7 | 8 | .loader-content { 9 | position: absolute; 10 | top: 50%; 11 | left: 50%; 12 | margin-top: -10px; 13 | margin-left: -20px; 14 | } -------------------------------------------------------------------------------- /src/components/loader/view.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | import styles from './loader.module.css'; 4 | 5 | const loader = () => { 6 | return ( 7 |
8 |
9 | 10 |
11 |
12 | ); 13 | }; 14 | 15 | export default loader; -------------------------------------------------------------------------------- /src/components/loading/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const SHOW_LOADING = 'LOADING/SHOW'; 2 | export const HIDE_LOADING = 'LOADING/HIDE'; -------------------------------------------------------------------------------- /src/components/loading/actions.js: -------------------------------------------------------------------------------- 1 | import { HIDE_LOADING, SHOW_LOADING } from './actionTypes'; 2 | 3 | export const showLoading = () => ({ 4 | type: SHOW_LOADING 5 | }); 6 | 7 | export const hideLoading = () => ({ 8 | type: HIDE_LOADING 9 | }); -------------------------------------------------------------------------------- /src/components/loading/index.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions'; 2 | import reducer from './reducer'; 3 | import view from './view'; 4 | 5 | export {actions, reducer, view}; -------------------------------------------------------------------------------- /src/components/loading/loading.module.css: -------------------------------------------------------------------------------- 1 | .loading-wrapper { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | z-index: 1050; 8 | -webkit-overflow-scrolling: touch; 9 | outline: 0; 10 | background: rgba(0, 0, 0, 0.7); 11 | } 12 | 13 | .loading-content { 14 | position: absolute; 15 | left: 50%; 16 | top: 50%; 17 | margin-left: -100px; 18 | margin-top: -10px; 19 | width: 200px; 20 | height: 20px; 21 | z-index: 20000; 22 | text-align: center; 23 | } -------------------------------------------------------------------------------- /src/components/loading/reducer.js: -------------------------------------------------------------------------------- 1 | import { HIDE_LOADING, SHOW_LOADING } from './actionTypes'; 2 | 3 | export default (state = {show: false}, action) => { 4 | switch (action.type) { 5 | case SHOW_LOADING: 6 | return {show: true}; 7 | case HIDE_LOADING: 8 | return {show: false}; 9 | default: 10 | return state; 11 | } 12 | }; -------------------------------------------------------------------------------- /src/components/loading/view.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Spin } from 'antd'; 4 | import styles from './loading.module.css'; 5 | 6 | const Loading = (props) => { 7 | const loadingStyle = { 8 | display: props.show ? 'block' : 'none' 9 | }; 10 | 11 | return ( 12 |
13 |
14 | 15 |
16 |
17 | ); 18 | }; 19 | 20 | const mapStateToProps = (state) => { 21 | const loadingData = state.loading; 22 | 23 | return { 24 | show: loadingData.show 25 | }; 26 | }; 27 | 28 | export default connect(mapStateToProps)(Loading); -------------------------------------------------------------------------------- /src/components/sidebar/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const ADD_MENU = 'MENU/ADD'; -------------------------------------------------------------------------------- /src/components/sidebar/actions.js: -------------------------------------------------------------------------------- 1 | import {ADD_MENU} from './actionTypes'; 2 | 3 | export const addMenu = (menu) => ({ 4 | type: ADD_MENU, 5 | value: menu 6 | }); -------------------------------------------------------------------------------- /src/components/sidebar/data.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "icon": "home", 3 | "key": "overview", 4 | "label": "总览", 5 | "url": "/home/overview" 6 | }, { 7 | "icon": "fork", 8 | "key": "topo", 9 | "label": "拓扑", 10 | "url": "/home/topo" 11 | }, { 12 | "icon": "area-chart", 13 | "key": "sub-res", 14 | "label": "一级菜单", 15 | "children": [{ 16 | "key": "rrs", 17 | "label": "二级菜单001", 18 | "url": "/home/rrs" 19 | }, { 20 | "key": "hms", 21 | "label": "二级菜单002", 22 | "url": "/home/hms" 23 | }, { 24 | "key": "mm", 25 | "label": "二级菜单003", 26 | "url": "/home/mm" 27 | }] 28 | }, { 29 | "icon": "setting", 30 | "key": "setting", 31 | "label": "设置", 32 | "url": "/home/setting" 33 | }] -------------------------------------------------------------------------------- /src/components/sidebar/index.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions'; 2 | import reducer from './reducer'; 3 | import view from './view'; 4 | 5 | export {actions, reducer, view}; -------------------------------------------------------------------------------- /src/components/sidebar/reducer.js: -------------------------------------------------------------------------------- 1 | import {ADD_MENU} from './actionTypes'; 2 | 3 | export default (state = [], action) => { 4 | switch (action.type) { 5 | case ADD_MENU: 6 | return [action.value, ...state]; 7 | default: 8 | return state; 9 | } 10 | }; -------------------------------------------------------------------------------- /src/components/sidebar/sidebar.module.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | position: relative; 3 | padding-left: 14px; 4 | height: 64px; 5 | line-height: 64px; 6 | transition: all .3s; 7 | background: #002140; 8 | overflow: hidden; 9 | } 10 | 11 | .logo img { 12 | display: inline-block; 13 | height: 36px; 14 | } 15 | 16 | .logo h1 { 17 | display: inline-block; 18 | margin: 0; 19 | color: #fff; 20 | vertical-align: middle; 21 | font-size: 20px; 22 | font-family: Avenir,Helvetica Neue,Arial,Helvetica,sans-serif; 23 | font-weight: 600; 24 | } -------------------------------------------------------------------------------- /src/components/sidebar/view.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import { Icon, Menu } from 'antd'; 5 | import styles from './sidebar.module.css'; 6 | import logo from '../../assets/images/logo.svg'; 7 | const { SubMenu } = Menu; 8 | 9 | const Sidebar = ({data, collapsed}) => { 10 | const [current, setCurrent] = useState('overview'); 11 | 12 | return ( 13 |
14 | 20 | setCurrent(e.key)} 23 | style={{ padding: '16px 0', width: '100%' }} 24 | defaultOpenKeys={['overview', 'sub-res', 'sub-other']} 25 | selectedKeys={[current]} 26 | mode="inline" 27 | inlineCollapsed={collapsed} 28 | > 29 | { 30 | data.map((item) => { 31 | if (item.children instanceof Array) { 32 | return ( 33 | {item.label}}> 35 | { 36 | item.children.map((subItem) => ( 37 | 38 | {subItem.label} 39 | 40 | )) 41 | } 42 | 43 | ) 44 | } else { 45 | return ( 46 | 47 | 48 | {item.label} 49 | 50 | 51 | ) 52 | } 53 | }) 54 | } 55 | 56 |
57 | ); 58 | }; 59 | 60 | const mapStateToProps = (state, props) => ({ 61 | data: state.sidebar, 62 | collapsed: props.collapsed 63 | }); 64 | 65 | export default connect(mapStateToProps)(Sidebar); -------------------------------------------------------------------------------- /src/enhancer/reset.js: -------------------------------------------------------------------------------- 1 | const RESET_ACTION_TYPE = '@@RESET'; 2 | 3 | const resetReducerCreator = (reducer, resetState) => (state, action) => { 4 | if (action.type === RESET_ACTION_TYPE) { 5 | return resetState; 6 | } else { 7 | return reducer(state, action); 8 | } 9 | }; 10 | 11 | const reset = (createStore) => (reducer, preloadedState, enhancer) => { 12 | const store = createStore(reducer, preloadedState, enhancer); 13 | 14 | const resetInner = (resetReducer, resetState) => { 15 | const newReducer = resetReducerCreator(resetReducer, resetState); 16 | store.replaceReducer(newReducer); 17 | store.dispatch({type: RESET_ACTION_TYPE, state: resetState}); 18 | }; 19 | 20 | return { 21 | ...store, 22 | resetInner 23 | }; 24 | }; 25 | 26 | export default reset; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import { Provider } from 'react-redux'; 5 | import { LocaleProvider } from 'antd'; 6 | import zh_CN from 'antd/lib/locale-provider/zh_CN'; 7 | import en_US from 'antd/lib/locale-provider/en_US'; 8 | import moment from 'moment'; 9 | import 'moment/locale/zh-cn'; 10 | import 'moment/locale/en-au'; 11 | import { addLocaleData, IntlProvider } from 'react-intl'; 12 | import en from 'react-intl/locale-data/en'; 13 | import zh from 'react-intl/locale-data/zh'; 14 | import ZH_WORDS from './assets/i18n/zh.json'; 15 | import EN_WORDS from './assets/i18n/en.json'; 16 | import store from './Store.js'; 17 | import App from './App'; 18 | import 'antd/dist/antd.css'; 19 | import 'ant-design-pro/dist/ant-design-pro.css'; 20 | import 'react-grid-layout/css/styles.css'; 21 | import 'react-resizable/css/styles.css'; 22 | import './assets/css/common.css'; 23 | 24 | addLocaleData([...en, ...zh]); 25 | 26 | const userLang = navigator.language || ''; 27 | const language = userLang.toLowerCase().substr(0, 2); 28 | let localAntD; 29 | let locale; 30 | let messages; 31 | 32 | if (language === 'zh') { 33 | locale = 'zh'; 34 | messages = ZH_WORDS; 35 | moment.locale('zh-cn'); 36 | localAntD = zh_CN; 37 | } else if (language === 'en') { 38 | locale = 'en'; 39 | messages = EN_WORDS; 40 | moment.locale('en-au'); 41 | localAntD = en_US; 42 | } 43 | 44 | ReactDOM.render( 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | , 54 | document.getElementById('root') 55 | ); -------------------------------------------------------------------------------- /src/login/actions.js: -------------------------------------------------------------------------------- 1 | import { actions as loadingActions } from '../components/loading/index'; 2 | import * as Fetch from '../util/fetch'; 3 | import * as LocalStorage from '../util/localstorage'; 4 | 5 | export const login = (formVal, history) => { 6 | return (dispatch) => { 7 | dispatch(loadingActions.showLoading()); 8 | 9 | Fetch.post('/api/login', formVal).then((response) => { 10 | dispatch(loadingActions.hideLoading()); 11 | 12 | if (response) { 13 | LocalStorage.put('TA-username', formVal.userName); 14 | // 没有发生异常,跳转至主页 15 | history.push('/home/overview'); 16 | } 17 | }); 18 | }; 19 | }; -------------------------------------------------------------------------------- /src/login/index.js: -------------------------------------------------------------------------------- 1 | import * as actions from './actions'; 2 | import view from './view'; 3 | 4 | export {actions, view}; -------------------------------------------------------------------------------- /src/login/login.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | position: fixed; 3 | width: 100%; 4 | min-height: 50px; 5 | padding: 8px 0; 6 | color: #fff; 7 | background: #20232a; 8 | } 9 | 10 | .header-wrapper { 11 | max-width: 1100px; 12 | margin: 0 auto; 13 | padding: 0 20px; 14 | } 15 | 16 | .header-wrapper header { 17 | position: relative; 18 | display: flex; 19 | flex-flow: row nowrap; 20 | text-align: left; 21 | } 22 | 23 | .header-wrapper header a { 24 | display: flex; 25 | height: 34px; 26 | align-items: center; 27 | border: 0; 28 | color: #fff; 29 | flex-flow: row nowrap; 30 | } 31 | 32 | .header-wrapper header img { 33 | height: 100%; 34 | margin-right: 10px; 35 | } 36 | 37 | .header-wrapper header h2 { 38 | display: block; 39 | position: relative; 40 | line-height: 18px; 41 | margin: 0; 42 | font-size: 1.25em; 43 | white-space: nowrap; 44 | } 45 | 46 | .nav-wrapper { 47 | position: relative; 48 | height: 34px; 49 | margin-left: auto; 50 | } 51 | 52 | .nav-wrapper nav { 53 | position: fixed; 54 | top: 0; 55 | right: 0; 56 | bottom: auto; 57 | left: 0; 58 | box-sizing: border-box; 59 | } 60 | 61 | @media only screen and (min-width: 1024px) { 62 | .nav-wrapper nav { 63 | position: relative; 64 | right: auto; 65 | top: auto; 66 | width: auto; 67 | height: auto; 68 | background: none; 69 | } 70 | } 71 | 72 | .nav-wrapper nav ul { 73 | display: flex; 74 | flex-wrap: nowrap; 75 | list-style: none; 76 | margin-top: 50px; 77 | padding: 0; 78 | width: 100%; 79 | background: #61dafb; 80 | box-sizing: border-box; 81 | color: #fff; 82 | } 83 | 84 | @media only screen and (min-width: 1024px) { 85 | .nav-wrapper nav ul { 86 | display: flex; 87 | flex-flow: row nowrap; 88 | margin: 0; 89 | padding: 0; 90 | width: auto; 91 | background: none; 92 | } 93 | } 94 | 95 | .nav-wrapper nav ul li { 96 | flex: 1 1 auto; 97 | margin: 0; 98 | text-align: center; 99 | white-space: nowrap; 100 | } 101 | 102 | .nav-wrapper nav ul li a { 103 | display: flex; 104 | margin: 0; 105 | padding: 10px; 106 | align-items: center; 107 | box-sizing: border-box; 108 | color: inherit; 109 | font-size: .9em; 110 | height: 50px; 111 | justify-content: center; 112 | transition: background-color .3s; 113 | } 114 | 115 | .nav-wrapper nav ul li a:hover { 116 | color: #ffffff; 117 | } 118 | 119 | @media only screen and (min-width: 1024px) { 120 | .nav-wrapper nav ul li a { 121 | display: flex; 122 | margin: 0; 123 | padding: 6px 10px; 124 | height: 32px; 125 | line-height: 1.2em; 126 | border: 0; 127 | color: hsla(0,0%,100%,.8); 128 | font-size: 1em; 129 | font-weight: 300; 130 | } 131 | } 132 | 133 | .content { 134 | position: absolute; 135 | left: 50%; 136 | top: 50%; 137 | margin-left: -200px; 138 | margin-top: -250px; 139 | width: 400px; 140 | height: 500px; 141 | padding: 20px; 142 | color: #EEE; 143 | } 144 | 145 | .login-form h3 { 146 | height: 20px; 147 | line-height: 20px; 148 | padding: 0 0 35px 0; 149 | text-align: center; 150 | font: 20px "microsoft yahei",Helvetica,Tahoma,Arial,"Microsoft jhengHei",sans-serif; 151 | } 152 | 153 | .login-form-forgot { 154 | float: right; 155 | } 156 | 157 | .login-form-button { 158 | width: 100%; 159 | } 160 | 161 | .footer { 162 | position: fixed; 163 | bottom: 0; 164 | width: 100%; 165 | height: 70px; 166 | font-size: 12px; 167 | line-height: 70px; 168 | color: #999; 169 | text-align: center; 170 | clear: both; 171 | } -------------------------------------------------------------------------------- /src/login/view.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Checkbox, Button, Form, Icon, Input } from 'antd'; 3 | import { connect } from 'react-redux'; 4 | import { actions as loginActions } from './index'; 5 | import logo from '../assets/images/logo.svg'; 6 | import styles from './login.module.css'; 7 | const FormItem = Form.Item; 8 | 9 | const LoginPage = ({login}) => { 10 | let userNameInput = null; 11 | const [userName, setUserName] = useState(''); 12 | const [password, setPassword] = useState(''); 13 | 14 | const emitEmptyUserName = () => { 15 | userNameInput.focus(); 16 | setUserName(''); 17 | }; 18 | 19 | const gotoLogin = (e) => { 20 | e.preventDefault(); 21 | login({userName, password}); 22 | }; 23 | 24 | const userNameSuffix = userName ? : null; 25 | 26 | return ( 27 | <> 28 |
29 |
30 |
31 | 32 | ant design mini 33 |

React Dev Dnd

34 |
35 |
36 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |

欢迎登录

53 | 54 | } 57 | suffix={userNameSuffix} 58 | value={userName} 59 | onChange={(e) => setUserName(e.target.value)} 60 | ref={node => userNameInput = node} 61 | size="large" 62 | /> 63 | 64 | 65 | } 69 | value={password} 70 | onChange={(e) => setPassword(e.target.value)} 71 | size="large" 72 | /> 73 | 74 | 75 | 记住 76 | 忘记密码 77 | 80 | 注册 81 | 82 |
83 |
84 |
85 | 版权所有 © XXX有限公司 2018 86 |
87 | 88 | ); 89 | }; 90 | 91 | const mapDispachToProps = (dispatch, props) => ({ 92 | login: (formValue) => { 93 | // 等待 94 | dispatch(loginActions.login(formValue, props.history)); 95 | } 96 | }); 97 | 98 | export default connect(null, mapDispachToProps)(LoginPage); -------------------------------------------------------------------------------- /src/pages/home.module.css: -------------------------------------------------------------------------------- 1 | .content-wrapper { 2 | min-height: 100vh !important; 3 | } 4 | 5 | .header { 6 | padding: 0 !important; 7 | width: 100%; 8 | } 9 | 10 | .content { 11 | padding-top: 0; 12 | margin: 24px; 13 | } -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import view from './view'; 2 | 3 | export {view}; -------------------------------------------------------------------------------- /src/pages/overview/dashboard.module.css: -------------------------------------------------------------------------------- 1 | .toolbar-wrapper { 2 | padding: 8px; 3 | margin-bottom: 10px; 4 | border: 1px solid #e6e9ed; 5 | background-color: #ffffff; 6 | } 7 | 8 | .toolbar { 9 | align-items: center; 10 | } 11 | 12 | .toolbar i { 13 | display: inline-block; 14 | margin: 0 6px; 15 | width: 27px; 16 | height: 27px; 17 | line-height: 27px; 18 | text-align: center; 19 | vertical-align: middle; 20 | cursor: pointer; 21 | } 22 | 23 | .toolbar i:hover { 24 | border: 1px solid #e6e9ed; 25 | } -------------------------------------------------------------------------------- /src/pages/overview/index.js: -------------------------------------------------------------------------------- 1 | import Loadable from 'react-loadable'; 2 | import { view as Loader } from '../../components/loader'; 3 | 4 | const view = Loadable({ 5 | loader: () => import('./view'), 6 | loading: Loader 7 | }); 8 | 9 | export {view}; -------------------------------------------------------------------------------- /src/pages/overview/view.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useEffect, useState } from 'react'; 2 | import { Card, Form, Input, Icon, Modal, Tooltip } from 'antd'; 3 | import { view as GridLayout } from '../../components/layout'; 4 | import { pluginMap, pluginInfo } from '../../plugins'; 5 | import styles from './dashboard.module.css'; 6 | 7 | const FormItem = Form.Item; 8 | 9 | //================================================================ 10 | // 获取点击事件源 11 | //================================================================ 12 | const getEventTarget = (e) => { 13 | let targetEle = e.target; 14 | 15 | if (targetEle.nodeName === 'svg') { 16 | targetEle = targetEle.parentNode; 17 | } else if (targetEle.nodeName === 'path') { 18 | targetEle = targetEle.parentNode.parentNode; 19 | } 20 | 21 | return targetEle; 22 | }; 23 | 24 | const formItemLayout = { 25 | labelCol: { 26 | xs: { span: 24 }, 27 | sm: { span: 6 }, 28 | }, 29 | wrapperCol: { 30 | xs: { span: 24 }, 31 | sm: { span: 14 }, 32 | }, 33 | }; 34 | 35 | //================================================================ 36 | // Dashboard组件 37 | //================================================================ 38 | const OverviewComponent = (props) => { 39 | // 页面ID 40 | let pageName = props.match.params.id; 41 | //let currentPanelId; 42 | const [visible, setVisible] = useState(false); 43 | const [panelList, setPanelList] = useState([]); 44 | 45 | useEffect( 46 | () => { 47 | setTimeout(() => { 48 | setPanelList([{ 49 | id: 'b', 50 | title: '折线图', 51 | type: 'line-chart', 52 | sql: '123', 53 | gridPos: {x: 0, y: 0, w: 8, h: 8} 54 | }, { 55 | id: 'a', 56 | title: '柱状图', 57 | type: 'bar-chart', 58 | sql: '123', 59 | gridPos: {x: 8, y: 0, w: 8, h: 8} 60 | }, { 61 | id: 'c', 62 | title: '饼图', 63 | type: 'pie-chart', 64 | sql: '123', 65 | gridPos: {x: 16, y: 0, w: 8, h: 8} 66 | }]); 67 | }, 500) 68 | }, 69 | [pageName] 70 | ); 71 | 72 | let panelMap = {}; 73 | 74 | //================================================================ 75 | // 根据panel信息生成其排版位置信息 76 | //================================================================ 77 | const buildLayout = function() { 78 | const layout = []; 79 | 80 | for (let panel of panelList) { 81 | let stringId = panel.id.toString(); 82 | panelMap[stringId] = panel; 83 | 84 | if (!panel.gridPos) { 85 | continue; 86 | } 87 | 88 | let panelPos = { 89 | i: stringId, 90 | x: panel.gridPos.x, 91 | y: panel.gridPos.y, 92 | w: panel.gridPos.w, 93 | h: panel.gridPos.h, 94 | }; 95 | 96 | layout.push(panelPos); 97 | } 98 | 99 | return layout; 100 | }; 101 | 102 | //================================================================ 103 | // 新增Panel 104 | //================================================================ 105 | const addPanel = function(e) { 106 | e.preventDefault(); 107 | let targetEle = getEventTarget(e); 108 | 109 | const panelType = targetEle.getAttribute('data-id'); 110 | setPanelList([{ 111 | id: '' + new Date().getTime(), 112 | title: 'New Chart', 113 | type: panelType, 114 | sql: '1232', 115 | gridPos: {x: 0, y: 0, w: 8, h: 8} 116 | }, ...panelList]); 117 | return false; 118 | }; 119 | 120 | //================================================================ 121 | // 移除Panel 122 | //================================================================ 123 | const removePanel = function(e) { 124 | e.preventDefault(); 125 | let targetEle = getEventTarget(e); 126 | const panelId = targetEle.getAttribute('data-id'); 127 | setPanelList(panelList.filter(x => x.id !== panelId)); 128 | return false; 129 | }; 130 | 131 | //================================================================ 132 | // 编辑Panel 133 | //================================================================ 134 | const editPanel = function(e) { 135 | e.preventDefault(); 136 | //let targetEle = getEventTarget(e); 137 | //currentPanelId = targetEle.getAttribute('data-id'); 138 | setVisible(true); 139 | return false; 140 | }; 141 | 142 | //================================================================ 143 | // 提交编辑Panel 144 | //================================================================ 145 | const editSubmit = function () { 146 | // null 147 | }; 148 | 149 | //================================================================ 150 | // 全屏或退出 151 | //================================================================ 152 | const toggleFullScreen = function(e) { 153 | e.preventDefault(); 154 | // let targetEle = getEventTarget(e); 155 | // const panelId = targetEle.getAttribute('data-id'); 156 | return false; 157 | }; 158 | 159 | //================================================================ 160 | // Panel操作工具栏 161 | //================================================================ 162 | const operateToolBar = (panelId) => { 163 | return ( 164 |
165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 |
178 | ); 179 | }; 180 | 181 | return ( 182 | <> 183 |
184 |
185 |
186 | { 187 | pluginInfo.map((plugin) => ( 188 | 189 | 191 | 192 | )) 193 | } 194 |  (<-- 点击添加组件,组件可拖拽 & 缩放) 195 | 196 | 197 | 198 |
199 |
200 |
201 | { 202 | panelList.length > 0 && ( 203 | loading}> 204 | 209 | { 210 | panelList.map((panel) => { 211 | // const PanelCmp = React.lazy(() => import('../../plugins/' + panel.type + '/view')); 212 | const PanelCmp = pluginMap[panel.type]; 213 | 214 | return ( 215 | 220 | 221 | 222 | ) 223 | }) 224 | } 225 | 226 | 227 | ) 228 | } 229 | setVisible(false)} 234 | > 235 |
236 | 238 | 239 | 240 | 242 | 243 | 244 |
245 |
246 | 247 | ); 248 | }; 249 | 250 | export default OverviewComponent; -------------------------------------------------------------------------------- /src/pages/topo/index.js: -------------------------------------------------------------------------------- 1 | import Loadable from 'react-loadable'; 2 | import { view as Loader } from '../../components/loader'; 3 | 4 | const view = Loadable({ 5 | loader: () => import('./view'), 6 | loading: Loader 7 | }); 8 | 9 | export {view}; -------------------------------------------------------------------------------- /src/pages/topo/view.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { injectIntl } from 'react-intl'; 3 | 4 | const topoComponent = (props) => { 5 | const { intl } = props; 6 | return ( 7 |
{ intl.formatMessage({id: 'topo.title'}) }
8 | ); 9 | }; 10 | 11 | export default injectIntl(topoComponent); -------------------------------------------------------------------------------- /src/pages/view.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Route } from 'react-router-dom'; 3 | import { view as Header } from '../components/header'; 4 | import { view as Sidebar } from '../components/sidebar'; 5 | import { view as Overview } from './overview'; 6 | import { view as Topo } from './topo'; 7 | import styles from './home.module.css'; 8 | 9 | const HomePage = () => { 10 | const [collapsed, setCollapsed] = useState(false); 11 | const sidebarWidth = collapsed ? 80 : 256; 12 | const sidebarStyle = { 13 | flex: '0 0 ' + sidebarWidth + 'px', 14 | width: sidebarWidth + 'px' 15 | }; 16 | 17 | return ( 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 |
32 | ); 33 | }; 34 | 35 | export default HomePage; -------------------------------------------------------------------------------- /src/plugins/bar-chart/view.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Bar } from 'ant-design-pro/lib/Charts'; 3 | 4 | const salesData = []; 5 | 6 | for (let i = 0; i < 12; i += 1) { 7 | salesData.push({ 8 | x: `${i + 1}月`, 9 | y: Math.floor(Math.random() * 1000) + 200, 10 | }); 11 | } 12 | 13 | const BarComponent = (props) => { 14 | return ( 15 | <> 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default BarComponent; -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import Bar from './bar-chart/view'; 2 | import LineChart from './line-chart/view'; 3 | import PieChart from './pie-chart/view'; 4 | 5 | // import React from 'react'; 6 | 7 | // const BarPromise = import('./bar-chart/view'); 8 | // const Bar = React.lazy(() => BarPromise); 9 | // 10 | // const LineChartPromise = import('./line-chart/view'); 11 | // const LineChart = React.lazy(() => LineChartPromise); 12 | // 13 | // const PieChartPromise = import('./pie-chart/view'); 14 | // const PieChart = React.lazy(() => PieChartPromise); 15 | 16 | const pluginInfo = [{ 17 | name: 'bar-chart', 18 | icon: 'bar-chart', 19 | title: '柱状图' 20 | }, { 21 | name: 'line-chart', 22 | icon: 'line-chart', 23 | title: '线状图' 24 | }, { 25 | name: 'pie-chart', 26 | icon: 'pie-chart', 27 | title: '饼图' 28 | }]; 29 | 30 | const pluginMap = { 31 | 'bar-chart': Bar, 32 | 'line-chart': LineChart, 33 | 'pie-chart': PieChart 34 | }; 35 | 36 | export {pluginMap, pluginInfo}; -------------------------------------------------------------------------------- /src/plugins/line-chart/view.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MiniArea } from 'ant-design-pro/lib/Charts'; 3 | import moment from 'moment'; 4 | 5 | const visitData = []; 6 | const beginDay = new Date().getTime(); 7 | for (let i = 0; i < 20; i += 1) { 8 | visitData.push({ 9 | x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), 10 | y: Math.floor(Math.random() * 100) + 10, 11 | }); 12 | } 13 | 14 | const LineComponent = (props) => { 15 | return ( 16 | <> 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default LineComponent; -------------------------------------------------------------------------------- /src/plugins/pie-chart/view.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Pie } from 'ant-design-pro/lib/Charts'; 3 | 4 | const LineComponent = (props) => { 5 | return ( 6 | <> 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default LineComponent; -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/util/fetch.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | 3 | const request = (url, config) => { 4 | return fetch(url, config).then((res) => { 5 | if (!res.ok) { 6 | // 服务器异常返回 7 | throw Error(''); 8 | } 9 | 10 | return res.json(); 11 | }).then((resJson) => { 12 | if (!resJson.success) { 13 | // 项目内部认为的错误 14 | throw Error(''); 15 | } else { 16 | return resJson; 17 | } 18 | }).catch(() => { 19 | // 公共错误处理 20 | message.error('内部错误,请重新登录'); 21 | }); 22 | }; 23 | 24 | // GET请求 25 | export const get = (url) => { 26 | return request(url, {method: 'GET'}); 27 | }; 28 | 29 | // POST请求 30 | export const post = (url, data) => { 31 | return request(url, { 32 | body: JSON.stringify(data), 33 | headers: { 34 | 'content-type': 'application/json' 35 | }, 36 | method: 'POST' 37 | }); 38 | }; -------------------------------------------------------------------------------- /src/util/localstorage.js: -------------------------------------------------------------------------------- 1 | const local = window.localStorage; 2 | const json = JSON; 3 | const disable = local === undefined || json === undefined; 4 | 5 | const put = (key, data) => { 6 | if (disable) { 7 | return; 8 | } 9 | 10 | try { 11 | let jsonstr = json.stringify(data); 12 | local[key] = jsonstr; 13 | } catch (err) { 14 | // null 15 | } 16 | }; 17 | 18 | const get = (key) => { 19 | if (disable) { 20 | return undefined; 21 | } 22 | 23 | try { 24 | let jsonstr = local[key]; 25 | const data = json.parse(jsonstr); 26 | return data; 27 | } catch (err) { 28 | return undefined; 29 | } 30 | }; 31 | 32 | const remove = (key) => { 33 | if (disable) { 34 | return; 35 | } 36 | 37 | try { 38 | local.removeItem(key); 39 | } catch (err) { 40 | // null 41 | } 42 | }; 43 | 44 | const clear = () => { 45 | if (disable) { 46 | return; 47 | } 48 | 49 | local.clear(); 50 | }; 51 | 52 | export {put, get, remove, clear}; --------------------------------------------------------------------------------