tuonina 5 years ago
commit
12249d4483
100 changed files with 11196 additions and 0 deletions
  1. 19 0
      .eslintrc
  2. 80 0
      .gitignore
  3. 28 0
      .idea/codeStyles/Project.xml
  4. 5 0
      .idea/codeStyles/codeStyleConfig.xml
  5. 6 0
      .idea/misc.xml
  6. 8 0
      .idea/modules.xml
  7. 12 0
      .idea/tuonq-admin.iml
  8. 6 0
      .idea/vcs.xml
  9. 144 0
      .idea/workspace.xml
  10. 7 0
      .prettierrc
  11. 185 0
      README.md
  12. 93 0
      config/env.js
  13. 14 0
      config/jest/cssTransform.js
  14. 30 0
      config/jest/fileTransform.js
  15. 57 0
      config/paths.js
  16. 416 0
      config/webpack.config.dev.js
  17. 525 0
      config/webpack.config.prod.js
  18. 105 0
      config/webpackDevServer.config.js
  19. 146 0
      package.json
  20. BIN
      public/favicon.ico
  21. BIN
      public/images/icons/icon-128x128.png
  22. BIN
      public/images/icons/icon-144x144.png
  23. BIN
      public/images/icons/icon-152x152.png
  24. BIN
      public/images/icons/icon-192x192.png
  25. BIN
      public/images/icons/icon-384x384.png
  26. BIN
      public/images/icons/icon-512x512.png
  27. BIN
      public/images/icons/icon-72x72.png
  28. BIN
      public/images/icons/icon-96x96.png
  29. 51 0
      public/index.html
  30. BIN
      public/logo.png
  31. 51 0
      public/manifest.json
  32. 3067 0
      public/theme.less
  33. 189 0
      scripts/build.js
  34. 116 0
      scripts/start.js
  35. 53 0
      scripts/test.js
  36. 107 0
      src/App.js
  37. 9 0
      src/App.test.js
  38. 17 0
      src/Page.js
  39. 19 0
      src/axios/config.js
  40. 47 0
      src/axios/index.js
  41. 37 0
      src/axios/tools.js
  42. 24 0
      src/components/BreadcrumbCustom.jsx
  43. 113 0
      src/components/HeaderCustom.jsx
  44. 17 0
      src/components/Page.jsx
  45. 95 0
      src/components/SiderCustom.jsx
  46. 79 0
      src/components/SiderMenu.js
  47. 68 0
      src/components/animation/BasicAnimations.jsx
  48. 102 0
      src/components/animation/ExampleAnimations.jsx
  49. 62 0
      src/components/auth/Basic.js
  50. 38 0
      src/components/auth/RouterEnter.js
  51. 55 0
      src/components/charts/Echarts.jsx
  52. 106 0
      src/components/charts/EchartsArea.jsx
  53. 522 0
      src/components/charts/EchartsEffectScatter.js
  54. 247 0
      src/components/charts/EchartsForce.js
  55. 85 0
      src/components/charts/EchartsGraphnpm.jsx
  56. 86 0
      src/components/charts/EchartsPie.jsx
  57. 131 0
      src/components/charts/EchartsScatter.jsx
  58. 54 0
      src/components/charts/Recharts.jsx
  59. 34 0
      src/components/charts/RechartsBarChart.jsx
  60. 30 0
      src/components/charts/RechartsRadarChart.jsx
  61. 27 0
      src/components/charts/RechartsRadialBarChart.jsx
  62. 36 0
      src/components/charts/RechartsSimpleLineChart.jsx
  63. 31 0
      src/components/cssmodule/index.js
  64. 32 0
      src/components/cssmodule/index.module.less
  65. 174 0
      src/components/dashboard/Dashboard.jsx
  66. 116 0
      src/components/dashboard/EchartsProjects.jsx
  67. 121 0
      src/components/dashboard/EchartsViews.jsx
  68. 39 0
      src/components/extension/QueryParams.js
  69. 255 0
      src/components/forms/BasicForm.jsx
  70. 69 0
      src/components/forms/HorizontalForm.jsx
  71. 55 0
      src/components/forms/LoginForm.jsx
  72. 87 0
      src/components/forms/ModalForm.jsx
  73. 62 0
      src/components/index.js
  74. 85 0
      src/components/pages/Login.jsx
  75. 24 0
      src/components/pages/NotFound.jsx
  76. 46 0
      src/components/tables/AdvancedTables.jsx
  77. 80 0
      src/components/tables/AsynchronousTable.jsx
  78. 57 0
      src/components/tables/BasicTable.jsx
  79. 52 0
      src/components/tables/BasicTables.jsx
  80. 191 0
      src/components/tables/EditableTable.jsx
  81. 28 0
      src/components/tables/ExpandedTable.jsx
  82. 43 0
      src/components/tables/FixedTable.jsx
  83. 123 0
      src/components/tables/SearchTable.jsx
  84. 76 0
      src/components/tables/SelectTable.jsx
  85. 106 0
      src/components/tables/SortTable.jsx
  86. 136 0
      src/components/ui/Buttons.jsx
  87. 122 0
      src/components/ui/Draggable.jsx
  88. 187 0
      src/components/ui/Gallery.jsx
  89. 21 0
      src/components/ui/Icons.jsx
  90. 221 0
      src/components/ui/Modals.jsx
  91. 128 0
      src/components/ui/Notifications.jsx
  92. 94 0
      src/components/ui/Spins.jsx
  93. 152 0
      src/components/ui/Tabs.jsx
  94. 136 0
      src/components/ui/Wysiwyg.jsx
  95. 57 0
      src/components/ui/banners/AutoPlay.jsx
  96. 60 0
      src/components/ui/banners/Basic.jsx
  97. 188 0
      src/components/ui/banners/Custom.jsx
  98. 45 0
      src/components/ui/banners/index.jsx
  99. 2 0
      src/components/ui/emoji/iconfont.js
  100. 35 0
      src/components/ui/emoji/index.jsx

+ 19 - 0
.eslintrc

@@ -0,0 +1,19 @@
+{
+  "extends": "react-app",
+  "plugins": [
+    "react-hooks"
+  ],
+  "rules": {
+    "react-hooks/rules-of-hooks": "error",
+    "react-hooks/exhaustive-deps": "warn",
+    "no-multi-spaces": 1,
+    "react/jsx-tag-spacing": 1,        // 总是在自动关闭的标签前加一个空格,正常情况下也不需要换行
+    "jsx-quotes": 1,
+    "react/jsx-closing-bracket-location": 1,    // 遵循JSX语法缩进/格式
+    "react/jsx-boolean-value": 1,               // 如果属性值为 true, 可以直接省略
+    "react/no-string-refs": 1,      // 总是在Refs里使用回调函数
+    "react/self-closing-comp": 1,    // 对于没有子元素的标签来说总是自己关闭标签
+    "react/sort-comp": 1,            // 按照具体规范的React.createClass 的生命周期函数书写代码
+    "react/jsx-pascal-case": 1        // React模块名使用帕斯卡命名,实例使用骆驼式命名
+  }
+}

+ 80 - 0
.gitignore

@@ -0,0 +1,80 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# next.js build output
+.next
+
+# nuxt.js build output
+.nuxt
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/

+ 28 - 0
.idea/codeStyles/Project.xml

@@ -0,0 +1,28 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <JSCodeStyleSettings version="0">
+      <option name="FORCE_SEMICOLON_STYLE" value="true" />
+      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
+      <option name="USE_DOUBLE_QUOTES" value="false" />
+      <option name="FORCE_QUOTE_STYlE" value="true" />
+      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
+      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
+      <option name="SPACES_WITHIN_IMPORTS" value="true" />
+    </JSCodeStyleSettings>
+    <TypeScriptCodeStyleSettings version="0">
+      <option name="FORCE_SEMICOLON_STYLE" value="true" />
+      <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
+      <option name="USE_DOUBLE_QUOTES" value="false" />
+      <option name="FORCE_QUOTE_STYlE" value="true" />
+      <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
+      <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
+      <option name="SPACES_WITHIN_IMPORTS" value="true" />
+    </TypeScriptCodeStyleSettings>
+    <codeStyleSettings language="JavaScript">
+      <option name="SOFT_MARGINS" value="100" />
+    </codeStyleSettings>
+    <codeStyleSettings language="TypeScript">
+      <option name="SOFT_MARGINS" value="100" />
+    </codeStyleSettings>
+  </code_scheme>
+</component>

+ 5 - 0
.idea/codeStyles/codeStyleConfig.xml

@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="JSX" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/tuonq-admin.iml" filepath="$PROJECT_DIR$/.idea/tuonq-admin.iml" />
+    </modules>
+  </component>
+</project>

+ 12 - 0
.idea/tuonq-admin.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 144 - 0
.idea/workspace.xml

@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <list default="true" id="6f4a0bf1-27c2-4804-af97-a0a1bdd98e1d" name="Default Changelist" comment="" />
+    <ignored path="$PROJECT_DIR$/.tmp/" />
+    <ignored path="$PROJECT_DIR$/temp/" />
+    <ignored path="$PROJECT_DIR$/tmp/" />
+    <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="FileEditorManager">
+    <leaf>
+      <file pinned="false" current-in-tab="true">
+        <entry file="file://$PROJECT_DIR$/src/Page.js">
+          <provider selected="true" editor-type-id="text-editor">
+            <state>
+              <folding>
+                <element signature="e#0#26#0" expanded="true" />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+    </leaf>
+  </component>
+  <component name="ProjectFrameBounds" extendedState="6">
+    <option name="x" value="85" />
+    <option name="y" value="25" />
+    <option name="width" value="1750" />
+    <option name="height" value="980" />
+  </component>
+  <component name="ProjectView">
+    <navigator proportions="" version="1">
+      <foldersAlwaysOnTop value="true" />
+    </navigator>
+    <panes>
+      <pane id="ProjectPane">
+        <subPane>
+          <expand>
+            <path>
+              <item name="tuonq-admin" type="b2602c69:ProjectViewProjectNode" />
+              <item name="tuonq-admin" type="462c0819:PsiDirectoryNode" />
+            </path>
+            <path>
+              <item name="tuonq-admin" type="b2602c69:ProjectViewProjectNode" />
+              <item name="tuonq-admin" type="462c0819:PsiDirectoryNode" />
+              <item name="src" type="462c0819:PsiDirectoryNode" />
+            </path>
+          </expand>
+          <select />
+        </subPane>
+      </pane>
+      <pane id="Scope" />
+    </panes>
+  </component>
+  <component name="PropertiesComponent">
+    <property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
+    <property name="WebServerToolWindowFactoryState" value="false" />
+    <property name="node.js.detected.package.eslint" value="true" />
+    <property name="node.js.detected.package.standard" value="true" />
+    <property name="node.js.path.for.package.eslint" value="project" />
+    <property name="node.js.path.for.package.standard" value="project" />
+    <property name="node.js.selected.package.eslint" value="(autodetect)" />
+    <property name="node.js.selected.package.standard" value="" />
+    <property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
+    <property name="nodejs_npm_path_reset_for_default_project" value="true" />
+    <property name="nodejs_package_manager_path" value="yarn" />
+  </component>
+  <component name="RunDashboard">
+    <option name="ruleStates">
+      <list>
+        <RuleState>
+          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
+        </RuleState>
+        <RuleState>
+          <option name="name" value="StatusDashboardGroupingRule" />
+        </RuleState>
+      </list>
+    </option>
+  </component>
+  <component name="SvnConfiguration">
+    <configuration />
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="6f4a0bf1-27c2-4804-af97-a0a1bdd98e1d" name="Default Changelist" comment="" />
+      <created>1565328742876</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1565328742876</updated>
+      <workItem from="1565328743940" duration="480000" />
+    </task>
+    <servers />
+  </component>
+  <component name="TimeTrackingManager">
+    <option name="totallyTimeSpent" value="480000" />
+  </component>
+  <component name="ToolWindowManager">
+    <frame x="-7" y="-7" width="1550" height="838" extended-state="7" />
+    <layout>
+      <window_info id="npm" side_tool="true" />
+      <window_info id="Favorites" side_tool="true" />
+      <window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.24966975" />
+      <window_info id="Structure" order="1" side_tool="true" weight="0.25" />
+      <window_info anchor="bottom" id="Docker" show_stripe_button="false" />
+      <window_info anchor="bottom" id="Version Control" />
+      <window_info anchor="bottom" id="RNConsole" />
+      <window_info active="true" anchor="bottom" id="Terminal" visible="true" weight="0.32956153" />
+      <window_info anchor="bottom" id="Event Log" side_tool="true" />
+      <window_info anchor="bottom" id="Message" order="0" />
+      <window_info anchor="bottom" id="Find" order="1" />
+      <window_info anchor="bottom" id="Run" order="2" />
+      <window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
+      <window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
+      <window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
+      <window_info anchor="bottom" id="TODO" order="6" />
+      <window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
+      <window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
+      <window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
+    </layout>
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="1" />
+  </component>
+  <component name="editorHistoryManager">
+    <entry file="file://$PROJECT_DIR$/src/routes/config.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="-684" />
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/Page.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state>
+          <folding>
+            <element signature="e#0#26#0" expanded="true" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+  </component>
+</project>

+ 7 - 0
.prettierrc

@@ -0,0 +1,7 @@
+{
+    "trailingComma": "es5",
+    "tabWidth": 4,
+    "singleQuote": true,
+    "jsxBracketSameLine": false,
+    "printWidth": 100
+}

+ 185 - 0
README.md

@@ -0,0 +1,185 @@
+# react-admin([尝试一下](https://codesandbox.io/s/react-admin-u9kdb))
+react-admin system solution
+
+<img src="https://raw.githubusercontent.com/yezihaohao/react-admin/master/screenshots/logo.png" alt="logo" width="150" height="53" />
+
+![travis-ci](https://travis-ci.org/yezihaohao/react-admin.svg?branch=master)
+[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
+
+### 文档地址:[wiki](https://github.com/yezihaohao/react-admin/wiki)
+
+### 问题和方案汇总:[issue](https://github.com/yezihaohao/react-admin/issues/12)
+
+### 更新日志迁移至[CHANGELOG.md](https://github.com/yezihaohao/react-admin/blob/master/CHANGELOG.md)😁(重要!对于了解项目部分功能和代码很有用!)
+
+### 前言
+> 网上react后台管理开源免费的完整版项目比较少,所以利用空余时间集成了一个版本出来,已放到GitHub
+  启动和打包的时间都稍长,请耐心等待两分钟
+
+- [GitHub地址](https://github.com/yezihaohao/react-admin)
+- [预览地址](https://admiring-dijkstra-34cb29.netlify.com)(已增加响应式,可手机预览😄)
+
+### 依赖模块
+<span style="color: rgb(184,49,47);">项目是用create-react-app创建的,主要还是列出新加的功能依赖包</span>
+
+<span style="color: rgb(184,49,47);">点击名称可跳转相关网站😄😄</span>
+
+- [react](https://facebook.github.io/react/)
+- [react-router](https://react-guide.github.io/react-router-cn/)(<span style="color: rgb(243,121,52);">react路由,4.x的版本,如果还使用3.x的版本,请切换分支(ps:分支不再维护)</span>)
+- [redux](https://redux.js.org/)(基础用法,但是封装了通用action和reducer,demo中主要用于权限控制(ps:目前可以用16.x的context api代替),可以简单了解下)
+- [antd](https://ant.design/index-cn)(<span style="color: rgb(243,121,52);">蚂蚁金服开源的react ui组件框架</span>)
+- [axios](https://github.com/mzabriskie/axios)(<span style="color: rgb(243,121,52);">http请求模块,可用于前端任何场景,很强大👍</span>)
+- [echarts-for-react](https://github.com/hustcc/echarts-for-react)(<span style="color: rgb(243,121,52);">可视化图表,别人基于react对echarts的封装,足够用了</span>)
+- [recharts](http://recharts.org/#/zh-CN/)(<span style="color: rgb(243,121,52);">另一个基于react封装的图表,个人觉得是没有echarts好用</span>)
+- [nprogress](https://github.com/rstacruz/nprogress)(<span style="color: rgb(243,121,52);">顶部加载条,蛮好用👍</span>)
+- [react-draft-wysiwyg](https://github.com/jpuri/react-draft-wysiwyg)(<span style="color: rgb(243,121,52);">别人基于react的富文本封装,如果找到其他更好的可以替换</span>)
+- [react-draggable](https://github.com/mzabriskie/react-draggable)(<span style="color: rgb(243,121,52);">拖拽模块,找了个简单版的</span>)
+- [screenfull](https://github.com/sindresorhus/screenfull.js/)(<span style="color: rgb(243,121,52);">全屏插件</span>)
+- [photoswipe](https://github.com/dimsemenov/photoswipe)(<span style="color: rgb(243,121,52);">图片弹层查看插件,不依赖jQuery,还是蛮好用👍</span>)
+- [animate.css](http://daneden.me/animate)(<span style="color: rgb(243,121,52);">css动画库</span>)
+- [react-loadable](https://github.com/jamiebuilds/react-loadable)(代码拆分,按需加载,预加载,样样都行,具体见其文档,推荐使用)
+- [redux-alita](https://github.com/yezihaohao/redux-alita) 极简的redux2react工具
+- 其他小细节省略
+
+### 功能模块
+<span style="color: rgb(184,49,47);">备注:项目只引入了ant-design的部分组件,其他的组件antd官网有源码,可以直接复制到项目中使用,后续有时间补上全部组件。</span>
+
+<span style="color: rgb(184,49,47);">项目使用了antd的自定义主题功能-->黑色,若想替换其他颜色,具体操作请查看antd官网</span>
+<!--more-->
+
+- 首页
+    - 完整布局
+    - 换肤(全局功能,暂时只实现了顶部导航的换肤,后续加上其他模块)
+- 导航菜单
+    - 顶部导航(菜单伸缩,全屏功能)
+    - 左边菜单(增加滚动条以及适配路由的active操作)
+- UI模块
+    - 按钮(antd组件)
+    - 图标(antd组件并增加彩色表情符)
+    - 加载中(antd组件并增加顶部加载条)
+    - 通知提醒框(antd组件)
+    - 标签页(antd组件)
+    - 轮播图(ant动效组件)
+    - 富文本
+    - 拖拽
+    - 画廊
+- 动画
+    - 基础动画(animate.css所有动画)
+    - 动画案例
+- 表格
+    - 基础表格(antd组件)
+    - 高级表格(antd组件)
+    - 异步表格(数据来自掘金酱的接口)
+- 表单
+    - 基础表单(antd组件)
+- 图表
+    - echarts图表
+    - recharts图表
+- 页面
+    - 登录页面(包括GitHub第三方登录)
+    - 404页面
+
+### 功能截图
+#### 首页
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd1.gif)
+#### 按钮图标等
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd2.gif)
+#### 轮播图
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd3.gif)
+#### 富文本
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd4.gif)
+#### 拖拽
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd5.gif)
+#### 画廊
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd6.gif)
+#### 动画
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd7.gif)
+#### 表格
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd8.gif)
+#### 表单
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd9.gif)
+#### 图表
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd10.gif)
+#### 页面
+![截图](https://raw.githubusercontent.com/yezihaohao/yezihaohao.github.io/master/imgs/rd11.gif)
+#### 菜单拖拽
+![截图](https://raw.githubusercontent.com/yezihaohao/react-admin/master/screenshots/menu_draggable.gif)
+
+### 代码目录
+```js
++-- build/                                  ---打包的文件目录
++-- config/                                 ---npm run eject 后的配置文件目录
++-- node_modules/                           ---npm下载文件目录
++-- public/
+|   --- index.html							---首页入口html文件
+|   --- npm.json							---echarts测试数据
+|   --- weibo.json							---echarts测试数据
++-- src/                                    ---核心代码目录
+|   +-- axios                               ---http请求存放目录
+|   |    --- index.js
+|   +-- components                          ---各式各样的组件存放目录
+|   |    +-- animation                      ---动画组件
+|   |    |    --- ...
+|   |    +-- charts                         ---图表组件
+|   |    |    --- ...
+|   |    +-- dashboard                      ---首页组件
+|   |    |    --- ...
+|   |    +-- forms                          ---表单组件
+|   |    |    --- ...
+|   |    +-- pages                          ---页面组件
+|   |    |    --- ...
+|   |    +-- tables                         ---表格组件
+|   |    |    --- ...
+|   |    +-- ui                             ---ui组件
+|   |    |    --- ...
+|   |    --- BreadcrumbCustom.jsx           ---面包屑组件
+|   |    --- HeaderCustom.jsx               ---顶部导航组件
+|   |    --- Page.jsx                       ---页面容器
+|   |    --- SiderCustom.jsx                ---左边菜单组件
+|   +-- style                               ---项目的样式存放目录,主要采用less编写
+|   +-- utils                               ---工具文件存放目录
+|   --- App.js                              ---组件入口文件
+|   --- index.js                            ---项目的整体js入口文件,包括路由配置等
+--- .env                                    ---启动项目自定义端口配置文件
+--- .eslintrc                               ---自定义eslint配置文件,包括增加的react jsx语法限制
+--- package.json
+```
+### 安装运行
+##### 1.下载或克隆项目源码
+##### 2.yarn 或者 npm安装相关包文件(首先推荐使用yarn,国内建议增加淘宝镜像源,不然很慢,你懂的😁)
+> 有些老铁遇到运行时报错,首先确定下是不是最新稳定版的nodejs和npm或者yarn(推荐用yarn),切记不要用cnpn
+
+```js
+// 首推荐使用yarn装包
+yarn or npm i
+```
+##### 3.启动项目
+```js
+yarn start or npm start
+```
+##### 4.打包项目
+```js
+yarn build or npm run build
+```
+
+### Q&A(点击问题查看答案)
+#### 1.[create-react-app 打包项目run build 增加进度条信息?](https://github.com/yezihaohao/react-admin/issues/12#issuecomment-325383346)
+#### 2.[接口跨域了,怎么在本地开发时配置代理?](https://github.com/yezihaohao/react-admin/issues/12#issuecomment-326169055)
+#### 3.[在使用hashRouter的情况下怎么实现类似锚点跳转?](https://github.com/yezihaohao/react-admin/issues/12#issuecomment-345127221)
+#### 4.[怎么添加多页面配置?](https://github.com/yezihaohao/react-admin/issues/12#issuecomment-348088852)
+#### 5.[路由传参数接问号怎么传?](https://github.com/yezihaohao/react-admin/issues/12#issuecomment-375583089)
+#### 6.[如何兼容IE浏览器?](https://github.com/yezihaohao/react-admin/issues/12#issuecomment-510295292)
+
+### 结尾
+该项目会不定时更新,后续时间会添加更多的模块
+
+欢迎和感谢大家PR~~👏👏
+
+若有问题,可加QQ群与我交流
+
+- 1群:264591039(已满)
+- 2群:592688854(已满)
+- 3群:743490497 (已满)
+- 4群:150131600
+
+如果对你有帮助,给个star哟~~❤️❤️❤️❤️

+ 93 - 0
config/env.js

@@ -0,0 +1,93 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const paths = require('./paths');
+
+// Make sure that including paths.js after env.js will read .env variables.
+delete require.cache[require.resolve('./paths')];
+
+const NODE_ENV = process.env.NODE_ENV;
+if (!NODE_ENV) {
+  throw new Error(
+    'The NODE_ENV environment variable is required but was not specified.'
+  );
+}
+
+// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
+var dotenvFiles = [
+  `${paths.dotenv}.${NODE_ENV}.local`,
+  `${paths.dotenv}.${NODE_ENV}`,
+  // Don't include `.env.local` for `test` environment
+  // since normally you expect tests to produce the same
+  // results for everyone
+  NODE_ENV !== 'test' && `${paths.dotenv}.local`,
+  paths.dotenv,
+].filter(Boolean);
+
+// Load environment variables from .env* files. Suppress warnings using silent
+// if this file is missing. dotenv will never modify any environment variables
+// that have already been set.  Variable expansion is supported in .env files.
+// https://github.com/motdotla/dotenv
+// https://github.com/motdotla/dotenv-expand
+dotenvFiles.forEach(dotenvFile => {
+  if (fs.existsSync(dotenvFile)) {
+    require('dotenv-expand')(
+      require('dotenv').config({
+        path: dotenvFile,
+      })
+    );
+  }
+});
+
+// We support resolving modules according to `NODE_PATH`.
+// This lets you use absolute paths in imports inside large monorepos:
+// https://github.com/facebook/create-react-app/issues/253.
+// It works similar to `NODE_PATH` in Node itself:
+// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
+// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
+// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
+// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
+// We also resolve them to make sure all tools using them work consistently.
+const appDirectory = fs.realpathSync(process.cwd());
+process.env.NODE_PATH = (process.env.NODE_PATH || '')
+  .split(path.delimiter)
+  .filter(folder => folder && !path.isAbsolute(folder))
+  .map(folder => path.resolve(appDirectory, folder))
+  .join(path.delimiter);
+
+// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
+// injected into the application via DefinePlugin in Webpack configuration.
+const REACT_APP = /^REACT_APP_/i;
+
+function getClientEnvironment(publicUrl) {
+  const raw = Object.keys(process.env)
+    .filter(key => REACT_APP.test(key))
+    .reduce(
+      (env, key) => {
+        env[key] = process.env[key];
+        return env;
+      },
+      {
+        // Useful for determining whether we’re running in production mode.
+        // Most importantly, it switches React into the correct mode.
+        NODE_ENV: process.env.NODE_ENV || 'development',
+        // Useful for resolving the correct path to static assets in `public`.
+        // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
+        // This should only be used as an escape hatch. Normally you would put
+        // images into the `src` and `import` them in code to get their paths.
+        PUBLIC_URL: publicUrl,
+      }
+    );
+  // Stringify all values so we can feed into Webpack DefinePlugin
+  const stringified = {
+    'process.env': Object.keys(raw).reduce((env, key) => {
+      env[key] = JSON.stringify(raw[key]);
+      return env;
+    }, {}),
+  };
+
+  return { raw, stringified };
+}
+
+module.exports = getClientEnvironment;

+ 14 - 0
config/jest/cssTransform.js

@@ -0,0 +1,14 @@
+'use strict';
+
+// This is a custom Jest transformer turning style imports into empty objects.
+// http://facebook.github.io/jest/docs/en/webpack.html
+
+module.exports = {
+  process() {
+    return 'module.exports = {};';
+  },
+  getCacheKey() {
+    // The output is always the same.
+    return 'cssTransform';
+  },
+};

+ 30 - 0
config/jest/fileTransform.js

@@ -0,0 +1,30 @@
+'use strict';
+
+const path = require('path');
+
+// This is a custom Jest transformer turning file imports into filenames.
+// http://facebook.github.io/jest/docs/en/webpack.html
+
+module.exports = {
+  process(src, filename) {
+    const assetFilename = JSON.stringify(path.basename(filename));
+
+    if (filename.match(/\.svg$/)) {
+      return `module.exports = {
+        __esModule: true,
+        default: ${assetFilename},
+        ReactComponent: (props) => ({
+          $$typeof: Symbol.for('react.element'),
+          type: 'svg',
+          ref: null,
+          key: null,
+          props: Object.assign({}, props, {
+            children: ${assetFilename}
+          })
+        }),
+      };`;
+    }
+
+    return `module.exports = ${assetFilename};`;
+  },
+};

+ 57 - 0
config/paths.js

@@ -0,0 +1,57 @@
+'use strict';
+
+const path = require('path');
+const fs = require('fs');
+const url = require('url');
+
+// Make sure any symlinks in the project folder are resolved:
+// https://github.com/facebook/create-react-app/issues/637
+const appDirectory = fs.realpathSync(process.cwd());
+const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
+
+const envPublicUrl = process.env.PUBLIC_URL;
+
+function ensureSlash(inputPath, needsSlash) {
+  const hasSlash = inputPath.endsWith('/');
+  if (hasSlash && !needsSlash) {
+    return inputPath.substr(0, inputPath.length - 1);
+  } else if (!hasSlash && needsSlash) {
+    return `${inputPath}/`;
+  } else {
+    return inputPath;
+  }
+}
+
+const getPublicUrl = appPackageJson =>
+  envPublicUrl || require(appPackageJson).homepage;
+
+// We use `PUBLIC_URL` environment variable or "homepage" field to infer
+// "public path" at which the app is served.
+// Webpack needs to know it to put the right <script> hrefs into HTML even in
+// single-page apps that may serve index.html for nested URLs like /todos/42.
+// We can't use a relative path in HTML because we don't want to load something
+// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
+function getServedPath(appPackageJson) {
+  const publicUrl = getPublicUrl(appPackageJson);
+  const servedUrl =
+    envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
+  return ensureSlash(servedUrl, true);
+}
+
+// config after eject: we're in ./config/
+module.exports = {
+  dotenv: resolveApp('.env'),
+  appPath: resolveApp('.'),
+  appBuild: resolveApp('build'),
+  appPublic: resolveApp('public'),
+  appHtml: resolveApp('public/index.html'),
+  appIndexJs: resolveApp('src/index.js'),
+  appPackageJson: resolveApp('package.json'),
+  appSrc: resolveApp('src'),
+  yarnLockFile: resolveApp('yarn.lock'),
+  testsSetup: resolveApp('src/setupTests.js'),
+  proxySetup: resolveApp('src/setupProxy.js'),
+  appNodeModules: resolveApp('node_modules'),
+  publicUrl: getPublicUrl(resolveApp('package.json')),
+  servedPath: getServedPath(resolveApp('package.json')),
+};

+ 416 - 0
config/webpack.config.dev.js

@@ -0,0 +1,416 @@
+'use strict';
+
+const path = require('path');
+const webpack = require('webpack');
+const PnpWebpackPlugin = require('pnp-webpack-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
+const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
+const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
+const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
+const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
+const getClientEnvironment = require('./env');
+const paths = require('./paths');
+const ManifestPlugin = require('webpack-manifest-plugin');
+const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
+const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
+
+// Webpack uses `publicPath` to determine where the app is being served from.
+// In development, we always serve from the root. This makes config easier.
+const publicPath = '/';
+// `publicUrl` is just like `publicPath`, but we will provide it to our app
+// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
+// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
+const publicUrl = '';
+// Get environment variables to inject into our app.
+const env = getClientEnvironment(publicUrl);
+
+// style files regexes
+const cssRegex = /\.css$/;
+const cssModuleRegex = /\.module\.css$/;
+const sassRegex = /\.(scss|sass)$/;
+const sassModuleRegex = /\.module\.(scss|sass)$/;
+const lessRegex = /\.less$/;
+const lessModuleRegex = /\.module\.less/;
+
+// common function to get style loaders
+const getStyleLoaders = (cssOptions, preProcessor) => {
+	const loaders = [
+		require.resolve('style-loader'),
+		{
+			loader: require.resolve('css-loader'),
+			options: cssOptions,
+		},
+		{
+			// Options for PostCSS as we reference these options twice
+			// Adds vendor prefixing based on your specified browser support in
+			// package.json
+			loader: require.resolve('postcss-loader'),
+			options: {
+				// Necessary for external CSS imports to work
+				// https://github.com/facebook/create-react-app/issues/2677
+				ident: 'postcss',
+				plugins: () => [
+					require('postcss-flexbugs-fixes'),
+					require('postcss-preset-env')({
+						autoprefixer: {
+							flexbox: 'no-2009',
+						},
+						stage: 3,
+					}),
+				],
+			},
+		},
+	];
+	if (preProcessor) {
+		loaders.push(require.resolve(preProcessor));
+	}
+	return loaders;
+};
+
+// This is the development configuration.
+// It is focused on developer experience and fast rebuilds.
+// The production configuration is different and lives in a separate file.
+module.exports = {
+	mode: 'development',
+	// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
+	// See the discussion in https://github.com/facebook/create-react-app/issues/343
+	devtool: 'cheap-module-source-map',
+	// These are the "entry points" to our application.
+	// This means they will be the "root" imports that are included in JS bundle.
+	entry: [
+		// Include an alternative client for WebpackDevServer. A client's job is to
+		// connect to WebpackDevServer by a socket and get notified about changes.
+		// When you save a file, the client will either apply hot updates (in case
+		// of CSS changes), or refresh the page (in case of JS changes). When you
+		// make a syntax error, this client will display a syntax error overlay.
+		// Note: instead of the default WebpackDevServer client, we use a custom one
+		// to bring better experience for Create React App users. You can replace
+		// the line below with these two lines if you prefer the stock client:
+		// require.resolve('webpack-dev-server/client') + '?/',
+		// require.resolve('webpack/hot/dev-server'),
+		require.resolve('react-dev-utils/webpackHotDevClient'),
+		// Finally, this is your app's code:
+		paths.appIndexJs,
+		// We include the app code last so that if there is a runtime error during
+		// initialization, it doesn't blow up the WebpackDevServer client, and
+		// changing JS code would still trigger a refresh.
+	],
+	output: {
+		// Add /* filename */ comments to generated require()s in the output.
+		pathinfo: true,
+		// This does not produce a real file. It's just the virtual path that is
+		// served by WebpackDevServer in development. This is the JS bundle
+		// containing code from all our entry points, and the Webpack runtime.
+		filename: 'static/js/bundle.js',
+		// There are also additional JS chunk files if you use code splitting.
+		chunkFilename: 'static/js/[name].chunk.js',
+		// This is the URL that app is served from. We use "/" in development.
+		publicPath: publicPath,
+		// Point sourcemap entries to original disk location (format as URL on Windows)
+		devtoolModuleFilenameTemplate: info =>
+			path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
+	},
+	optimization: {
+		// Automatically split vendor and commons
+		// https://twitter.com/wSokra/status/969633336732905474
+		// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
+		splitChunks: {
+			chunks: 'all',
+			name: false,
+		},
+		// Keep the runtime chunk seperated to enable long term caching
+		// https://twitter.com/wSokra/status/969679223278505985
+		runtimeChunk: true,
+	},
+	resolve: {
+		// This allows you to set a fallback for where Webpack should look for modules.
+		// We placed these paths second because we want `node_modules` to "win"
+		// if there are any conflicts. This matches Node resolution mechanism.
+		// https://github.com/facebook/create-react-app/issues/253
+		modules: ['node_modules'].concat(
+			// It is guaranteed to exist because we tweak it in `env.js`
+			process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
+		),
+		// These are the reasonable defaults supported by the Node ecosystem.
+		// We also include JSX as a common component filename extension to support
+		// some tools, although we do not recommend using it, see:
+		// https://github.com/facebook/create-react-app/issues/290
+		// `web` extension prefixes have been added for better support
+		// for React Native Web.
+		extensions: ['.mjs', '.web.js', '.js', '.json', '.web.jsx', '.jsx'],
+		alias: {
+			// Support React Native Web
+			// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
+			'react-native': 'react-native-web',
+			// 全局相对路径别名,处理相对路径过长和繁琐问题
+			'@': paths.appSrc
+		},
+		plugins: [
+			// Adds support for installing with Plug'n'Play, leading to faster installs and adding
+			// guards against forgotten dependencies and such.
+			PnpWebpackPlugin,
+			// Prevents users from importing files from outside of src/ (or node_modules/).
+			// This often causes confusion because we only process files within src/ with babel.
+			// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
+			// please link the files into your node_modules/ and let module-resolution kick in.
+			// Make sure your source files are compiled, as they will not be processed in any way.
+			new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
+		],
+	},
+	resolveLoader: {
+		plugins: [
+			// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
+			// from the current package.
+			PnpWebpackPlugin.moduleLoader(module),
+		],
+	},
+	module: {
+		strictExportPresence: true,
+		rules: [
+			// Disable require.ensure as it's not a standard language feature.
+			{ parser: { requireEnsure: false } },
+
+			// First, run the linter.
+			// It's important to do this before Babel processes the JS.
+			{
+				test: /\.(js|mjs|jsx)$/,
+				enforce: 'pre',
+				use: [
+					{
+						options: {
+							formatter: require.resolve('react-dev-utils/eslintFormatter'),
+							eslintPath: require.resolve('eslint'),
+
+						},
+						loader: require.resolve('eslint-loader'),
+					},
+				],
+				include: paths.appSrc,
+			},
+			{
+				// "oneOf" will traverse all following loaders until one will
+				// match the requirements. When no loader matches it will fall
+				// back to the "file" loader at the end of the loader list.
+				oneOf: [
+					// "url" loader works like "file" loader except that it embeds assets
+					// smaller than specified limit in bytes as data URLs to avoid requests.
+					// A missing `test` is equivalent to a match.
+					{
+						test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
+						loader: require.resolve('url-loader'),
+						options: {
+							limit: 10000,
+							name: 'static/media/[name].[hash:8].[ext]',
+						},
+					},
+					// Process application JS with Babel.
+					// The preset includes JSX, Flow, and some ESnext features.
+					{
+						test: /\.(js|mjs|jsx)$/,
+						include: paths.appSrc,
+						loader: require.resolve('babel-loader'),
+						options: {
+							customize: require.resolve(
+								'babel-preset-react-app/webpack-overrides'
+							),
+
+							plugins: [
+								[
+									require.resolve('babel-plugin-named-asset-import'),
+									{
+										loaderMap: {
+											svg: {
+												ReactComponent: '@svgr/webpack?-prettier,-svgo![path]',
+											},
+										},
+									},
+								],
+								['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }],
+							],
+							// This is a feature of `babel-loader` for webpack (not Babel itself).
+							// It enables caching results in ./node_modules/.cache/babel-loader/
+							// directory for faster rebuilds.
+							cacheDirectory: true,
+							// Don't waste time on Gzipping the cache
+							cacheCompression: false,
+						},
+					},
+					// Process any JS outside of the app with Babel.
+					// Unlike the application JS, we only compile the standard ES features.
+					{
+						test: /\.(js|mjs)$/,
+						exclude: /@babel(?:\/|\\{1,2})runtime/,
+						loader: require.resolve('babel-loader'),
+						options: {
+							babelrc: false,
+							configFile: false,
+							compact: false,
+							presets: [
+								[
+									require.resolve('babel-preset-react-app/dependencies'),
+									{ helpers: true },
+								],
+							],
+							cacheDirectory: true,
+							// Don't waste time on Gzipping the cache
+							cacheCompression: false,
+
+							// If an error happens in a package, it's possible to be
+							// because it was compiled. Thus, we don't want the browser
+							// debugger to show the original code. Instead, the code
+							// being evaluated would be much more helpful.
+							sourceMaps: false,
+						},
+					},
+					// "postcss" loader applies autoprefixer to our CSS.
+					// "css" loader resolves paths in CSS and adds assets as dependencies.
+					// "style" loader turns CSS into JS modules that inject <style> tags.
+					// In production, we use a plugin to extract that CSS to a file, but
+					// in development "style" loader enables hot editing of CSS.
+					// By default we support CSS Modules with the extension .module.css
+					{
+						test: cssRegex,
+						exclude: cssModuleRegex,
+						use: getStyleLoaders({
+							importLoaders: 1,
+						}),
+					},
+					// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
+					// using the extension .module.css
+					{
+						test: cssModuleRegex,
+						use: getStyleLoaders({
+							importLoaders: 1,
+							modules: true,
+							getLocalIdent: getCSSModuleLocalIdent,
+						}),
+					},
+					// Opt-in support for SASS (using .scss or .sass extensions).
+					// Chains the sass-loader with the css-loader and the style-loader
+					// to immediately apply all styles to the DOM.
+					// By default we support SASS Modules with the
+					// extensions .module.scss or .module.sass
+					{
+						test: sassRegex,
+						exclude: sassModuleRegex,
+						use: getStyleLoaders({ importLoaders: 2 }, 'sass-loader'),
+					},
+					// Adds support for CSS Modules, but using SASS
+					// using the extension .module.scss or .module.sass
+					{
+						test: sassModuleRegex,
+						use: getStyleLoaders(
+							{
+								importLoaders: 2,
+								modules: true,
+								getLocalIdent: getCSSModuleLocalIdent,
+							},
+							'sass-loader'
+						),
+					},
+					// Opt-in support for LESS (using .less extensions).
+					{
+						test: lessRegex,
+						exclude: lessModuleRegex,
+						use: [
+							...getStyleLoaders({ importLoaders: 2 }, 'less-loader'),
+							// {
+							// 	loader: require.resolve('less-loader'),
+							// 		options: {
+							// 		  	modifyVars: { "@primary-color": "#001529" },
+							// 		},
+							// }
+						],
+					},
+					// Adds support for CSS Modules, but using LESS
+					// using the extension .module.scss or .module.sass
+					{
+						test: lessModuleRegex,
+						use: getStyleLoaders(
+							{
+								importLoaders: 2,
+								modules: true,
+								getLocalIdent: getCSSModuleLocalIdent,
+							},
+							'less-loader'
+						),
+					},
+					// "file" loader makes sure those assets get served by WebpackDevServer.
+					// When you `import` an asset, you get its (virtual) filename.
+					// In production, they would get copied to the `build` folder.
+					// This loader doesn't use a "test" so it will catch all modules
+					// that fall through the other loaders.
+					{
+						// Exclude `js` files to keep "css" loader working as it injects
+						// its runtime that would otherwise be processed through "file" loader.
+						// Also exclude `html` and `json` extensions so they get processed
+						// by webpacks internal loaders.
+						exclude: [/\.(js|mjs|jsx)$/, /\.html$/, /\.json$/],
+						loader: require.resolve('file-loader'),
+						options: {
+							name: 'static/media/[name].[hash:8].[ext]',
+						},
+					},
+				],
+			},
+			// ** STOP ** Are you adding a new loader?
+			// Make sure to add the new loader(s) before the "file" loader.
+		],
+	},
+	plugins: [
+		// Generates an `index.html` file with the <script> injected.
+		new HtmlWebpackPlugin({
+			inject: true,
+			template: paths.appHtml,
+		}),
+		// Makes some environment variables available in index.html.
+		// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
+		// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
+		// In development, this will be an empty string.
+		new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
+		// This gives some necessary context to module not found errors, such as
+		// the requesting resource.
+		new ModuleNotFoundPlugin(paths.appPath),
+		// Makes some environment variables available to the JS code, for example:
+		// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
+		new webpack.DefinePlugin(env.stringified),
+		// This is necessary to emit hot updates (currently CSS only):
+		new webpack.HotModuleReplacementPlugin(),
+		// Watcher doesn't work well if you mistype casing in a path so we use
+		// a plugin that prints an error when you attempt to do this.
+		// See https://github.com/facebook/create-react-app/issues/240
+		new CaseSensitivePathsPlugin(),
+		// If you require a missing module and then `npm install` it, you still have
+		// to restart the development server for Webpack to discover it. This plugin
+		// makes the discovery automatic so you don't have to restart.
+		// See https://github.com/facebook/create-react-app/issues/186
+		new WatchMissingNodeModulesPlugin(paths.appNodeModules),
+		// Moment.js is an extremely popular library that bundles large locale files
+		// by default due to how Webpack interprets its code. This is a practical
+		// solution that requires the user to opt into importing specific locales.
+		// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
+		// You can remove this if you don't use Moment.js:
+		new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
+		// Generate a manifest file which contains a mapping of all asset filenames
+		// to their corresponding output file so that tools can pick it up without
+		// having to parse `index.html`.
+		new ManifestPlugin({
+			fileName: 'asset-manifest.json',
+			publicPath: publicPath,
+		}),
+	],
+
+	// Some libraries import Node modules but don't use them in the browser.
+	// Tell Webpack to provide empty mocks for them so importing them works.
+	node: {
+		dgram: 'empty',
+		fs: 'empty',
+		net: 'empty',
+		tls: 'empty',
+		child_process: 'empty',
+	},
+	// Turn off performance processing because we utilize
+	// our own hints via the FileSizeReporter
+	performance: false,
+};

+ 525 - 0
config/webpack.config.prod.js

@@ -0,0 +1,525 @@
+'use strict';
+
+const path = require('path');
+const webpack = require('webpack');
+const PnpWebpackPlugin = require('pnp-webpack-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
+const TerserPlugin = require('terser-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
+const safePostCssParser = require('postcss-safe-parser');
+const ManifestPlugin = require('webpack-manifest-plugin');
+const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
+const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
+const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
+const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
+const paths = require('./paths');
+const getClientEnvironment = require('./env');
+const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
+const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
+
+// Webpack uses `publicPath` to determine where the app is being served from.
+// It requires a trailing slash, or the file assets will get an incorrect path.
+const publicPath = paths.servedPath;
+// Some apps do not use client-side routing with pushState.
+// For these, "homepage" can be set to "." to enable relative asset paths.
+const shouldUseRelativeAssetPaths = publicPath === './';
+// Source maps are resource heavy and can cause out of memory issue for large source files.
+const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
+// `publicUrl` is just like `publicPath`, but we will provide it to our app
+// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
+// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
+const publicUrl = publicPath.slice(0, -1);
+// Get environment variables to inject into our app.
+const env = getClientEnvironment(publicUrl);
+
+// Assert this just to be safe.
+// Development builds of React are slow and not intended for production.
+if (env.stringified['process.env'].NODE_ENV !== '"production"') {
+	throw new Error('Production builds must have NODE_ENV=production.');
+}
+
+// style files regexes
+const cssRegex = /\.css$/;
+const cssModuleRegex = /\.module\.css$/;
+const sassRegex = /\.(scss|sass)$/;
+const sassModuleRegex = /\.module\.(scss|sass)$/;
+const lessRegex = /\.less$/;
+const lessModuleRegex = /\.module\.less/;
+
+// common function to get style loaders
+const getStyleLoaders = (cssOptions, preProcessor) => {
+	const loaders = [
+		{
+			loader: MiniCssExtractPlugin.loader,
+			options: Object.assign(
+				{},
+				shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
+			),
+		},
+		{
+			loader: require.resolve('css-loader'),
+			options: cssOptions,
+		},
+		{
+			// Options for PostCSS as we reference these options twice
+			// Adds vendor prefixing based on your specified browser support in
+			// package.json
+			loader: require.resolve('postcss-loader'),
+			options: {
+				// Necessary for external CSS imports to work
+				// https://github.com/facebook/create-react-app/issues/2677
+				ident: 'postcss',
+				plugins: () => [
+					require('postcss-flexbugs-fixes'),
+					require('postcss-preset-env')({
+						autoprefixer: {
+							flexbox: 'no-2009',
+						},
+						stage: 3,
+					}),
+				],
+				sourceMap: shouldUseSourceMap,
+			},
+		},
+	];
+	if (preProcessor) {
+		loaders.push({
+			loader: require.resolve(preProcessor),
+			options: {
+				sourceMap: shouldUseSourceMap,
+			},
+		});
+	}
+	return loaders;
+};
+
+// This is the production configuration.
+// It compiles slowly and is focused on producing a fast and minimal bundle.
+// The development configuration is different and lives in a separate file.
+module.exports = {
+	mode: 'production',
+	// Don't attempt to continue if there are any errors.
+	bail: true,
+	// We generate sourcemaps in production. This is slow but gives good results.
+	// You can exclude the *.map files from the build during deployment.
+	devtool: shouldUseSourceMap ? 'source-map' : false,
+	// In production, we only want to load the app code.
+	entry: [paths.appIndexJs],
+	output: {
+		// The build folder.
+		path: paths.appBuild,
+		// Generated JS file names (with nested folders).
+		// There will be one main bundle, and one file per asynchronous chunk.
+		// We don't currently advertise code splitting but Webpack supports it.
+		filename: 'static/js/[name].[chunkhash:8].js',
+		chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
+		// We inferred the "public path" (such as / or /my-project) from homepage.
+		publicPath: publicPath,
+		// Point sourcemap entries to original disk location (format as URL on Windows)
+		devtoolModuleFilenameTemplate: info =>
+			path
+				.relative(paths.appSrc, info.absoluteResourcePath)
+				.replace(/\\/g, '/'),
+	},
+	optimization: {
+		minimizer: [
+			new TerserPlugin({
+				terserOptions: {
+					parse: {
+						// we want terser to parse ecma 8 code. However, we don't want it
+						// to apply any minfication steps that turns valid ecma 5 code
+						// into invalid ecma 5 code. This is why the 'compress' and 'output'
+						// sections only apply transformations that are ecma 5 safe
+						// https://github.com/facebook/create-react-app/pull/4234
+						ecma: 8,
+					},
+					compress: {
+						ecma: 5,
+						warnings: false,
+						// Disabled because of an issue with Uglify breaking seemingly valid code:
+						// https://github.com/facebook/create-react-app/issues/2376
+						// Pending further investigation:
+						// https://github.com/mishoo/UglifyJS2/issues/2011
+						comparisons: false,
+						// Disabled because of an issue with Terser breaking valid code:
+						// https://github.com/facebook/create-react-app/issues/5250
+						// Pending futher investigation:
+						// https://github.com/terser-js/terser/issues/120
+						inline: 2,
+					},
+					mangle: {
+						safari10: true,
+					},
+					output: {
+						ecma: 5,
+						comments: false,
+						// Turned on because emoji and regex is not minified properly using default
+						// https://github.com/facebook/create-react-app/issues/2488
+						ascii_only: true,
+					},
+				},
+				// Use multi-process parallel running to improve the build speed
+				// Default number of concurrent runs: os.cpus().length - 1
+				parallel: true,
+				// Enable file caching
+				cache: true,
+				sourceMap: shouldUseSourceMap,
+			}),
+			new OptimizeCSSAssetsPlugin({
+				cssProcessorOptions: {
+					parser: safePostCssParser,
+					map: shouldUseSourceMap
+						? {
+							// `inline: false` forces the sourcemap to be output into a
+							// separate file
+							inline: false,
+							// `annotation: true` appends the sourceMappingURL to the end of
+							// the css file, helping the browser find the sourcemap
+							annotation: true,
+						}
+						: false,
+				},
+			}),
+		],
+		// Automatically split vendor and commons
+		// https://twitter.com/wSokra/status/969633336732905474
+		// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
+		splitChunks: {
+			chunks: 'all',
+			name: false,
+		},
+		// Keep the runtime chunk seperated to enable long term caching
+		// https://twitter.com/wSokra/status/969679223278505985
+		runtimeChunk: true,
+	},
+	resolve: {
+		// This allows you to set a fallback for where Webpack should look for modules.
+		// We placed these paths second because we want `node_modules` to "win"
+		// if there are any conflicts. This matches Node resolution mechanism.
+		// https://github.com/facebook/create-react-app/issues/253
+		modules: ['node_modules'].concat(
+			// It is guaranteed to exist because we tweak it in `env.js`
+			process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
+		),
+		// These are the reasonable defaults supported by the Node ecosystem.
+		// We also include JSX as a common component filename extension to support
+		// some tools, although we do not recommend using it, see:
+		// https://github.com/facebook/create-react-app/issues/290
+		// `web` extension prefixes have been added for better support
+		// for React Native Web.
+		extensions: ['.mjs', '.web.js', '.js', '.json', '.web.jsx', '.jsx'],
+		alias: {
+			// Support React Native Web
+			// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
+			'react-native': 'react-native-web',
+			// 全局相对路径别名,处理相对路径过长和繁琐问题
+			'@': paths.appSrc
+		},
+		plugins: [
+			// Adds support for installing with Plug'n'Play, leading to faster installs and adding
+			// guards against forgotten dependencies and such.
+			PnpWebpackPlugin,
+			// Prevents users from importing files from outside of src/ (or node_modules/).
+			// This often causes confusion because we only process files within src/ with babel.
+			// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
+			// please link the files into your node_modules/ and let module-resolution kick in.
+			// Make sure your source files are compiled, as they will not be processed in any way.
+			new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
+		],
+	},
+	resolveLoader: {
+		plugins: [
+			// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
+			// from the current package.
+			PnpWebpackPlugin.moduleLoader(module),
+		],
+	},
+	module: {
+		strictExportPresence: true,
+		rules: [
+			// Disable require.ensure as it's not a standard language feature.
+			{ parser: { requireEnsure: false } },
+
+			// First, run the linter.
+			// It's important to do this before Babel processes the JS.
+			{
+				test: /\.(js|mjs|jsx)$/,
+				enforce: 'pre',
+				use: [
+					{
+						options: {
+							formatter: require.resolve('react-dev-utils/eslintFormatter'),
+							eslintPath: require.resolve('eslint'),
+
+						},
+						loader: require.resolve('eslint-loader'),
+					},
+				],
+				include: paths.appSrc,
+			},
+			{
+				// "oneOf" will traverse all following loaders until one will
+				// match the requirements. When no loader matches it will fall
+				// back to the "file" loader at the end of the loader list.
+				oneOf: [
+					// "url" loader works just like "file" loader but it also embeds
+					// assets smaller than specified size as data URLs to avoid requests.
+					{
+						test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
+						loader: require.resolve('url-loader'),
+						options: {
+							limit: 10000,
+							name: 'static/media/[name].[hash:8].[ext]',
+						},
+					},
+					// Process application JS with Babel.
+					// The preset includes JSX, Flow, and some ESnext features.
+					{
+						test: /\.(js|mjs|jsx)$/,
+						include: paths.appSrc,
+
+						loader: require.resolve('babel-loader'),
+						options: {
+							customize: require.resolve(
+								'babel-preset-react-app/webpack-overrides'
+							),
+
+							plugins: [
+								[
+									require.resolve('babel-plugin-named-asset-import'),
+									{
+										loaderMap: {
+											svg: {
+												ReactComponent: '@svgr/webpack?-prettier,-svgo![path]',
+											},
+										},
+									},
+								],
+								['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }],
+							],
+							cacheDirectory: true,
+							// Save disk space when time isn't as important
+							cacheCompression: true,
+							compact: true,
+						},
+					},
+					// Process any JS outside of the app with Babel.
+					// Unlike the application JS, we only compile the standard ES features.
+					{
+						test: /\.(js|mjs)$/,
+						exclude: /@babel(?:\/|\\{1,2})runtime/,
+						loader: require.resolve('babel-loader'),
+						options: {
+							babelrc: false,
+							configFile: false,
+							compact: false,
+							presets: [
+								[
+									require.resolve('babel-preset-react-app/dependencies'),
+									{ helpers: true },
+								],
+							],
+							cacheDirectory: true,
+							// Save disk space when time isn't as important
+							cacheCompression: true,
+
+							// If an error happens in a package, it's possible to be
+							// because it was compiled. Thus, we don't want the browser
+							// debugger to show the original code. Instead, the code
+							// being evaluated would be much more helpful.
+							sourceMaps: false,
+						},
+					},
+					// "postcss" loader applies autoprefixer to our CSS.
+					// "css" loader resolves paths in CSS and adds assets as dependencies.
+					// `MiniCSSExtractPlugin` extracts styles into CSS
+					// files. If you use code splitting, async bundles will have their own separate CSS chunk file.
+					// By default we support CSS Modules with the extension .module.css
+					{
+						test: cssRegex,
+						exclude: cssModuleRegex,
+						loader: getStyleLoaders({
+							importLoaders: 1,
+							sourceMap: shouldUseSourceMap,
+						}),
+						// Don't consider CSS imports dead code even if the
+						// containing package claims to have no side effects.
+						// Remove this when webpack adds a warning or an error for this.
+						// See https://github.com/webpack/webpack/issues/6571
+						sideEffects: true,
+					},
+					// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
+					// using the extension .module.css
+					{
+						test: cssModuleRegex,
+						loader: getStyleLoaders({
+							importLoaders: 1,
+							sourceMap: shouldUseSourceMap,
+							modules: true,
+							getLocalIdent: getCSSModuleLocalIdent,
+						}),
+					},
+					// Opt-in support for SASS. The logic here is somewhat similar
+					// as in the CSS routine, except that "sass-loader" runs first
+					// to compile SASS files into CSS.
+					// By default we support SASS Modules with the
+					// extensions .module.scss or .module.sass
+					{
+						test: sassRegex,
+						exclude: sassModuleRegex,
+						loader: getStyleLoaders(
+							{
+								importLoaders: 2,
+								sourceMap: shouldUseSourceMap,
+							},
+							'sass-loader'
+						),
+						// Don't consider CSS imports dead code even if the
+						// containing package claims to have no side effects.
+						// Remove this when webpack adds a warning or an error for this.
+						// See https://github.com/webpack/webpack/issues/6571
+						sideEffects: true,
+					},
+					// Adds support for CSS Modules, but using SASS
+					// using the extension .module.scss or .module.sass
+					{
+						test: sassModuleRegex,
+						loader: getStyleLoaders(
+							{
+								importLoaders: 2,
+								sourceMap: shouldUseSourceMap,
+								modules: true,
+								getLocalIdent: getCSSModuleLocalIdent,
+							},
+							'sass-loader'
+						),
+					},
+					// Opt-in support for LESS (using .less extensions).
+					{
+						test: lessRegex,
+						exclude: lessModuleRegex,
+						loader: getStyleLoaders({ 
+							importLoaders: 2,
+							sourceMap: shouldUseSourceMap,
+						}, 'less-loader'),
+					},
+					// Adds support for CSS Modules, but using LESS
+					// using the extension .module.scss or .module.sass
+					{
+						test: lessModuleRegex,
+						loader: getStyleLoaders(
+							{
+								importLoaders: 2,
+								sourceMap: shouldUseSourceMap,
+								modules: true,
+								getLocalIdent: getCSSModuleLocalIdent,
+							},
+							'less-loader'
+						),
+					},
+					// "file" loader makes sure assets end up in the `build` folder.
+					// When you `import` an asset, you get its filename.
+					// This loader doesn't use a "test" so it will catch all modules
+					// that fall through the other loaders.
+					{
+						loader: require.resolve('file-loader'),
+						// Exclude `js` files to keep "css" loader working as it injects
+						// it's runtime that would otherwise be processed through "file" loader.
+						// Also exclude `html` and `json` extensions so they get processed
+						// by webpacks internal loaders.
+						exclude: [/\.(js|mjs|jsx)$/, /\.html$/, /\.json$/],
+						options: {
+							name: 'static/media/[name].[hash:8].[ext]',
+						},
+					},
+					// ** STOP ** Are you adding a new loader?
+					// Make sure to add the new loader(s) before the "file" loader.
+				],
+			},
+		],
+	},
+	plugins: [
+		// Generates an `index.html` file with the <script> injected.
+		new HtmlWebpackPlugin({
+			inject: true,
+			template: paths.appHtml,
+			minify: {
+				removeComments: true,
+				collapseWhitespace: true,
+				removeRedundantAttributes: true,
+				useShortDoctype: true,
+				removeEmptyAttributes: true,
+				removeStyleLinkTypeAttributes: true,
+				keepClosingSlash: true,
+				minifyJS: true,
+				minifyCSS: true,
+				minifyURLs: true,
+			},
+		}),
+		// Inlines the webpack runtime script. This script is too small to warrant
+		// a network request.
+		new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
+		// Makes some environment variables available in index.html.
+		// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
+		// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
+		// In production, it will be an empty string unless you specify "homepage"
+		// in `package.json`, in which case it will be the pathname of that URL.
+		new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
+		// This gives some necessary context to module not found errors, such as
+		// the requesting resource.
+		new ModuleNotFoundPlugin(paths.appPath),
+		// Makes some environment variables available to the JS code, for example:
+		// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
+		// It is absolutely essential that NODE_ENV was set to production here.
+		// Otherwise React will be compiled in the very slow development mode.
+		new webpack.DefinePlugin(env.stringified),
+		new MiniCssExtractPlugin({
+			// Options similar to the same options in webpackOptions.output
+			// both options are optional
+			filename: 'static/css/[name].[contenthash:8].css',
+			chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
+		}),
+		// Generate a manifest file which contains a mapping of all asset filenames
+		// to their corresponding output file so that tools can pick it up without
+		// having to parse `index.html`.
+		new ManifestPlugin({
+			fileName: 'asset-manifest.json',
+			publicPath: publicPath,
+		}),
+		// Moment.js is an extremely popular library that bundles large locale files
+		// by default due to how Webpack interprets its code. This is a practical
+		// solution that requires the user to opt into importing specific locales.
+		// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
+		// You can remove this if you don't use Moment.js:
+		new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
+		// Generate a service worker script that will precache, and keep up to date,
+		// the HTML & assets that are part of the Webpack build.
+		new WorkboxWebpackPlugin.GenerateSW({
+			clientsClaim: true,
+			exclude: [/\.map$/, /asset-manifest\.json$/],
+			importWorkboxFrom: 'cdn',
+			navigateFallback: publicUrl + '/index.html',
+			navigateFallbackBlacklist: [
+				// Exclude URLs starting with /_, as they're likely an API call
+				new RegExp('^/_'),
+				// Exclude URLs containing a dot, as they're likely a resource in
+				// public/ and not a SPA route
+				new RegExp('/[^/]+\\.[^/]+$'),
+			],
+		}),
+	],
+	// Some libraries import Node modules but don't use them in the browser.
+	// Tell Webpack to provide empty mocks for them so importing them works.
+	node: {
+		dgram: 'empty',
+		fs: 'empty',
+		net: 'empty',
+		tls: 'empty',
+		child_process: 'empty',
+	},
+	// Turn off performance processing because we utilize
+	// our own hints via the FileSizeReporter
+	performance: false,
+};

+ 105 - 0
config/webpackDevServer.config.js

@@ -0,0 +1,105 @@
+'use strict';
+
+const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
+const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
+const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
+const ignoredFiles = require('react-dev-utils/ignoredFiles');
+const config = require('./webpack.config.dev');
+const paths = require('./paths');
+const fs = require('fs');
+
+const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
+const host = process.env.HOST || '0.0.0.0';
+
+module.exports = function(proxy, allowedHost) {
+  return {
+    // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
+    // websites from potentially accessing local content through DNS rebinding:
+    // https://github.com/webpack/webpack-dev-server/issues/887
+    // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
+    // However, it made several existing use cases such as development in cloud
+    // environment or subdomains in development significantly more complicated:
+    // https://github.com/facebook/create-react-app/issues/2271
+    // https://github.com/facebook/create-react-app/issues/2233
+    // While we're investigating better solutions, for now we will take a
+    // compromise. Since our WDS configuration only serves files in the `public`
+    // folder we won't consider accessing them a vulnerability. However, if you
+    // use the `proxy` feature, it gets more dangerous because it can expose
+    // remote code execution vulnerabilities in backends like Django and Rails.
+    // So we will disable the host check normally, but enable it if you have
+    // specified the `proxy` setting. Finally, we let you override it if you
+    // really know what you're doing with a special environment variable.
+    disableHostCheck:
+      !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
+    // Enable gzip compression of generated files.
+    compress: true,
+    // Silence WebpackDevServer's own logs since they're generally not useful.
+    // It will still show compile warnings and errors with this setting.
+    clientLogLevel: 'none',
+    // By default WebpackDevServer serves physical files from current directory
+    // in addition to all the virtual build products that it serves from memory.
+    // This is confusing because those files won’t automatically be available in
+    // production build folder unless we copy them. However, copying the whole
+    // project directory is dangerous because we may expose sensitive files.
+    // Instead, we establish a convention that only files in `public` directory
+    // get served. Our build script will copy `public` into the `build` folder.
+    // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
+    // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
+    // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
+    // Note that we only recommend to use `public` folder as an escape hatch
+    // for files like `favicon.ico`, `manifest.json`, and libraries that are
+    // for some reason broken when imported through Webpack. If you just want to
+    // use an image, put it in `src` and `import` it from JavaScript instead.
+    contentBase: paths.appPublic,
+    // By default files from `contentBase` will not trigger a page reload.
+    watchContentBase: true,
+    // Enable hot reloading server. It will provide /sockjs-node/ endpoint
+    // for the WebpackDevServer client so it can learn when the files were
+    // updated. The WebpackDevServer client is included as an entry point
+    // in the Webpack development configuration. Note that only changes
+    // to CSS are currently hot reloaded. JS changes will refresh the browser.
+    hot: true,
+    // It is important to tell WebpackDevServer to use the same "root" path
+    // as we specified in the config. In development, we always serve from /.
+    publicPath: config.output.publicPath,
+    // WebpackDevServer is noisy by default so we emit custom message instead
+    // by listening to the compiler events with `compiler.hooks[...].tap` calls above.
+    quiet: true,
+    // Reportedly, this avoids CPU overload on some systems.
+    // https://github.com/facebook/create-react-app/issues/293
+    // src/node_modules is not ignored to support absolute imports
+    // https://github.com/facebook/create-react-app/issues/1065
+    watchOptions: {
+      ignored: ignoredFiles(paths.appSrc),
+    },
+    // Enable HTTPS if the HTTPS environment variable is set to 'true'
+    https: protocol === 'https',
+    host,
+    overlay: false,
+    historyApiFallback: {
+      // Paths with dots should still use the history fallback.
+      // See https://github.com/facebook/create-react-app/issues/387.
+      disableDotRule: true,
+    },
+    public: allowedHost,
+    proxy,
+    before(app, server) {
+      if (fs.existsSync(paths.proxySetup)) {
+        // This registers user provided middleware for proxy reasons
+        require(paths.proxySetup)(app);
+      }
+
+      // This lets us fetch source contents from webpack for the error overlay
+      app.use(evalSourceMapMiddleware(server));
+      // This lets us open files from the runtime error overlay.
+      app.use(errorOverlayMiddleware());
+
+      // This service worker file is effectively a 'no-op' that will reset any
+      // previous service worker registered for the same host:port combination.
+      // We do this in development to avoid hitting the production cache if
+      // it used the same host and port.
+      // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
+      app.use(noopServiceWorkerMiddleware());
+    },
+  };
+};

+ 146 - 0
package.json

@@ -0,0 +1,146 @@
+{
+  "name": "tuonq-admin",
+  "version": "0.0.1",
+  "homepage": ".",
+  "dependencies": {
+    "@babel/core": "7.1.0",
+    "@svgr/webpack": "2.4.1",
+    "antd": "^3.10.0",
+    "antd-theme-generator": "^1.1.3",
+    "axios": "^0.18.0",
+    "babel-core": "7.0.0-bridge.0",
+    "babel-eslint": "9.0.0",
+    "babel-jest": "23.6.0",
+    "babel-loader": "8.0.4",
+    "babel-plugin-import": "^1.9.1",
+    "babel-plugin-named-asset-import": "^0.2.2",
+    "babel-preset-react-app": "^5.0.3",
+    "bfj": "6.1.1",
+    "case-sensitive-paths-webpack-plugin": "2.1.2",
+    "chalk": "2.4.1",
+    "classnames": "^2.2.6",
+    "css-loader": "1.0.0",
+    "dotenv": "6.0.0",
+    "dotenv-expand": "4.2.0",
+    "draftjs-to-html": "^0.8.4",
+    "draftjs-to-markdown": "^0.5.1",
+    "echarts": "^4.2.0-rc.1",
+    "echarts-for-react": "^2.0.15-beta.0",
+    "eslint": "5.6.0",
+    "eslint-config-react-app": "^3.0.3",
+    "eslint-loader": "2.1.1",
+    "eslint-plugin-flowtype": "2.50.1",
+    "eslint-plugin-import": "2.14.0",
+    "eslint-plugin-jsx-a11y": "6.1.1",
+    "eslint-plugin-react": "7.11.1",
+    "file-loader": "2.0.0",
+    "fs-extra": "7.0.0",
+    "html-webpack-plugin": "4.0.0-alpha.2",
+    "identity-obj-proxy": "3.0.0",
+    "jest": "23.6.0",
+    "jest-pnp-resolver": "1.0.1",
+    "jest-resolve": "23.6.0",
+    "less": "2.7.2",
+    "less-loader": "^4.1.0",
+    "mini-css-extract-plugin": "0.4.3",
+    "nprogress": "^0.2.0",
+    "optimize-css-assets-webpack-plugin": "5.0.1",
+    "photoswipe": "^4.1.2",
+    "pnp-webpack-plugin": "1.1.0",
+    "postcss-flexbugs-fixes": "4.1.0",
+    "postcss-loader": "3.0.0",
+    "postcss-preset-env": "6.0.6",
+    "postcss-safe-parser": "4.0.1",
+    "query-string": "5",
+    "rc-banner-anim": "^2.0.8",
+    "react": "^16.8.6",
+    "react-app-polyfill": "^0.1.3",
+    "react-beautiful-dnd": "^11.0.3",
+    "react-color": "^2.14.1",
+    "react-dev-utils": "^6.0.4",
+    "react-document-title": "^2.0.3",
+    "react-dom": "^16.8.6",
+    "react-draft-wysiwyg": "^1.12.13",
+    "react-draggable": "^3.0.5",
+    "react-hot-loader": "^4.3.11",
+    "react-loadable": "^5.5.0",
+    "react-qmap": "^0.1.6",
+    "react-redux": "^5.0.7",
+    "react-router-dom": "^4.3.1",
+    "recharts": "^1.3.3",
+    "redux": "^4.0.0",
+    "redux-alita": "^1.0.0",
+    "redux-thunk": "^2.3.0",
+    "resolve": "1.8.1",
+    "sass-loader": "7.1.0",
+    "screenfull": "^3.3.3",
+    "style-loader": "0.23.0",
+    "terser-webpack-plugin": "1.1.0",
+    "url-loader": "1.1.1",
+    "webpack": "4.19.1",
+    "webpack-dev-server": ">=3.1.11",
+    "webpack-manifest-plugin": "2.0.4",
+    "workbox-webpack-plugin": "3.6.2"
+  },
+  "scripts": {
+    "start": "node scripts/start.js",
+    "build": "node scripts/build.js",
+    "test": "node scripts/test.js",
+    "theme": "node theme.js",
+    "prettier": "prettier -c --write \"src/**/*.{js,ts,html,css}\""
+  },
+  "eslintConfig": {
+    "extends": "react-app"
+  },
+  "browserslist": [
+    ">0.2%",
+    "not dead",
+    "not ie <= 11",
+    "not op_mini all"
+  ],
+  "jest": {
+    "collectCoverageFrom": [
+      "src/**/*.{js,jsx}"
+    ],
+    "resolver": "jest-pnp-resolver",
+    "setupFiles": [
+      "react-app-polyfill/jsdom"
+    ],
+    "testMatch": [
+      "<rootDir>/src/**/__tests__/**/*.{js,jsx}",
+      "<rootDir>/src/**/?(*.)(spec|test).{js,jsx}"
+    ],
+    "testEnvironment": "jsdom",
+    "testURL": "http://localhost",
+    "transform": {
+      "^.+\\.(js|jsx)$": "<rootDir>/node_modules/babel-jest",
+      "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
+      "^(?!.*\\.(js|jsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
+    },
+    "transformIgnorePatterns": [
+      "[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$",
+      "^.+\\.module\\.(css|sass|scss)$"
+    ],
+    "moduleNameMapper": {
+      "^react-native$": "react-native-web",
+      "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
+    },
+    "moduleFileExtensions": [
+      "web.js",
+      "js",
+      "json",
+      "web.jsx",
+      "jsx",
+      "node"
+    ]
+  },
+  "babel": {
+    "presets": [
+      "react-app"
+    ]
+  },
+  "devDependencies": {
+    "eslint-plugin-react-hooks": "^1.6.0"
+  },
+  "description": "项目的管理后台"
+}

BIN
public/favicon.ico


BIN
public/images/icons/icon-128x128.png


BIN
public/images/icons/icon-144x144.png


BIN
public/images/icons/icon-152x152.png


BIN
public/images/icons/icon-192x192.png


BIN
public/images/icons/icon-384x384.png


BIN
public/images/icons/icon-512x512.png


BIN
public/images/icons/icon-72x72.png


BIN
public/images/icons/icon-96x96.png


+ 51 - 0
public/index.html

@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+	<meta charset="utf-8">
+	<link rel="shortcut icon" href="%PUBLIC_URL%/logo.png">
+	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+	<meta name="theme-color" content="#000000">
+	<!--
+		manifest.json provides metadata used when your web app is added to the
+		homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
+    -->
+	<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
+	<!--
+		Notice the use of %PUBLIC_URL% in the tags above.
+		It will be replaced with the URL of the `public` folder during the build.
+		Only files inside the `public` folder can be referenced from the HTML.
+
+		Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+		work correctly both with client-side routing and a non-root public URL.
+		Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+	<title>React Admin</title>
+</head>
+
+<body>
+	<link rel="stylesheet/less" type="text/css" href="./theme.less" />
+	<script src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script>
+	<script>
+		const primaryColor = localStorage.getItem('@primary-color');
+		primaryColor && window.less.modifyVars({
+			'@primary-color': primaryColor,
+		})
+    </script>
+	<noscript>
+		You need to enable JavaScript to run this app.
+	</noscript>
+	<div id="root"></div>
+	<!--
+		This HTML file is a template.
+		If you open it directly in the browser, you will see an empty page.
+
+		You can add webfonts, meta tags, or analytics to this file.
+		The build step will place the bundled scripts into the <body> tag.
+
+		To begin the development, run `npm start` or `yarn start`.
+		To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+</body>
+
+</html>

BIN
public/logo.png


+ 51 - 0
public/manifest.json

@@ -0,0 +1,51 @@
+{
+  "name": "ReactAdmin",
+  "short_name": "ReactAdmin",
+  "theme_color": "#313653",
+  "background_color": "#313653",
+  "display": "fullscreen",
+  "start_url": ".",
+  "icons": [
+    {
+      "src": "images/icons/icon-72x72.png",
+      "sizes": "72x72",
+      "type": "image/png"
+    },
+    {
+      "src": "images/icons/icon-96x96.png",
+      "sizes": "96x96",
+      "type": "image/png"
+    },
+    {
+      "src": "images/icons/icon-128x128.png",
+      "sizes": "128x128",
+      "type": "image/png"
+    },
+    {
+      "src": "images/icons/icon-144x144.png",
+      "sizes": "144x144",
+      "type": "image/png"
+    },
+    {
+      "src": "images/icons/icon-152x152.png",
+      "sizes": "152x152",
+      "type": "image/png"
+    },
+    {
+      "src": "images/icons/icon-192x192.png",
+      "sizes": "192x192",
+      "type": "image/png"
+    },
+    {
+      "src": "images/icons/icon-384x384.png",
+      "sizes": "384x384",
+      "type": "image/png"
+    },
+    {
+      "src": "images/icons/icon-512x512.png",
+      "sizes": "512x512",
+      "type": "image/png"
+    }
+  ],
+  "splash_pages": null
+}

File diff suppressed because it is too large
+ 3067 - 0
public/theme.less


+ 189 - 0
scripts/build.js

@@ -0,0 +1,189 @@
+'use strict';
+
+// Do this as the first thing so that any code reading it knows the right env.
+process.env.BABEL_ENV = 'production';
+process.env.NODE_ENV = 'production';
+
+// Makes the script crash on unhandled rejections instead of silently
+// ignoring them. In the future, promise rejections that are not handled will
+// terminate the Node.js process with a non-zero exit code.
+process.on('unhandledRejection', err => {
+  throw err;
+});
+
+// Ensure environment variables are read.
+require('../config/env');
+
+
+const path = require('path');
+const chalk = require('chalk');
+const fs = require('fs-extra');
+const webpack = require('webpack');
+const bfj = require('bfj');
+const config = require('../config/webpack.config.prod');
+const paths = require('../config/paths');
+const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
+const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
+const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
+const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
+const printBuildError = require('react-dev-utils/printBuildError');
+
+const measureFileSizesBeforeBuild =
+  FileSizeReporter.measureFileSizesBeforeBuild;
+const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
+const useYarn = fs.existsSync(paths.yarnLockFile);
+
+// These sizes are pretty large. We'll warn for bundles exceeding them.
+const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
+const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
+
+const isInteractive = process.stdout.isTTY;
+
+// Warn and crash if required files are missing
+if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
+  process.exit(1);
+}
+
+// Process CLI arguments
+const argv = process.argv.slice(2);
+const writeStatsJson = argv.indexOf('--stats') !== -1;
+
+// We require that you explictly set browsers and do not fall back to
+// browserslist defaults.
+const { checkBrowsers } = require('react-dev-utils/browsersHelper');
+checkBrowsers(paths.appPath, isInteractive)
+  .then(() => {
+    // First, read the current file sizes in build directory.
+    // This lets us display how much they changed later.
+    return measureFileSizesBeforeBuild(paths.appBuild);
+  })
+  .then(previousFileSizes => {
+    // Remove all content but keep the directory so that
+    // if you're in it, you don't end up in Trash
+    fs.emptyDirSync(paths.appBuild);
+    // Merge with the public folder
+    copyPublicFolder();
+    // Start the webpack build
+    return build(previousFileSizes);
+  })
+  .then(
+    ({ stats, previousFileSizes, warnings }) => {
+      if (warnings.length) {
+        console.log(chalk.yellow('Compiled with warnings.\n'));
+        console.log(warnings.join('\n\n'));
+        console.log(
+          '\nSearch for the ' +
+            chalk.underline(chalk.yellow('keywords')) +
+            ' to learn more about each warning.'
+        );
+        console.log(
+          'To ignore, add ' +
+            chalk.cyan('// eslint-disable-next-line') +
+            ' to the line before.\n'
+        );
+      } else {
+        console.log(chalk.green('Compiled successfully.\n'));
+      }
+
+      console.log('File sizes after gzip:\n');
+      printFileSizesAfterBuild(
+        stats,
+        previousFileSizes,
+        paths.appBuild,
+        WARN_AFTER_BUNDLE_GZIP_SIZE,
+        WARN_AFTER_CHUNK_GZIP_SIZE
+      );
+      console.log();
+
+      const appPackage = require(paths.appPackageJson);
+      const publicUrl = paths.publicUrl;
+      const publicPath = config.output.publicPath;
+      const buildFolder = path.relative(process.cwd(), paths.appBuild);
+      printHostingInstructions(
+        appPackage,
+        publicUrl,
+        publicPath,
+        buildFolder,
+        useYarn
+      );
+    },
+    err => {
+      console.log(chalk.red('Failed to compile.\n'));
+      printBuildError(err);
+      process.exit(1);
+    }
+  )
+  .catch(err => {
+    if (err && err.message) {
+      console.log(err.message);
+    }
+    process.exit(1);
+  });
+
+// Create the production build and print the deployment instructions.
+function build(previousFileSizes) {
+  console.log('Creating an optimized production build...');
+
+  let compiler = webpack(config);
+  return new Promise((resolve, reject) => {
+    compiler.run((err, stats) => {
+      let messages;
+      if (err) {
+        if (!err.message) {
+          return reject(err);
+        }
+        messages = formatWebpackMessages({
+          errors: [err.message],
+          warnings: [],
+        });
+      } else {
+        messages = formatWebpackMessages(
+          stats.toJson({ all: false, warnings: true, errors: true })
+        );
+      }
+      if (messages.errors.length) {
+        // Only keep the first error. Others are often indicative
+        // of the same problem, but confuse the reader with noise.
+        if (messages.errors.length > 1) {
+          messages.errors.length = 1;
+        }
+        return reject(new Error(messages.errors.join('\n\n')));
+      }
+      if (
+        process.env.CI &&
+        (typeof process.env.CI !== 'string' ||
+          process.env.CI.toLowerCase() !== 'false') &&
+        messages.warnings.length
+      ) {
+        console.log(
+          chalk.yellow(
+            '\nTreating warnings as errors because process.env.CI = true.\n' +
+              'Most CI servers set it automatically.\n'
+          )
+        );
+        return reject(new Error(messages.warnings.join('\n\n')));
+      }
+
+      const resolveArgs = {
+        stats,
+        previousFileSizes,
+        warnings: messages.warnings,
+      };
+      if (writeStatsJson) {
+        return bfj
+          .write(paths.appBuild + '/bundle-stats.json', stats.toJson())
+          .then(() => resolve(resolveArgs))
+          .catch(error => reject(new Error(error)));
+      }
+
+      return resolve(resolveArgs);
+    });
+  });
+}
+
+function copyPublicFolder() {
+  fs.copySync(paths.appPublic, paths.appBuild, {
+    dereference: true,
+    filter: file => file !== paths.appHtml,
+  });
+}

+ 116 - 0
scripts/start.js

@@ -0,0 +1,116 @@
+'use strict';
+
+// Do this as the first thing so that any code reading it knows the right env.
+process.env.BABEL_ENV = 'development';
+process.env.NODE_ENV = 'development';
+
+// Makes the script crash on unhandled rejections instead of silently
+// ignoring them. In the future, promise rejections that are not handled will
+// terminate the Node.js process with a non-zero exit code.
+process.on('unhandledRejection', err => {
+  throw err;
+});
+
+// Ensure environment variables are read.
+require('../config/env');
+
+
+const fs = require('fs');
+const chalk = require('chalk');
+const webpack = require('webpack');
+const WebpackDevServer = require('webpack-dev-server');
+const clearConsole = require('react-dev-utils/clearConsole');
+const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
+const {
+  choosePort,
+  createCompiler,
+  prepareProxy,
+  prepareUrls,
+} = require('react-dev-utils/WebpackDevServerUtils');
+const openBrowser = require('react-dev-utils/openBrowser');
+const paths = require('../config/paths');
+const config = require('../config/webpack.config.dev');
+const createDevServerConfig = require('../config/webpackDevServer.config');
+
+const useYarn = fs.existsSync(paths.yarnLockFile);
+const isInteractive = process.stdout.isTTY;
+
+// Warn and crash if required files are missing
+if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
+  process.exit(1);
+}
+
+// Tools like Cloud9 rely on this.
+const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
+const HOST = process.env.HOST || '0.0.0.0';
+
+if (process.env.HOST) {
+  console.log(
+    chalk.cyan(
+      `Attempting to bind to HOST environment variable: ${chalk.yellow(
+        chalk.bold(process.env.HOST)
+      )}`
+    )
+  );
+  console.log(
+    `If this was unintentional, check that you haven't mistakenly set it in your shell.`
+  );
+  console.log(
+    `Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}`
+  );
+  console.log();
+}
+
+// We require that you explictly set browsers and do not fall back to
+// browserslist defaults.
+const { checkBrowsers } = require('react-dev-utils/browsersHelper');
+checkBrowsers(paths.appPath, isInteractive)
+  .then(() => {
+    // We attempt to use the default port but if it is busy, we offer the user to
+    // run on a different port. `choosePort()` Promise resolves to the next free port.
+    return choosePort(HOST, DEFAULT_PORT);
+  })
+  .then(port => {
+    if (port == null) {
+      // We have not found a port.
+      return;
+    }
+    const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
+    const appName = require(paths.appPackageJson).name;
+    const urls = prepareUrls(protocol, HOST, port);
+    // Create a webpack compiler that is configured with custom messages.
+    const compiler = createCompiler(webpack, config, appName, urls, useYarn);
+    // Load proxy config
+    const proxySetting = require(paths.appPackageJson).proxy;
+    const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
+    // Serve webpack assets generated by the compiler over a web server.
+    const serverConfig = createDevServerConfig(
+      proxyConfig,
+      urls.lanUrlForConfig
+    );
+    const devServer = new WebpackDevServer(compiler, serverConfig);
+    // Launch WebpackDevServer.
+    devServer.listen(port, HOST, err => {
+      if (err) {
+        return console.log(err);
+      }
+      if (isInteractive) {
+        clearConsole();
+      }
+      console.log(chalk.cyan('Starting the development server...\n'));
+      openBrowser(urls.localUrlForBrowser);
+    });
+
+    ['SIGINT', 'SIGTERM'].forEach(function(sig) {
+      process.on(sig, function() {
+        devServer.close();
+        process.exit();
+      });
+    });
+  })
+  .catch(err => {
+    if (err && err.message) {
+      console.log(err.message);
+    }
+    process.exit(1);
+  });

+ 53 - 0
scripts/test.js

@@ -0,0 +1,53 @@
+'use strict';
+
+// Do this as the first thing so that any code reading it knows the right env.
+process.env.BABEL_ENV = 'test';
+process.env.NODE_ENV = 'test';
+process.env.PUBLIC_URL = '';
+
+// Makes the script crash on unhandled rejections instead of silently
+// ignoring them. In the future, promise rejections that are not handled will
+// terminate the Node.js process with a non-zero exit code.
+process.on('unhandledRejection', err => {
+  throw err;
+});
+
+// Ensure environment variables are read.
+require('../config/env');
+
+
+const jest = require('jest');
+const execSync = require('child_process').execSync;
+let argv = process.argv.slice(2);
+
+function isInGitRepository() {
+  try {
+    execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+function isInMercurialRepository() {
+  try {
+    execSync('hg --cwd . root', { stdio: 'ignore' });
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+// Watch unless on CI, in coverage mode, or explicitly running all tests
+if (
+  !process.env.CI &&
+  argv.indexOf('--coverage') === -1 &&
+  argv.indexOf('--watchAll') === -1
+) {
+  // https://github.com/facebook/create-react-app/issues/5210
+  const hasSourceControl = isInGitRepository() || isInMercurialRepository();
+  argv.push(hasSourceControl ? '--watch' : '--watchAll');
+}
+
+
+jest.run(argv);

+ 107 - 0
src/App.js

@@ -0,0 +1,107 @@
+import React, { Component } from 'react';
+import Routes from './routes';
+import DocumentTitle from 'react-document-title';
+import SiderCustom from './components/SiderCustom';
+import HeaderCustom from './components/HeaderCustom';
+import { Layout, notification, Icon } from 'antd';
+import { ThemePicker } from './components/widget';
+import { connectAlita } from 'redux-alita';
+
+const { Content, Footer } = Layout;
+
+class App extends Component {
+    state = {
+        collapsed: false,
+        title: '',
+    };
+    componentWillMount() {
+        const { setAlitaState } = this.props;
+        const user = JSON.parse(localStorage.getItem('user'));
+        // user && receiveData(user, 'auth');
+        user && setAlitaState({ stateName: 'auth', data: user });
+        // receiveData({a: 213}, 'auth');
+        // fetchData({funcName: 'admin', stateName: 'auth'});
+        this.getClientWidth();
+        window.onresize = () => {
+            console.log('屏幕变化了');
+            this.getClientWidth();
+        };
+    }
+    componentDidMount() {
+        const openNotification = () => {
+            notification.open({
+                message: '博主-yezihaohao',
+                description: (
+                    <div>
+                        <p>
+                            GitHub地址:{' '}
+                            <a
+                                href="https://github.com/yezihaohao"
+                                target="_blank"
+                                rel="noopener noreferrer"
+                            >
+                                https://github.com/yezihaohao
+                            </a>
+                        </p>
+                        <p>
+                            博客地址:{' '}
+                            <a
+                                href="https://yezihaohao.github.io/"
+                                target="_blank"
+                                rel="noopener noreferrer"
+                            >
+                                https://yezihaohao.github.io/
+                            </a>
+                        </p>
+                    </div>
+                ),
+                icon: <Icon type="smile-circle" style={{ color: 'red' }} />,
+                duration: 0,
+            });
+            localStorage.setItem('isFirst', JSON.stringify(true));
+        };
+        const isFirst = JSON.parse(localStorage.getItem('isFirst'));
+        !isFirst && openNotification();
+    }
+    getClientWidth = () => {
+        // 获取当前浏览器宽度并设置responsive管理响应式
+        const { setAlitaState } = this.props;
+        const clientWidth = window.innerWidth;
+        console.log(clientWidth);
+        setAlitaState({ stateName: 'responsive', data: { isMobile: clientWidth <= 992 } });
+        // receiveData({isMobile: clientWidth <= 992}, 'responsive');
+    };
+    toggle = () => {
+        this.setState({
+            collapsed: !this.state.collapsed,
+        });
+    };
+    render() {
+        const { title } = this.state;
+        const { auth = { data: {} }, responsive = { data: {} } } = this.props;
+        console.log(auth);
+        return (
+            <DocumentTitle title={title}>
+                <Layout>
+                    {!responsive.data.isMobile && <SiderCustom collapsed={this.state.collapsed} />}
+                    <ThemePicker />
+                    <Layout style={{ flexDirection: 'column' }}>
+                        <HeaderCustom
+                            toggle={this.toggle}
+                            collapsed={this.state.collapsed}
+                            user={auth.data || {}}
+                        />
+                        <Content style={{ margin: '0 16px', overflow: 'initial', flex: '1 1 0' }}>
+                            <Routes auth={auth} />
+                        </Content>
+                        <Footer style={{ textAlign: 'center' }}>
+                            React-Admin ©{new Date().getFullYear()} Created by 865470087@qq.com
+                        </Footer>
+                    </Layout>
+                </Layout>
+            </DocumentTitle>
+        );
+    }
+}
+
+export default connectAlita(['auth', 'responsive'])(App);

+ 9 - 0
src/App.test.js

@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+
+it('renders without crashing', () => {
+    const div = document.createElement('div');
+    ReactDOM.render(<App />, div);
+    ReactDOM.unmountComponentAtNode(div);
+});

+ 17 - 0
src/Page.js

@@ -0,0 +1,17 @@
+import React from 'react';
+import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
+import NotFound from './components/pages/NotFound';
+import Login from './components/pages/Login';
+import App from './App';
+
+export default () => (
+    <Router>
+        <Switch>
+            <Route exact path="/" render={() => <Redirect to="/app/dashboard/index" push />} />
+            <Route path="/app" component={App} />
+            <Route path="/404" component={NotFound} />
+            <Route path="/login" component={Login} />
+            <Route component={NotFound} />
+        </Switch>
+    </Router>
+);

+ 19 - 0
src/axios/config.js

@@ -0,0 +1,19 @@
+/**
+ * Created by 叶子 on 2017/7/30.
+ * 接口地址配置文件
+ */
+
+//easy-mock模拟数据接口地址
+const EASY_MOCK = 'https://www.easy-mock.com/mock';
+const MOCK_AUTH = EASY_MOCK + '/597b5ed9a1d30433d8411456/auth'; // 权限接口地址
+export const MOCK_AUTH_ADMIN = MOCK_AUTH + '/admin'; // 管理员权限接口
+export const MOCK_AUTH_VISITOR = MOCK_AUTH + '/visitor'; // 访问权限接口
+
+// github授权
+export const GIT_OAUTH = 'https://github.com/login/oauth';
+// github用户
+export const GIT_USER = 'https://api.github.com/user';
+
+// bbc top news
+export const NEWS_BBC =
+    'https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=429904aa01f54a39a278a406acf50070';

+ 47 - 0
src/axios/index.js

@@ -0,0 +1,47 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+import axios from 'axios';
+import { get, post } from './tools';
+import * as config from './config';
+
+export const getBbcNews = () => get({ url: config.NEWS_BBC });
+
+export const npmDependencies = () =>
+    axios
+        .get('./npm.json')
+        .then(res => res.data)
+        .catch(err => console.log(err));
+
+export const weibo = () =>
+    axios
+        .get('./weibo.json')
+        .then(res => res.data)
+        .catch(err => console.log(err));
+
+export const gitOauthLogin = () =>
+    get({
+        url: `${
+            config.GIT_OAUTH
+        }/authorize?client_id=792cdcd244e98dcd2dee&redirect_uri=http://localhost:3006/&scope=user&state=reactAdmin`,
+    });
+export const gitOauthToken = code =>
+    post({
+        url: `https://cors-anywhere.herokuapp.com/${config.GIT_OAUTH}/access_token`,
+        data: {
+            client_id: '792cdcd244e98dcd2dee',
+            client_secret: '81c4ff9df390d482b7c8b214a55cf24bf1f53059',
+            redirect_uri: 'http://localhost:3006/',
+            state: 'reactAdmin',
+            code,
+        },
+    });
+// {headers: {Accept: 'application/json'}}
+export const gitOauthInfo = access_token =>
+    get({ url: `${config.GIT_USER}access_token=${access_token}` });
+
+// easy-mock数据交互
+// 管理员权限获取
+export const admin = () => get({ url: config.MOCK_AUTH_ADMIN });
+// 访问权限获取
+export const guest = () => get({ url: config.MOCK_AUTH_VISITOR });

+ 37 - 0
src/axios/tools.js

@@ -0,0 +1,37 @@
+/**
+ * Created by 叶子 on 2017/7/30.
+ * http通用工具函数
+ */
+import axios from 'axios';
+import { message } from 'antd';
+
+/**
+ * 公用get请求
+ * @param url       接口地址
+ * @param msg       接口异常提示
+ * @param headers   接口所需header配置
+ */
+export const get = ({ url, msg = '接口异常', headers }) =>
+    axios
+        .get(url, headers)
+        .then(res => res.data)
+        .catch(err => {
+            console.log(err);
+            message.warn(msg);
+        });
+
+/**
+ * 公用post请求
+ * @param url       接口地址
+ * @param data      接口参数
+ * @param msg       接口异常提示
+ * @param headers   接口所需header配置
+ */
+export const post = ({ url, data, msg = '接口异常', headers }) =>
+    axios
+        .post(url, data, headers)
+        .then(res => res.data)
+        .catch(err => {
+            console.log(err);
+            message.warn(msg);
+        });

+ 24 - 0
src/components/BreadcrumbCustom.jsx

@@ -0,0 +1,24 @@
+/**
+ * Created by hao.cheng on 2017/4/22.
+ */
+import React from 'react';
+import { Breadcrumb } from 'antd';
+import { Link } from 'react-router-dom';
+
+class BreadcrumbCustom extends React.Component {
+    render() {
+        const first = <Breadcrumb.Item>{this.props.first}</Breadcrumb.Item> || '';
+        const second = <Breadcrumb.Item>{this.props.second}</Breadcrumb.Item> || '';
+        return (
+            <span>
+                <Breadcrumb style={{ margin: '12px 0' }}>
+                    <Breadcrumb.Item><Link to={'/app/dashboard/index'}>首页</Link></Breadcrumb.Item>
+                        {first}
+                        {second}
+                </Breadcrumb>
+            </span>
+        )
+    }
+}
+
+export default BreadcrumbCustom;

+ 113 - 0
src/components/HeaderCustom.jsx

@@ -0,0 +1,113 @@
+/**
+ * Created by hao.cheng on 2017/4/13.
+ */
+import React, { Component } from 'react';
+import screenfull from 'screenfull';
+import avater from '../style/imgs/b1.jpg';
+import SiderCustom from './SiderCustom';
+import { Menu, Icon, Layout, Badge, Popover } from 'antd';
+import { gitOauthToken, gitOauthInfo } from '../axios';
+import { queryString } from '../utils';
+import { withRouter } from 'react-router-dom';
+import { PwaInstaller } from './widget';
+import { connectAlita } from 'redux-alita';
+const { Header } = Layout;
+const SubMenu = Menu.SubMenu;
+const MenuItemGroup = Menu.ItemGroup;
+
+class HeaderCustom extends Component {
+    state = {
+        user: '',
+        visible: false,
+    };
+    componentDidMount() {
+        const QueryString = queryString();
+        const _user = JSON.parse(localStorage.getItem('user')) || '测试';
+        if (!_user && QueryString.hasOwnProperty('code')) {
+            gitOauthToken(QueryString.code).then(res => {
+                gitOauthInfo(res.access_token).then(info => {
+                    this.setState({
+                        user: info
+                    });
+                    localStorage.setItem('user', JSON.stringify(info));
+                });
+            });
+        } else {
+            this.setState({
+                user: _user
+            });
+        }
+    };
+    screenFull = () => {
+        if (screenfull.enabled) {
+            screenfull.request();
+        }
+
+    };
+    menuClick = e => {
+        console.log(e);
+        e.key === 'logout' && this.logout();
+    };
+    logout = () => {
+        localStorage.removeItem('user');
+        this.props.history.push('/login')
+    };
+    popoverHide = () => {
+        this.setState({
+            visible: false,
+        });
+    };
+    handleVisibleChange = (visible) => {
+        this.setState({ visible });
+    };
+    render() {
+        const { responsive = { data: {} }, path } = this.props;
+        return (
+            <Header className="custom-theme header" >
+                {
+                    responsive.data.isMobile ? (
+                        <Popover content={<SiderCustom path={path} popoverHide={this.popoverHide} />} trigger="click" placement="bottomLeft" visible={this.state.visible} onVisibleChange={this.handleVisibleChange}>
+                            <Icon type="bars" className="header__trigger custom-trigger" />
+                        </Popover>
+                    ) : (
+                        <Icon
+                            className="header__trigger custom-trigger"
+                            type={this.props.collapsed ? 'menu-unfold' : 'menu-fold'}
+                            onClick={this.props.toggle}
+                        />
+                    )
+                }
+                <Menu
+                    mode="horizontal"
+                    style={{ lineHeight: '64px', float: 'right' }}
+                    onClick={this.menuClick}
+                >
+                    <Menu.Item key="pwa">
+                        <PwaInstaller />
+                    </Menu.Item>
+                    <Menu.Item key="full" onClick={this.screenFull} >
+                        <Icon type="arrows-alt" onClick={this.screenFull} />
+                    </Menu.Item>
+                    <Menu.Item key="1">
+                        <Badge count={25} overflowCount={10} style={{marginLeft: 10}}>
+                            <Icon type="notification" />
+                        </Badge>
+                    </Menu.Item>
+                    <SubMenu title={<span className="avatar"><img src={avater} alt="头像" /><i className="on bottom b-white" /></span>}>
+                        <MenuItemGroup title="用户中心">
+                            <Menu.Item key="setting:1">你好 - {this.props.user.userName}</Menu.Item>
+                            <Menu.Item key="setting:2">个人信息</Menu.Item>
+                            <Menu.Item key="logout"><span onClick={this.logout}>退出登录</span></Menu.Item>
+                        </MenuItemGroup>
+                        <MenuItemGroup title="设置中心">
+                            <Menu.Item key="setting:3">个人设置</Menu.Item>
+                            <Menu.Item key="setting:4">系统设置</Menu.Item>
+                        </MenuItemGroup>
+                    </SubMenu>
+                </Menu>
+            </Header>
+        )
+    }
+}
+
+export default withRouter(connectAlita(['responsive'])(HeaderCustom));

+ 17 - 0
src/components/Page.jsx

@@ -0,0 +1,17 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+import React from 'react';
+
+class Page extends React.Component {
+    render() {
+        return (
+            <div style={{height: '100%'}}>
+                {this.props.children}
+            </div>
+        )
+
+    }
+}
+
+export default Page;

+ 95 - 0
src/components/SiderCustom.jsx

@@ -0,0 +1,95 @@
+/**
+ * Created by hao.cheng on 2017/4/13.
+ */
+import React, { Component } from 'react';
+import { Layout } from 'antd';
+import { withRouter } from 'react-router-dom';
+import routes from '../routes/config';
+import SiderMenu from './SiderMenu';
+
+const { Sider } = Layout;
+
+class SiderCustom extends Component {
+    static getDerivedStateFromProps (props, state) {
+        if (props.collapsed !== state.collapsed) {
+            const state1 = SiderCustom.setMenuOpen(props);
+            const state2 = SiderCustom.onCollapse(props.collapsed);
+            return {
+                ...state1,
+                ...state2,
+                firstHide: state.collapsed !== props.collapsed && props.collapsed, // 两个不等时赋值props属性值否则为false
+                openKey: state.openKey || (!props.collapsed && state1.openKey)
+            }
+        }
+        return null;
+    }
+    static setMenuOpen = props => {
+        const { pathname } = props.location;
+        return {
+            openKey: pathname.substr(0, pathname.lastIndexOf('/')),
+            selectedKey: pathname
+        };
+    };
+    static onCollapse = (collapsed) => {
+        return {
+            collapsed,
+            // firstHide: collapsed,
+            mode: collapsed ? 'vertical' : 'inline',
+        };
+    };
+    state = {
+        mode: 'inline',
+        openKey: '',
+        selectedKey: '',
+        firstHide: true, // 点击收缩菜单,第一次隐藏展开子菜单,openMenu时恢复
+    };
+    componentDidMount() {
+        // this.setMenuOpen(this.props);
+        const state = SiderCustom.setMenuOpen(this.props);
+        this.setState(state);
+    }
+    menuClick = e => {
+        this.setState({
+            selectedKey: e.key
+        });
+        const { popoverHide } = this.props; // 响应式布局控制小屏幕点击菜单时隐藏菜单操作
+        popoverHide && popoverHide();
+    };
+    openMenu = v => {
+        this.setState({
+            openKey: v[v.length - 1],
+            firstHide: false,
+        })
+    };
+    render() {
+        const { selectedKey, openKey, firstHide, collapsed } = this.state;
+        return (
+            <Sider
+                trigger={null}
+                breakpoint="lg"
+                collapsed={collapsed}
+                style={{ overflowY: 'auto' }}
+            >
+                <div className="logo" />
+                <SiderMenu
+                    menus={routes.menus}
+                    onClick={this.menuClick}
+                    mode="inline"
+                    selectedKeys={[selectedKey]}
+                    openKeys={firstHide ? null : [openKey]}
+                    onOpenChange={this.openMenu}
+                />
+                <style>
+                    {`
+                    #nprogress .spinner{
+                        left: ${collapsed ? '70px' : '206px'};
+                        right: 0 !important;
+                    }
+                    `}
+                </style>
+            </Sider>
+        )
+    }
+}
+
+export default withRouter(SiderCustom);

+ 79 - 0
src/components/SiderMenu.js

@@ -0,0 +1,79 @@
+import React, { useState } from 'react';
+import { Menu, Icon } from 'antd';
+import { Link } from 'react-router-dom';
+import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
+
+const renderMenuItem = (
+    item // item.route 菜单单独跳转的路由
+) => (
+    <Menu.Item key={item.key}>
+        <Link to={(item.route || item.key) + (item.query || '')}>
+            {item.icon && <Icon type={item.icon} />}
+            <span className="nav-text">{item.title}</span>
+        </Link>
+    </Menu.Item>
+);
+
+const renderSubMenu = item => (
+    <Menu.SubMenu
+        key={item.key}
+        title={
+            <span>
+                {item.icon && <Icon type={item.icon} />}
+                <span className="nav-text">{item.title}</span>
+            </span>
+        }
+    >
+        {item.subs.map(item => renderMenuItem(item))}
+    </Menu.SubMenu>
+);
+
+export default ({ menus, ...props }) => {
+    const [dragItems, setDragItems] = useState(menus);
+    const reorder = (list, startIndex, endIndex) => {
+        const result = Array.from(list);
+        const [removed] = result.splice(startIndex, 1);
+        result.splice(endIndex, 0, removed);
+        return result;
+    };
+    const onDragEnd = result => {
+        // dropped outside the list
+        if (!result.destination) {
+            return;
+        }
+
+        const _items = reorder(dragItems, result.source.index, result.destination.index);
+        setDragItems(_items);
+    };
+    return (
+        <DragDropContext onDragEnd={onDragEnd}>
+            <Droppable droppableId="droppable">
+                {(provided, snapshot) => (
+                    <div ref={provided.innerRef} {...provided.droppableProps}>
+                        {dragItems.map((item, index) => (
+                            <Draggable key={item.key} draggableId={item.key} index={index}>
+                                {(provided, snapshot) => (
+                                    <div>
+                                        <div
+                                            ref={provided.innerRef}
+                                            {...provided.dragHandleProps}
+                                            {...provided.draggableProps}
+                                        >
+                                            <Menu {...props}>
+                                                {item.subs
+                                                    ? renderSubMenu(item)
+                                                    : renderMenuItem(item)}
+                                            </Menu>
+                                        </div>
+                                        {provided.placeholder}
+                                    </div>
+                                )}
+                            </Draggable>
+                        ))}
+                        {provided.placeholder}
+                    </div>
+                )}
+            </Droppable>
+        </DragDropContext>
+    );
+};

+ 68 - 0
src/components/animation/BasicAnimations.jsx

@@ -0,0 +1,68 @@
+/**
+ * Created by hao.cheng on 2017/5/8.
+ */
+import React from 'react';
+import { Row, Col, Card, Switch } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+
+class BasicAnimations extends React.Component {
+    state = {
+        animated: false,
+        animatedOne: -1
+    };
+    animatedAll = (checked) => {
+        checked && this.setState({animated: true});
+        !checked && this.setState({animated: false});
+
+    };
+    animatedOne = (i) => {
+        this.setState({animatedOne: i});
+    };
+    animatedOneOver = () => {
+        this.setState({animatedOne: -1});
+    };
+    render() {
+        const animations = [
+            'bounce', 'flash', 'rubberBand', 'shake', 'headShake',
+            'swing', 'tada', 'wobble', 'jello', 'bounceIn', 'bounceInDown',
+            'bounceInLeft', 'bounceInRight', 'bounceOut', 'bounceOutDown', 'bounceOutLeft',
+            'bounceOutLeft', 'bounceOutUp', 'fadeIn', 'fadeInDown', 'fadeInDownBig', 'fadeInLeft',
+            'fadeInLeftBig', 'fadeInRight', 'fadeInRightBig', 'fadeInUp', 'fadeInUpBig', 'fadeOut',
+            'fadeOutDown', 'fadeOutDownBig', 'fadeOutLeft', 'fadeOutLeftBig', 'fadeOutRight', 'fadeOutRightBig',
+            'fadeOutUp', 'fadeOutUpBig', 'flipInX', 'flipInY', 'flipOutX', 'flipOutY',
+            'lightSpeedIn', 'lightSpeedOut', 'rotateIn', 'rotateInDownLeft', 'rotateInDownRight', 'rotateInUpLeft',
+            'rotateInUpRight', 'rotateOut', 'rotateOutDownLeft', 'rotateOutDownRight', 'rotateOutUpLeft', 'rotateOutUpRight',
+            'hinge', 'jackInTheBox', 'rollIn', 'rollOut','zoomIn', 'zoomInDown', 'zoomInLeft', 'zoomInRight', 'zoomInUp',
+            'zoomOut', 'zoomOutDown', 'zoomOutLeft', 'zoomOutRight', 'zoomOutUp', 'slideInDown',
+            'slideInLeft', 'slideInRight', 'slideInUp', 'slideOutDown', 'slideOutLeft', 'slideOutRight', 'slideOutUp'
+        ];
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="动画" second="基础动画" />
+                <Row className="mb-m">
+                    <span className="mr-s">全部动画(单个动画请移动鼠标)</span>
+                    <Switch onChange={this.animatedAll} />
+                </Row>
+                <Row gutter={14}>
+                    {animations.map((v, i) => (
+                        <Col className="gutter-row" md={6} key={i}>
+                            <div className="gutter-box">
+                                <Card
+                                    className={`${this.state.animated || (this.state.animatedOne === i) ? 'animated' : ''} ${this.state.animated || (this.state.animatedOne === i) ? 'infinite' : ''} ${v}`}
+                                    onMouseEnter={() => this.animatedOne(i)}
+                                    onMouseLeave={() => this.animatedOneOver()}
+                                >
+                                    <div className="pa-m text-center">
+                                        <h3>{v}</h3>
+                                    </div>
+                                </Card>
+                            </div>
+                        </Col>
+                    ))}
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default BasicAnimations;

+ 102 - 0
src/components/animation/ExampleAnimations.jsx

@@ -0,0 +1,102 @@
+/**
+ * Created by hao.cheng on 2017/5/8.
+ */
+import React from 'react';
+import { Row, Col, Card, Table, Popconfirm, Button } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+
+
+class ExampleAnimations extends React.Component {
+    constructor(props) {
+        super(props);
+        this.columns = [{
+            title: 'name',
+            dataIndex: 'name',
+            width: '30%'
+        }, {
+            title: 'age',
+            dataIndex: 'age',
+        }, {
+            title: 'address',
+            dataIndex: 'address',
+        }, {
+            title: 'operation',
+            dataIndex: 'operation',
+            render: (text, record, index) => {
+                return (
+                    this.state.dataSource.length > 1 ?
+                        (
+                            <Popconfirm title="Sure to delete?" onConfirm={() => this.onDelete(record, index)}>
+                                <span>Delete</span>
+                            </Popconfirm>
+                        ) : null
+                );
+            },
+        }];
+        this.state = {
+            dataSource: [{
+                key: '0',
+                name: 'Edward King 0',
+                age: '32',
+                address: 'London, Park Lane no. 0',
+            }, {
+                key: '1',
+                name: 'Edward King 1',
+                age: '32',
+                address: 'London, Park Lane no. 1',
+            }],
+            count: 2,
+            deleteIndex: -1
+        };
+    }
+    onDelete = (record, index) => {
+        const dataSource = [...this.state.dataSource];
+        dataSource.splice(index, 1);
+        this.setState({ deleteIndex: record.key});
+        setTimeout(() => {
+            this.setState({ dataSource })
+        }, 500);
+    };
+    handleAdd = () => {
+        const { count, dataSource } = this.state;
+        const newData = {
+            key: count,
+            name: `Edward King ${count}`,
+            age: 32,
+            address: `London, Park Lane no. ${count}`,
+        };
+        this.setState({
+            dataSource: [newData, ...dataSource],
+            count: count + 1,
+        });
+    };
+    render() {
+        const { dataSource } = this.state;
+        const columns = this.columns;
+        return (
+            <div className="gutter-example">
+                <BreadcrumbCustom first="动画" second="动画案例" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button className="editable-add-btn mb-s" onClick={this.handleAdd}>Add</Button>
+                                <Table
+                                    bordered
+                                    dataSource={dataSource}
+                                    columns={columns}
+                                    rowClassName={(record, index) => {
+                                        if (this.state.deleteIndex === record.key) return 'animated zoomOutLeft min-black';
+                                        return 'animated fadeInRight';
+                                    }}
+                                />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default ExampleAnimations;

+ 62 - 0
src/components/auth/Basic.js

@@ -0,0 +1,62 @@
+/**
+ * Created by 叶子 on 2017/7/31.
+ */
+import React, { Component } from 'react';
+import { Row, Col, Card } from 'antd';
+import BreadcrumbCustom from '@/components/BreadcrumbCustom';
+import AuthWidget from '@/components/widget/AuthWidget';
+import beauty from '@/style/imgs/beauty.jpg';
+
+class Basic extends Component {
+    render() {
+        return (
+            <div>
+                <BreadcrumbCustom first="权限管理" second="基础演示" />
+                <AuthWidget
+                    children={auth => (
+                        <Row>
+                            <Col span={24}>
+                                <Card bordered={false} bodyStyle={{ minHeight: 600 }}>
+                                    {!auth.uid && (
+                                        <h2 style={{ height: 500 }} className="center">
+                                            登录之后你将看到一张美女图
+                                        </h2>
+                                    )}
+                                    {auth.permissions &&
+                                        auth.permissions.includes('auth/authPage/visit') && (
+                                            <div style={{ textAlign: 'center' }}>
+                                                <img src={beauty} alt="" style={{ height: 400 }} />
+                                                {(auth.permissions.includes(
+                                                    'auth/authPage/edit'
+                                                ) && (
+                                                    <div>
+                                                        <p>
+                                                            看啥子美女,看点美景就行啦~
+                                                            <span
+                                                                role="img"
+                                                                aria-label=""
+                                                                aria-labelledby=""
+                                                            >
+                                                                😄😄
+                                                            </span>
+                                                        </p>
+                                                        <p>管理员身份登录才能看到这两段话</p>
+                                                    </div>
+                                                )) || (
+                                                    <div>
+                                                        <p>管理员登录将看到不一样的效果</p>
+                                                    </div>
+                                                )}
+                                            </div>
+                                        )}
+                                </Card>
+                            </Col>
+                        </Row>
+                    )}
+                />
+            </div>
+        );
+    }
+}
+
+export default Basic;

+ 38 - 0
src/components/auth/RouterEnter.js

@@ -0,0 +1,38 @@
+/**
+ * Created by 叶子 on 2017/8/1.
+ */
+/**
+ * Created by 叶子 on 2017/7/31.
+ */
+import React, { Component } from 'react';
+import { Row, Col, Card } from 'antd';
+import BreadcrumbCustom from '@/components/BreadcrumbCustom';
+import AuthWidget from '@/components/widget/AuthWidget';
+
+class RouterEnter extends Component {
+    componentDidMount() {
+        console.log('RouterEnter');
+    }
+    render() {
+        return (
+            <div>
+                <BreadcrumbCustom first="权限管理" second="路由拦截" />
+                <AuthWidget
+                    children={auth => (
+                        <Row>
+                            <Col span={24}>
+                                <Card bordered={false} bodyStyle={{ minHeight: 600 }}>
+                                    <h2 style={{ height: 500 }} className="center">
+                                        只有管理员登录才能看到该页面,否则跳转到404页面
+                                    </h2>
+                                </Card>
+                            </Col>
+                        </Row>
+                    )}
+                />
+            </div>
+        );
+    }
+}
+
+export default RouterEnter;

+ 55 - 0
src/components/charts/Echarts.jsx

@@ -0,0 +1,55 @@
+/**
+ * Created by hao.cheng on 2017/4/17.
+ */
+import React from 'react';
+import { Row, Col, Card } from 'antd';
+import EchartsArea from './EchartsArea';
+import EchartsPie from './EchartsPie';
+import EchartsEffectScatter from './EchartsEffectScatter';
+import EchartsForce from './EchartsForce';
+
+class Echarts extends React.Component {
+    render() {
+        return (
+            <div className="gutter-example">
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="区域图" bordered={false}>
+                                <EchartsArea />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card title="关系图" bordered={false}>
+                                {/*<EchartsGraphnpm />*/}
+                                <EchartsForce />
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card title="饼图" bordered={false}>
+                                <EchartsPie />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="散点图" bordered={false}>
+                                <EchartsEffectScatter />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default Echarts;

+ 106 - 0
src/components/charts/EchartsArea.jsx

@@ -0,0 +1,106 @@
+/**
+ * Created by hao.cheng on 2017/4/17.
+ */
+import React from 'react';
+import ReactEcharts from 'echarts-for-react';
+import echarts from 'echarts';
+
+let base = +new Date(1968, 9, 3);
+let oneDay = 24 * 3600 * 1000;
+let date = [];
+
+let data = [Math.random() * 300];
+
+for (var i = 1; i < 20000; i++) {
+    var now = new Date(base += oneDay);
+    date.push([now.getFullYear(), now.getMonth() + 1, now.getDate()].join('/'));
+    data.push(Math.round((Math.random() - 0.5) * 20 + data[i - 1]));
+}
+
+const option = {
+    tooltip: {
+        trigger: 'axis',
+        position: function (pt) {
+            return [pt[0], '10%'];
+        }
+    },
+    title: {
+        left: 'center',
+        text: '大数据量面积图',
+    },
+    toolbox: {
+        feature: {
+            dataZoom: {
+                yAxisIndex: 'none'
+            },
+            restore: {},
+            saveAsImage: {}
+        }
+    },
+    xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: date
+    },
+    yAxis: {
+        type: 'value',
+        boundaryGap: [0, '100%']
+    },
+    dataZoom: [{
+        type: 'inside',
+        start: 0,
+        end: 10
+    }, {
+        start: 0,
+        end: 10,
+        handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
+        handleSize: '80%',
+        handleStyle: {
+            color: '#fff',
+            shadowBlur: 3,
+            shadowColor: 'rgba(0, 0, 0, 0.6)',
+            shadowOffsetX: 2,
+            shadowOffsetY: 2
+        }
+    }],
+    series: [
+        {
+            name:'模拟数据',
+            type:'line',
+            smooth:true,
+            symbol: 'none',
+            sampling: 'average',
+            itemStyle: {
+                normal: {
+                    color: 'rgb(255, 70, 131)'
+                }
+            },
+            areaStyle: {
+                normal: {
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                        offset: 0,
+                        color: 'rgb(255, 158, 68)'
+                    }, {
+                        offset: 1,
+                        color: 'rgb(255, 70, 131)'
+                    }])
+                }
+            },
+            data: data
+        }
+    ]
+};
+
+class EchartsArea extends React.Component {
+    render() {
+        return (
+            <ReactEcharts
+                option={option}
+                style={{height: '300px', width: '100%'}}
+                className={'react_for_echarts'}
+            />
+        )
+    }
+}
+
+export default EchartsArea;

+ 522 - 0
src/components/charts/EchartsEffectScatter.js

@@ -0,0 +1,522 @@
+/**
+ * Created by SEELE on 2017/8/23.
+ */
+import React, { Component } from 'react';
+import ReactEcharts from 'echarts-for-react';
+require('echarts/map/js/china.js');
+
+const data = [
+    { name: '海门', value: 9 },
+    { name: '鄂尔多斯', value: 12 },
+    { name: '招远', value: 12 },
+    { name: '舟山', value: 12 },
+    { name: '齐齐哈尔', value: 14 },
+    { name: '盐城', value: 15 },
+    { name: '赤峰', value: 16 },
+    { name: '青岛', value: 18 },
+    { name: '乳山', value: 18 },
+    { name: '金昌', value: 19 },
+    { name: '泉州', value: 21 },
+    { name: '莱西', value: 21 },
+    { name: '日照', value: 21 },
+    { name: '胶南', value: 22 },
+    { name: '南通', value: 23 },
+    { name: '拉萨', value: 24 },
+    { name: '云浮', value: 24 },
+    { name: '梅州', value: 25 },
+    { name: '文登', value: 25 },
+    { name: '上海', value: 25 },
+    { name: '攀枝花', value: 25 },
+    { name: '威海', value: 25 },
+    { name: '承德', value: 25 },
+    { name: '厦门', value: 26 },
+    { name: '汕尾', value: 26 },
+    { name: '潮州', value: 26 },
+    { name: '丹东', value: 27 },
+    { name: '太仓', value: 27 },
+    { name: '曲靖', value: 27 },
+    { name: '烟台', value: 28 },
+    { name: '福州', value: 29 },
+    { name: '瓦房店', value: 30 },
+    { name: '即墨', value: 30 },
+    { name: '抚顺', value: 31 },
+    { name: '玉溪', value: 31 },
+    { name: '张家口', value: 31 },
+    { name: '阳泉', value: 31 },
+    { name: '莱州', value: 32 },
+    { name: '湖州', value: 32 },
+    { name: '汕头', value: 32 },
+    { name: '昆山', value: 33 },
+    { name: '宁波', value: 33 },
+    { name: '湛江', value: 33 },
+    { name: '揭阳', value: 34 },
+    { name: '荣成', value: 34 },
+    { name: '连云港', value: 35 },
+    { name: '葫芦岛', value: 35 },
+    { name: '常熟', value: 36 },
+    { name: '东莞', value: 36 },
+    { name: '河源', value: 36 },
+    { name: '淮安', value: 36 },
+    { name: '泰州', value: 36 },
+    { name: '南宁', value: 37 },
+    { name: '营口', value: 37 },
+    { name: '惠州', value: 37 },
+    { name: '江阴', value: 37 },
+    { name: '蓬莱', value: 37 },
+    { name: '韶关', value: 38 },
+    { name: '嘉峪关', value: 38 },
+    { name: '广州', value: 38 },
+    { name: '延安', value: 38 },
+    { name: '太原', value: 39 },
+    { name: '清远', value: 39 },
+    { name: '中山', value: 39 },
+    { name: '昆明', value: 39 },
+    { name: '寿光', value: 40 },
+    { name: '盘锦', value: 40 },
+    { name: '长治', value: 41 },
+    { name: '深圳', value: 41 },
+    { name: '珠海', value: 42 },
+    { name: '宿迁', value: 43 },
+    { name: '咸阳', value: 43 },
+    { name: '铜川', value: 44 },
+    { name: '平度', value: 44 },
+    { name: '佛山', value: 44 },
+    { name: '海口', value: 44 },
+    { name: '江门', value: 45 },
+    { name: '章丘', value: 45 },
+    { name: '肇庆', value: 46 },
+    { name: '大连', value: 47 },
+    { name: '临汾', value: 47 },
+    { name: '吴江', value: 47 },
+    { name: '石嘴山', value: 49 },
+    { name: '沈阳', value: 50 },
+    { name: '苏州', value: 50 },
+    { name: '茂名', value: 50 },
+    { name: '嘉兴', value: 51 },
+    { name: '长春', value: 51 },
+    { name: '胶州', value: 52 },
+    { name: '银川', value: 52 },
+    { name: '张家港', value: 52 },
+    { name: '三门峡', value: 53 },
+    { name: '锦州', value: 54 },
+    { name: '南昌', value: 54 },
+    { name: '柳州', value: 54 },
+    { name: '三亚', value: 54 },
+    { name: '自贡', value: 56 },
+    { name: '吉林', value: 56 },
+    { name: '阳江', value: 57 },
+    { name: '泸州', value: 57 },
+    { name: '西宁', value: 57 },
+    { name: '宜宾', value: 58 },
+    { name: '呼和浩特', value: 58 },
+    { name: '成都', value: 58 },
+    { name: '大同', value: 58 },
+    { name: '镇江', value: 59 },
+    { name: '桂林', value: 59 },
+    { name: '张家界', value: 59 },
+    { name: '宜兴', value: 59 },
+    { name: '北海', value: 60 },
+    { name: '西安', value: 61 },
+    { name: '金坛', value: 62 },
+    { name: '东营', value: 62 },
+    { name: '牡丹江', value: 63 },
+    { name: '遵义', value: 63 },
+    { name: '绍兴', value: 63 },
+    { name: '扬州', value: 64 },
+    { name: '常州', value: 64 },
+    { name: '潍坊', value: 65 },
+    { name: '重庆', value: 66 },
+    { name: '台州', value: 67 },
+    { name: '南京', value: 67 },
+    { name: '滨州', value: 70 },
+    { name: '贵阳', value: 71 },
+    { name: '无锡', value: 71 },
+    { name: '本溪', value: 71 },
+    { name: '克拉玛依', value: 72 },
+    { name: '渭南', value: 72 },
+    { name: '马鞍山', value: 72 },
+    { name: '宝鸡', value: 72 },
+    { name: '焦作', value: 75 },
+    { name: '句容', value: 75 },
+    { name: '北京', value: 79 },
+    { name: '徐州', value: 79 },
+    { name: '衡水', value: 80 },
+    { name: '包头', value: 80 },
+    { name: '绵阳', value: 80 },
+    { name: '乌鲁木齐', value: 84 },
+    { name: '枣庄', value: 84 },
+    { name: '杭州', value: 84 },
+    { name: '淄博', value: 85 },
+    { name: '鞍山', value: 86 },
+    { name: '溧阳', value: 86 },
+    { name: '库尔勒', value: 86 },
+    { name: '安阳', value: 90 },
+    { name: '开封', value: 90 },
+    { name: '济南', value: 92 },
+    { name: '德阳', value: 93 },
+    { name: '温州', value: 95 },
+    { name: '九江', value: 96 },
+    { name: '邯郸', value: 98 },
+    { name: '临安', value: 99 },
+    { name: '兰州', value: 99 },
+    { name: '沧州', value: 100 },
+    { name: '临沂', value: 103 },
+    { name: '南充', value: 104 },
+    { name: '天津', value: 105 },
+    { name: '富阳', value: 106 },
+    { name: '泰安', value: 112 },
+    { name: '诸暨', value: 112 },
+    { name: '郑州', value: 113 },
+    { name: '哈尔滨', value: 114 },
+    { name: '聊城', value: 116 },
+    { name: '芜湖', value: 117 },
+    { name: '唐山', value: 119 },
+    { name: '平顶山', value: 119 },
+    { name: '邢台', value: 119 },
+    { name: '德州', value: 120 },
+    { name: '济宁', value: 120 },
+    { name: '荆州', value: 127 },
+    { name: '宜昌', value: 130 },
+    { name: '义乌', value: 132 },
+    { name: '丽水', value: 133 },
+    { name: '洛阳', value: 134 },
+    { name: '秦皇岛', value: 136 },
+    { name: '株洲', value: 143 },
+    { name: '石家庄', value: 147 },
+    { name: '莱芜', value: 148 },
+    { name: '常德', value: 152 },
+    { name: '保定', value: 153 },
+    { name: '湘潭', value: 154 },
+    { name: '金华', value: 157 },
+    { name: '岳阳', value: 169 },
+    { name: '长沙', value: 175 },
+    { name: '衢州', value: 177 },
+    { name: '廊坊', value: 193 },
+    { name: '菏泽', value: 194 },
+    { name: '合肥', value: 229 },
+    { name: '武汉', value: 273 },
+    { name: '大庆', value: 279 },
+];
+const geoCoordMap = {
+    海门: [121.15, 31.89],
+    鄂尔多斯: [109.781327, 39.608266],
+    招远: [120.38, 37.35],
+    舟山: [122.207216, 29.985295],
+    齐齐哈尔: [123.97, 47.33],
+    盐城: [120.13, 33.38],
+    赤峰: [118.87, 42.28],
+    青岛: [120.33, 36.07],
+    乳山: [121.52, 36.89],
+    金昌: [102.188043, 38.520089],
+    泉州: [118.58, 24.93],
+    莱西: [120.53, 36.86],
+    日照: [119.46, 35.42],
+    胶南: [119.97, 35.88],
+    南通: [121.05, 32.08],
+    拉萨: [91.11, 29.97],
+    云浮: [112.02, 22.93],
+    梅州: [116.1, 24.55],
+    文登: [122.05, 37.2],
+    上海: [121.48, 31.22],
+    攀枝花: [101.718637, 26.582347],
+    威海: [122.1, 37.5],
+    承德: [117.93, 40.97],
+    厦门: [118.1, 24.46],
+    汕尾: [115.375279, 22.786211],
+    潮州: [116.63, 23.68],
+    丹东: [124.37, 40.13],
+    太仓: [121.1, 31.45],
+    曲靖: [103.79, 25.51],
+    烟台: [121.39, 37.52],
+    福州: [119.3, 26.08],
+    瓦房店: [121.979603, 39.627114],
+    即墨: [120.45, 36.38],
+    抚顺: [123.97, 41.97],
+    玉溪: [102.52, 24.35],
+    张家口: [114.87, 40.82],
+    阳泉: [113.57, 37.85],
+    莱州: [119.942327, 37.177017],
+    湖州: [120.1, 30.86],
+    汕头: [116.69, 23.39],
+    昆山: [120.95, 31.39],
+    宁波: [121.56, 29.86],
+    湛江: [110.359377, 21.270708],
+    揭阳: [116.35, 23.55],
+    荣成: [122.41, 37.16],
+    连云港: [119.16, 34.59],
+    葫芦岛: [120.836932, 40.711052],
+    常熟: [120.74, 31.64],
+    东莞: [113.75, 23.04],
+    河源: [114.68, 23.73],
+    淮安: [119.15, 33.5],
+    泰州: [119.9, 32.49],
+    南宁: [108.33, 22.84],
+    营口: [122.18, 40.65],
+    惠州: [114.4, 23.09],
+    江阴: [120.26, 31.91],
+    蓬莱: [120.75, 37.8],
+    韶关: [113.62, 24.84],
+    嘉峪关: [98.289152, 39.77313],
+    广州: [113.23, 23.16],
+    延安: [109.47, 36.6],
+    太原: [112.53, 37.87],
+    清远: [113.01, 23.7],
+    中山: [113.38, 22.52],
+    昆明: [102.73, 25.04],
+    寿光: [118.73, 36.86],
+    盘锦: [122.070714, 41.119997],
+    长治: [113.08, 36.18],
+    深圳: [114.07, 22.62],
+    珠海: [113.52, 22.3],
+    宿迁: [118.3, 33.96],
+    咸阳: [108.72, 34.36],
+    铜川: [109.11, 35.09],
+    平度: [119.97, 36.77],
+    佛山: [113.11, 23.05],
+    海口: [110.35, 20.02],
+    江门: [113.06, 22.61],
+    章丘: [117.53, 36.72],
+    肇庆: [112.44, 23.05],
+    大连: [121.62, 38.92],
+    临汾: [111.5, 36.08],
+    吴江: [120.63, 31.16],
+    石嘴山: [106.39, 39.04],
+    沈阳: [123.38, 41.8],
+    苏州: [120.62, 31.32],
+    茂名: [110.88, 21.68],
+    嘉兴: [120.76, 30.77],
+    长春: [125.35, 43.88],
+    胶州: [120.03336, 36.264622],
+    银川: [106.27, 38.47],
+    张家港: [120.555821, 31.875428],
+    三门峡: [111.19, 34.76],
+    锦州: [121.15, 41.13],
+    南昌: [115.89, 28.68],
+    柳州: [109.4, 24.33],
+    三亚: [109.511909, 18.252847],
+    自贡: [104.778442, 29.33903],
+    吉林: [126.57, 43.87],
+    阳江: [111.95, 21.85],
+    泸州: [105.39, 28.91],
+    西宁: [101.74, 36.56],
+    宜宾: [104.56, 29.77],
+    呼和浩特: [111.65, 40.82],
+    成都: [104.06, 30.67],
+    大同: [113.3, 40.12],
+    镇江: [119.44, 32.2],
+    桂林: [110.28, 25.29],
+    张家界: [110.479191, 29.117096],
+    宜兴: [119.82, 31.36],
+    北海: [109.12, 21.49],
+    西安: [108.95, 34.27],
+    金坛: [119.56, 31.74],
+    东营: [118.49, 37.46],
+    牡丹江: [129.58, 44.6],
+    遵义: [106.9, 27.7],
+    绍兴: [120.58, 30.01],
+    扬州: [119.42, 32.39],
+    常州: [119.95, 31.79],
+    潍坊: [119.1, 36.62],
+    重庆: [106.54, 29.59],
+    台州: [121.420757, 28.656386],
+    南京: [118.78, 32.04],
+    滨州: [118.03, 37.36],
+    贵阳: [106.71, 26.57],
+    无锡: [120.29, 31.59],
+    本溪: [123.73, 41.3],
+    克拉玛依: [84.77, 45.59],
+    渭南: [109.5, 34.52],
+    马鞍山: [118.48, 31.56],
+    宝鸡: [107.15, 34.38],
+    焦作: [113.21, 35.24],
+    句容: [119.16, 31.95],
+    北京: [116.46, 39.92],
+    徐州: [117.2, 34.26],
+    衡水: [115.72, 37.72],
+    包头: [110, 40.58],
+    绵阳: [104.73, 31.48],
+    乌鲁木齐: [87.68, 43.77],
+    枣庄: [117.57, 34.86],
+    杭州: [120.19, 30.26],
+    淄博: [118.05, 36.78],
+    鞍山: [122.85, 41.12],
+    溧阳: [119.48, 31.43],
+    库尔勒: [86.06, 41.68],
+    安阳: [114.35, 36.1],
+    开封: [114.35, 34.79],
+    济南: [117, 36.65],
+    德阳: [104.37, 31.13],
+    温州: [120.65, 28.01],
+    九江: [115.97, 29.71],
+    邯郸: [114.47, 36.6],
+    临安: [119.72, 30.23],
+    兰州: [103.73, 36.03],
+    沧州: [116.83, 38.33],
+    临沂: [118.35, 35.05],
+    南充: [106.110698, 30.837793],
+    天津: [117.2, 39.13],
+    富阳: [119.95, 30.07],
+    泰安: [117.13, 36.18],
+    诸暨: [120.23, 29.71],
+    郑州: [113.65, 34.76],
+    哈尔滨: [126.63, 45.75],
+    聊城: [115.97, 36.45],
+    芜湖: [118.38, 31.33],
+    唐山: [118.02, 39.63],
+    平顶山: [113.29, 33.75],
+    邢台: [114.48, 37.05],
+    德州: [116.29, 37.45],
+    济宁: [116.59, 35.38],
+    荆州: [112.239741, 30.335165],
+    宜昌: [111.3, 30.7],
+    义乌: [120.06, 29.32],
+    丽水: [119.92, 28.45],
+    洛阳: [112.44, 34.7],
+    秦皇岛: [119.57, 39.95],
+    株洲: [113.16, 27.83],
+    石家庄: [114.48, 38.03],
+    莱芜: [117.67, 36.19],
+    常德: [111.69, 29.05],
+    保定: [115.48, 38.85],
+    湘潭: [112.91, 27.87],
+    金华: [119.64, 29.12],
+    岳阳: [113.09, 29.37],
+    长沙: [113, 28.21],
+    衢州: [118.88, 28.97],
+    廊坊: [116.7, 39.53],
+    菏泽: [115.480656, 35.23375],
+    合肥: [117.27, 31.86],
+    武汉: [114.31, 30.52],
+    大庆: [125.03, 46.58],
+};
+
+const convertData = function(data) {
+    let res = [];
+    for (let i = 0; i < data.length; i++) {
+        let geoCoord = geoCoordMap[data[i].name];
+        if (geoCoord) {
+            res.push({
+                name: data[i].name,
+                value: geoCoord.concat(data[i].value),
+            });
+        }
+    }
+    return res;
+};
+
+const option = {
+    backgroundColor: '#404a59',
+    title: {
+        text: '全国主要城市空气质量',
+        subtext: 'data from PM25.in',
+        sublink: 'http://www.pm25.in',
+        left: 'center',
+        textStyle: {
+            color: '#fff',
+        },
+    },
+    tooltip: {
+        trigger: 'item',
+    },
+    legend: {
+        orient: 'vertical',
+        y: 'bottom',
+        x: 'right',
+        data: ['pm2.5'],
+        textStyle: {
+            color: '#fff',
+        },
+    },
+    geo: {
+        map: 'china',
+        label: {
+            emphasis: {
+                show: false,
+            },
+        },
+        roam: true,
+        itemStyle: {
+            normal: {
+                areaColor: '#323c48',
+                borderColor: '#111',
+            },
+            emphasis: {
+                areaColor: '#2a333d',
+            },
+        },
+    },
+    series: [
+        {
+            name: 'pm2.5',
+            type: 'scatter',
+            coordinateSystem: 'geo',
+            data: convertData(data),
+            symbolSize: function(val) {
+                return val[2] / 10;
+            },
+            label: {
+                normal: {
+                    formatter: '{b}',
+                    position: 'right',
+                    show: false,
+                },
+                emphasis: {
+                    show: true,
+                },
+            },
+            itemStyle: {
+                normal: {
+                    color: '#ddb926',
+                },
+            },
+        },
+        {
+            name: 'Top 5',
+            type: 'effectScatter',
+            coordinateSystem: 'geo',
+            data: convertData(
+                data
+                    .sort(function(a, b) {
+                        return b.value - a.value;
+                    })
+                    .slice(0, 6)
+            ),
+            symbolSize: function(val) {
+                return val[2] / 10;
+            },
+            showEffectOn: 'render',
+            rippleEffect: {
+                brushType: 'stroke',
+            },
+            hoverAnimation: true,
+            label: {
+                normal: {
+                    formatter: '{b}',
+                    position: 'right',
+                    show: true,
+                },
+            },
+            itemStyle: {
+                normal: {
+                    color: '#f4e925',
+                    shadowBlur: 10,
+                    shadowColor: '#333',
+                },
+            },
+            zlevel: 1,
+        },
+    ],
+};
+class EchartsEffectScatter extends Component {
+    render() {
+        return (
+            <ReactEcharts
+                option={option}
+                style={{ height: '400px', width: '100%' }}
+                className={'react_for_echarts'}
+            />
+        );
+    }
+}
+
+export default EchartsEffectScatter;

+ 247 - 0
src/components/charts/EchartsForce.js

@@ -0,0 +1,247 @@
+/**
+ * Created by SEELE on 2017/8/23.
+ */
+import React, { Component } from 'react';
+import ReactEcharts from 'echarts-for-react';
+
+const option = {
+    title: {
+        text: '',
+    },
+    tooltip: {},
+    animationDurationUpdate: 1500,
+    animationEasingUpdate: 'quinticInOut',
+    label: {
+        normal: {
+            show: true,
+            textStyle: {
+                fontSize: 12,
+            },
+        },
+    },
+    legend: {
+        x: 'center',
+        show: false,
+        data: ['朋友', '战友', '亲戚'],
+    },
+    series: [
+        {
+            type: 'graph',
+            layout: 'force',
+            symbolSize: 45,
+            focusNodeAdjacency: true,
+            roam: true,
+            categories: [
+                {
+                    name: '朋友',
+                    itemStyle: {
+                        normal: {
+                            color: '#009800',
+                        },
+                    },
+                },
+                {
+                    name: '战友',
+                    itemStyle: {
+                        normal: {
+                            color: '#4592FF',
+                        },
+                    },
+                },
+                {
+                    name: '亲戚',
+                    itemStyle: {
+                        normal: {
+                            color: '#3592F',
+                        },
+                    },
+                },
+            ],
+            label: {
+                normal: {
+                    show: true,
+                    textStyle: {
+                        fontSize: 12,
+                    },
+                },
+            },
+            force: {
+                repulsion: 1000,
+            },
+            edgeSymbolSize: [4, 50],
+            edgeLabel: {
+                normal: {
+                    show: true,
+                    textStyle: {
+                        fontSize: 10,
+                    },
+                    formatter: '{c}',
+                },
+            },
+            data: [
+                {
+                    name: '徐贱云',
+                    draggable: true,
+                },
+                {
+                    name: '冯可梁',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '邓志荣',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '李荣庆',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '郑志勇',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '赵英杰',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '王承军',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '陈卫东',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '邹劲松',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '赵成',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '陈现忠',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '陶泳',
+                    category: 1,
+                    draggable: true,
+                },
+                {
+                    name: '王德福',
+                    category: 1,
+                    draggable: true,
+                },
+            ],
+            links: [
+                {
+                    source: 0,
+                    target: 1,
+                    category: 0,
+                    value: '朋友',
+                },
+                {
+                    source: 0,
+                    target: 2,
+                    value: '战友',
+                },
+                {
+                    source: 0,
+                    target: 3,
+                    value: '房东',
+                },
+                {
+                    source: 0,
+                    target: 4,
+                    value: '朋友',
+                },
+                {
+                    source: 1,
+                    target: 2,
+                    value: '表亲',
+                },
+                {
+                    source: 0,
+                    target: 5,
+                    value: '朋友',
+                },
+                {
+                    source: 4,
+                    target: 5,
+                    value: '姑姑',
+                },
+                {
+                    source: 2,
+                    target: 8,
+                    value: '叔叔',
+                },
+                {
+                    source: 0,
+                    target: 12,
+                    value: '朋友',
+                },
+                {
+                    source: 6,
+                    target: 11,
+                    value: '爱人',
+                },
+                {
+                    source: 6,
+                    target: 3,
+                    value: '朋友',
+                },
+                {
+                    source: 7,
+                    target: 5,
+                    value: '朋友',
+                },
+                {
+                    source: 9,
+                    target: 10,
+                    value: '朋友',
+                },
+                {
+                    source: 3,
+                    target: 10,
+                    value: '朋友',
+                },
+                {
+                    source: 2,
+                    target: 11,
+                    value: '同学',
+                },
+            ],
+            lineStyle: {
+                normal: {
+                    opacity: 0.9,
+                    width: 1,
+                    curveness: 0,
+                },
+            },
+        },
+    ],
+};
+class EchartsForce extends Component {
+    render() {
+        return (
+            <ReactEcharts
+                option={option}
+                style={{ height: '400px', width: '100%' }}
+                className={'react_for_echarts'}
+            />
+        );
+    }
+}
+
+export default EchartsForce;

+ 85 - 0
src/components/charts/EchartsGraphnpm.jsx

@@ -0,0 +1,85 @@
+/**
+ * Created by hao.cheng on 2017/4/21.
+ */
+import React from 'react';
+import ReactEcharts from 'echarts-for-react';
+import { npmDependencies } from '../../axios';
+
+class EchartsGraphnpm extends React.Component {
+    state = {
+        option : {
+        title: {
+            text: 'NPM Dependencies'
+        },
+        animationDurationUpdate: 1500,
+        animationEasingUpdate: 'quinticInOut',
+        series : [
+            {
+                type: 'graph',
+                layout: 'none',
+                // progressiveThreshold: 700,
+                data: [],
+                edges: [],
+                label: {
+                    emphasis: {
+                        position: 'right',
+                        show: true
+                    }
+                },
+                roam: true,
+                focusNodeAdjacency: true,
+                lineStyle: {
+                    normal: {
+                        width: 0.5,
+                        curveness: 0.3,
+                        opacity: 0.7
+                    }
+                }
+            }
+        ]
+    }
+    };
+    componentDidMount() {
+        npmDependencies().then(npm => {
+            this.setState({
+                option: {
+                    series: [
+                        {
+                            data: npm.nodes.map(function (node) {
+                                return {
+                                    x: node.x,
+                                    y: node.y,
+                                    id: node.id,
+                                    name: node.label,
+                                    symbolSize: node.size,
+                                    itemStyle: {
+                                        normal: {
+                                            color: node.color
+                                        }
+                                    }
+                                };
+                            }),
+                            edges: npm.edges.map(function (edge) {
+                                return {
+                                    source: edge.sourceID,
+                                    target: edge.targetID
+                                };
+                            })
+                        }
+                    ]
+                }
+            })
+        })
+    }
+    render() {
+        return (
+            <ReactEcharts
+                option={this.state.option}
+                style={{height: '300px', width: '100%'}}
+                className={'react_for_echarts'}
+            />
+        )
+    }
+}
+
+export default EchartsGraphnpm;

+ 86 - 0
src/components/charts/EchartsPie.jsx

@@ -0,0 +1,86 @@
+/**
+ * Created by hao.cheng on 2017/4/21.
+ */
+import React from 'react';
+import ReactEcharts from 'echarts-for-react';
+
+const option = {
+    title: {
+        text: 'Customized Pie',
+        left: 'center',
+        top: 20,
+        textStyle: {
+            color: '#777'
+        }
+    },
+
+    tooltip : {
+        trigger: 'item',
+        formatter: "{a} <br/>{b} : {c} ({d}%)"
+    },
+
+    visualMap: {
+        show: false,
+        min: 80,
+        max: 600,
+        inRange: {
+            colorLightness: [0, 1]
+        }
+    },
+    series : [
+        {
+            name:'访问来源',
+            type:'pie',
+            radius : '55%',
+            center: ['50%', '50%'],
+            data:[
+                {value:335, name:'直接访问'},
+                {value:310, name:'邮件营销'},
+                {value:274, name:'联盟广告'},
+                {value:235, name:'视频广告'},
+                {value:400, name:'搜索引擎'}
+            ].sort(function (a, b) { return a.value - b.value}),
+            roseType: 'angle',
+            label: {
+                normal: {
+                    textStyle: {
+                        color: '#777'
+                    }
+                }
+            },
+            labelLine: {
+                normal: {
+                    lineStyle: {
+                        color: '#777'
+                    },
+                    smooth: 0.2,
+                    length: 10,
+                    length2: 20
+                }
+            },
+            itemStyle: {
+                normal: {
+                    color: '#c23531',
+                    shadowBlur: 200,
+                    shadowColor: '#777'
+                }
+            },
+
+            animationType: 'scale',
+            animationEasing: 'elasticOut',
+            animationDelay: function (idx) {
+                return Math.random() * 200;
+            }
+        }
+    ]
+};
+
+const EchartsPie = () => (
+    <ReactEcharts
+        option={option}
+        style={{height: '300px', width: '100%'}}
+        className={'react_for_echarts'}
+    />
+);
+
+export default EchartsPie;

+ 131 - 0
src/components/charts/EchartsScatter.jsx

@@ -0,0 +1,131 @@
+/**
+ * Created by hao.cheng on 2017/4/21.
+ */
+import React from 'react';
+import ReactEcharts from 'echarts-for-react';
+import { weibo } from '../../axios';
+require('echarts/map/js/china.js');
+
+
+class EchartsScatter extends React.Component {
+    state = {
+        option: {
+        backgroundColor: '#404a59',
+        title : {
+            text: '微博签到数据点亮中国',
+            subtext: 'From ThinkGIS',
+            sublink: 'http://www.thinkgis.cn/public/sina',
+            left: 'center',
+            top: 'top',
+            textStyle: {
+                color: '#fff'
+            }
+        },
+        tooltip: {},
+        legend: {
+            left: 'left',
+            data: ['强', '中', '弱'],
+            textStyle: {
+                color: '#ccc'
+            }
+        },
+        geo: {
+            map: 'china',
+            label: {
+                emphasis: {
+                    show: false
+                }
+            },
+            itemStyle: {
+                normal: {
+                    areaColor: '#323c48',
+                    borderColor: '#111'
+                },
+                emphasis: {
+                    areaColor: '#2a333d'
+                }
+            }
+        },
+        series: [{
+            name: '弱',
+            type: 'scatter',
+            coordinateSystem: 'geo',
+            symbolSize: 1,
+            large: true,
+            itemStyle: {
+                normal: {
+                    shadowBlur: 2,
+                    shadowColor: 'rgba(37, 140, 249, 0.8)',
+                    color: 'rgba(37, 140, 249, 0.8)'
+                }
+            },
+            data: []
+        }, {
+            name: '中',
+            type: 'scatter',
+            coordinateSystem: 'geo',
+            symbolSize: 1,
+            large: true,
+            itemStyle: {
+                normal: {
+                    shadowBlur: 2,
+                    shadowColor: 'rgba(14, 241, 242, 0.8)',
+                    color: 'rgba(14, 241, 242, 0.8)'
+                }
+            },
+            data: []
+        }, {
+            name: '强',
+            type: 'scatter',
+            coordinateSystem: 'geo',
+            symbolSize: 1,
+            large: true,
+            itemStyle: {
+                normal: {
+                    shadowBlur: 2,
+                    shadowColor: 'rgba(255, 255, 255, 0.8)',
+                    color: 'rgba(255, 255, 255, 0.8)'
+                }
+            },
+            data: []
+        }]
+    }
+    };
+    componentDidMount() {
+        weibo().then(weiboData => {
+            weiboData = weiboData.map(function (serieData, idx) {
+                var px = serieData[0] / 1000;
+                var py = serieData[1] / 1000;
+                var res = [[px, py]];
+
+                for (var i = 2; i < serieData.length; i += 2) {
+                    var dx = serieData[i] / 1000;
+                    var dy = serieData[i + 1] / 1000;
+                    var x = px + dx;
+                    var y = py + dy;
+                    res.push([x.toFixed(2), y.toFixed(2), 1]);
+
+                    px = x;
+                    py = y;
+                }
+                return res;
+            });
+            this.setState({
+                option: {
+                    series: [{data: weiboData[0]}, {data: weiboData[1]}, {data: weiboData[2]}]
+                }
+            })
+        });
+    }
+    render() {
+        return (
+            <ReactEcharts
+                option={this.state.option}
+                style={{height: '400px', width: '100%'}}
+                className={'react_for_echarts'}
+            />
+        )
+    }
+}
+
+export default EchartsScatter;

+ 54 - 0
src/components/charts/Recharts.jsx

@@ -0,0 +1,54 @@
+/**
+ * Created by hao.cheng on 2017/4/21.
+ */
+import React from 'react';
+import { Row, Col, Card } from 'antd';
+import RechartsSimpleLineChart from './RechartsSimpleLineChart';
+import RechartsBarChart from './RechartsBarChart';
+import RechartsRadialBarChart from './RechartsRadialBarChart';
+import RechartsRadarChart from './RechartsRadarChart';
+
+class Recharts extends React.Component {
+    render() {
+        return (
+            <div className="gutter-example">
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="基础线形图" bordered={false}>
+                                <RechartsSimpleLineChart />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="基础线形图" bordered={false}>
+                                <RechartsBarChart />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card title="基础线形图" bordered={false}>
+                                <RechartsRadialBarChart />
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card title="基础线形图" bordered={false}>
+                                <RechartsRadarChart />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default Recharts;

+ 34 - 0
src/components/charts/RechartsBarChart.jsx

@@ -0,0 +1,34 @@
+/**
+ * Created by hao.cheng on 2017/4/21.
+ */
+import React from 'react';
+import {BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer} from 'recharts';
+
+const data = [
+    {name: 'Page A', uv: 4000, pv: 2400, amt: 2400},
+    {name: 'Page B', uv: 3000, pv: 1398, amt: 2210},
+    {name: 'Page C', uv: 2000, pv: 9800, amt: 2290},
+    {name: 'Page D', uv: 2780, pv: 3908, amt: 2000},
+    {name: 'Page E', uv: 1890, pv: 4800, amt: 2181},
+    {name: 'Page F', uv: 2390, pv: 3800, amt: 2500},
+    {name: 'Page G', uv: 3490, pv: 4300, amt: 2100},
+];
+
+const RechartsBarChart = () => (
+    <ResponsiveContainer width="100%" height={300}>
+        <BarChart
+            data={data}
+            margin={{top: 5, right: 30, left: 20, bottom: 5}}
+        >
+            <XAxis dataKey="name" />
+            <YAxis />
+            <CartesianGrid strokeDasharray="3 3" />
+            <Tooltip />
+            <Legend />
+            <Bar dataKey="pv" fill="#8884d8" />
+            <Bar dataKey="uv" fill="#82ca9d" />
+        </BarChart>
+    </ResponsiveContainer>
+);
+
+export default RechartsBarChart;

+ 30 - 0
src/components/charts/RechartsRadarChart.jsx

@@ -0,0 +1,30 @@
+/**
+ * Created by hao.cheng on 2017/4/22.
+ */
+import React from 'react';
+import {Radar, RadarChart, PolarGrid, Legend,
+    PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer} from 'recharts';
+
+const data = [
+    { subject: 'Math', A: 120, B: 110, fullMark: 150 },
+    { subject: 'Chinese', A: 98, B: 130, fullMark: 150 },
+    { subject: 'English', A: 86, B: 130, fullMark: 150 },
+    { subject: 'Geography', A: 99, B: 100, fullMark: 150 },
+    { subject: 'Physics', A: 85, B: 90, fullMark: 150 },
+    { subject: 'History', A: 65, B: 85, fullMark: 150 },
+];
+
+const RechartsRadarChart = () => (
+        <ResponsiveContainer width="100%" height={300} >
+            <RadarChart outerRadius={90} data={data}>
+                <Radar name="Mike" dataKey="A" stroke="#8884d8" fill="#8884d8" fillOpacity={0.6} />
+                <Radar name="Lily" dataKey="B" stroke="#82ca9d" fill="#82ca9d" fillOpacity={0.6} />
+                <PolarGrid />
+                <Legend />
+                <PolarAngleAxis dataKey="subject" />
+                <PolarRadiusAxis angle={30} domain={[0, 150]} />
+            </RadarChart>
+        </ResponsiveContainer>
+);
+
+export default RechartsRadarChart;

+ 27 - 0
src/components/charts/RechartsRadialBarChart.jsx

@@ -0,0 +1,27 @@
+/**
+ * Created by hao.cheng on 2017/4/22.
+ */
+import React from 'react';
+import { RadialBarChart, RadialBar, Legend, Tooltip, ResponsiveContainer } from 'recharts';
+
+const data = [
+    {name: '18-24', uv: 31.47, pv: 2400, fill: '#8884d8'},
+    {name: '25-29', uv: 26.69, pv: 4567, fill: '#83a6ed'},
+    {name: '30-34', uv: 15.69, pv: 1398, fill: '#8dd1e1'},
+    {name: '35-39', uv: 8.22, pv: 9800, fill: '#82ca9d'},
+    {name: '40-49', uv: 8.63, pv: 3908, fill: '#a4de6c'},
+    {name: '50+', uv: 2.63, pv: 4800, fill: '#d0ed57'},
+    {name: 'unknow', uv: 6.67, pv: 4800, fill: '#ffc658'}
+];
+
+const RechartsRadialBarChart = () => (
+    <ResponsiveContainer width="100%" height={300} >
+        <RadialBarChart width={730} height={250} innerRadius="10%" outerRadius="80%" data={data}>
+            <RadialBar startAngle={90} endAngle={-270} minAngle={15} label background clockWise dataKey="uv" />
+            <Legend iconSize={10} width={120} height={140} layout="vertical" verticalAlign="middle" align="right" />
+            <Tooltip />
+        </RadialBarChart>
+    </ResponsiveContainer>
+);
+
+export default RechartsRadialBarChart;

+ 36 - 0
src/components/charts/RechartsSimpleLineChart.jsx

@@ -0,0 +1,36 @@
+/**
+ * Created by hao.cheng on 2017/4/21.
+ */
+import React from 'react';
+import {LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer} from 'recharts';
+
+
+const data = [
+    {name: 'Page A', uv: 4000, pv: 2400, amt: 2400},
+    {name: 'Page B', uv: 3000, pv: 1398, amt: 2210},
+    {name: 'Page C', uv: 2000, pv: 9800, amt: 2290},
+    {name: 'Page D', uv: 2780, pv: 3908, amt: 2000},
+    {name: 'Page E', uv: 1890, pv: 4800, amt: 2181},
+    {name: 'Page F', uv: 2390, pv: 3800, amt: 2500},
+    {name: 'Page G', uv: 3490, pv: 4300, amt: 2100},
+];
+
+const RechartsSimpleLineChart = () => (
+    <ResponsiveContainer width="100%" height={300}>
+        <LineChart
+            data={data}
+            margin={{top: 5, right: 30, left: 20, bottom: 5}}
+        >
+
+            <XAxis dataKey="name" />
+            <YAxis />
+            <CartesianGrid strokeDasharray="3 3" />
+            <Tooltip />
+            <Legend />
+            <Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{r: 8}} />
+            <Line type="monotone" dataKey="uv" stroke="#82ca9d" />
+        </LineChart>
+    </ResponsiveContainer>
+);
+
+export default RechartsSimpleLineChart;

+ 31 - 0
src/components/cssmodule/index.js

@@ -0,0 +1,31 @@
+/**
+ *
+ * 添加注释
+ * Created by SEELE on 2018/1/12
+ *
+ */
+import React, { Component } from 'react';
+import { Col, Card, Row } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+import styles from './index.module.less';
+
+class Cssmodule extends Component {
+    render() {
+        return (
+            <div>
+                <BreadcrumbCustom first="cssModule" />
+                <Row gutter={16}>
+                    <Col md={24}>
+                        <Card title="cssModule" bordered={false}>
+                            <div className={styles.header}>
+                                <p>Hello CssModule</p>
+                            </div>
+                        </Card>
+                    </Col>
+                </Row>
+            </div>
+        );
+    }
+}
+
+export default Cssmodule;

+ 32 - 0
src/components/cssmodule/index.module.less

@@ -0,0 +1,32 @@
+@font-face {
+  font-family: 'Monoton';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Monoton'), local('Monoton-Regular'), url(../../style/font/y6oxFxU60dYw9khW6q8jGw.woff2) format('woff2');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
+}
+.header {
+  font-size: 7em;
+  width: 100%;
+  height: 500px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #fff;
+  font-family: Monoton;
+  p {
+    animation: neon1 1.5s ease-in-out infinite alternate;
+    &:hover {
+      color: #FF1177;
+      animation: none;
+    }
+  }
+}
+@keyframes neon1 {
+  from {
+    text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #FF1177, 0 0 70px #FF1177, 0 0 80px #FF1177, 0 0 100px #FF1177, 0 0 150px #FF1177;
+  }
+  to {
+    text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #FF1177, 0 0 35px #FF1177, 0 0 40px #FF1177, 0 0 50px #FF1177, 0 0 75px #FF1177;
+  }
+}

+ 174 - 0
src/components/dashboard/Dashboard.jsx

@@ -0,0 +1,174 @@
+/**
+ * Created by hao.cheng on 2017/5/3.
+ */
+import React from 'react';
+import { Row, Col, Card, Timeline, Icon } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+import EchartsViews from './EchartsViews';
+import EchartsProjects from './EchartsProjects';
+import b1 from '../../style/imgs/b1.jpg';
+
+
+class Dashboard extends React.Component {
+    render() {
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom />
+                <Row gutter={10}>
+                    <Col className="gutter-row" md={4}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <div className="clear y-center">
+                                    <div className="pull-left mr-m">
+                                        <Icon type="heart" className="text-2x text-danger" />
+                                    </div>
+                                    <div className="clear">
+                                        <div className="text-muted">收藏</div>
+                                        <h2>301</h2>
+                                    </div>
+                                </div>
+                            </Card>
+                        </div>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <div className="clear y-center">
+                                    <div className="pull-left mr-m">
+                                        <Icon type="cloud" className="text-2x" />
+                                    </div>
+                                    <div className="clear">
+                                        <div className="text-muted">云数据</div>
+                                        <h2>30122</h2>
+                                    </div>
+                                </div>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={4}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <div className="clear y-center">
+                                    <div className="pull-left mr-m">
+                                        <Icon type="camera" className="text-2x text-info" />
+                                    </div>
+                                    <div className="clear">
+                                        <div className="text-muted">照片</div>
+                                        <h2>802</h2>
+                                    </div>
+                                </div>
+                            </Card>
+                        </div>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <div className="clear y-center">
+                                    <div className="pull-left mr-m">
+                                        <Icon type="mail" className="text-2x text-success" />
+                                    </div>
+                                    <div className="clear">
+                                        <div className="text-muted">邮件</div>
+                                        <h2>102</h2>
+                                    </div>
+                                </div>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={16}>
+                        <div className="gutter-box">
+                            <Card bordered={false} className={'no-padding'}>
+                                <EchartsProjects />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+                <Row gutter={10}>
+                    <Col className="gutter-row" md={8}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <div className="pb-m">
+                                    <h3>任务</h3>
+                                    <small>10个已经完成,2个待完成,1个正在进行中</small>
+                                </div>
+                                <span className="card-tool"><Icon type="sync" /></span>
+                                <Timeline>
+                                    <Timeline.Item color="green">新版本迭代会</Timeline.Item>
+                                    <Timeline.Item color="green">完成网站设计初版</Timeline.Item>
+                                    <Timeline.Item color="red">
+                                        <p>联调接口</p>
+                                        <p>功能验收</p>
+                                    </Timeline.Item>
+
+                                    <Timeline.Item color="#108ee9">
+                                        <p>登录功能设计</p>
+                                        <p>权限验证</p>
+                                        <p>页面排版</p>
+                                    </Timeline.Item>
+                                </Timeline>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={8}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <div className="pb-m">
+                                    <h3>消息栏</h3>
+                                </div>
+                                <span className="card-tool"><Icon type="sync" /></span>
+                                <ul className="list-group no-border">
+                                    <li className="list-group-item">
+                                        <span className="pull-left w-40 mr-m">
+                                            <img src={b1} className="img-responsive img-circle" alt="test" />
+                                        </span>
+                                        <div className="clear">
+                                            <span className="block">鸣人</span>
+                                            <span className="text-muted">终于当上火影了!</span>
+                                        </div>
+                                    </li>
+                                    <li className="list-group-item">
+                                        <span className="pull-left w-40 mr-m">
+                                            <img src={b1} className="img-responsive img-circle" alt="test" />
+                                        </span>
+                                        <div className="clear">
+                                            <span className="block">佐助</span>
+                                            <span className="text-muted">吊车尾~~</span>
+                                        </div>
+                                    </li>
+                                    <li className="list-group-item">
+                                        <span className="pull-left w-40 mr-m">
+                                            <img src={b1} className="img-responsive img-circle" alt="test" />
+                                        </span>
+                                        <div className="clear">
+                                            <span className="block">小樱</span>
+                                            <span className="text-muted">佐助,你好帅!</span>
+                                        </div>
+                                    </li>
+                                    <li className="list-group-item">
+                                        <span className="pull-left w-40 mr-m">
+                                            <img src={b1} className="img-responsive img-circle" alt="test" />
+                                        </span>
+                                        <div className="clear">
+                                            <span className="block">雏田</span>
+                                            <span className="text-muted">鸣人君。。。那个。。。我。。喜欢你..</span>
+                                        </div>
+                                    </li>
+                                </ul>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={8}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <div className="pb-m">
+                                    <h3>访问量统计</h3>
+                                    <small>最近7天用户访问量</small>
+                                </div>
+                                <span className="card-tool"><Icon type="sync" /></span>
+                                <EchartsViews />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default Dashboard;

+ 116 - 0
src/components/dashboard/EchartsProjects.jsx

@@ -0,0 +1,116 @@
+/**
+ * Created by hao.cheng on 2017/5/5.
+ */
+import React from 'react';
+import ReactEcharts from 'echarts-for-react';
+
+let xAxisData = [];
+let data = [];
+for (let i = 0; i < 50; i++) {
+    xAxisData.push(i);
+    data.push(Math.ceil((Math.cos(i / 5) * (i / 5) + i / 6) * 5) + 10);
+}
+
+const option = {
+    title: {
+        text: '最近50天每天项目完成情况',
+        left: 'center',
+        textStyle: {
+            color: '#ccc',
+            fontSize: 10
+        }
+    },
+    backgroundColor: '#08263a',
+    xAxis: [{
+        show: true,
+        data: xAxisData,
+        axisLabel: {
+            textStyle: {
+                color: '#ccc'
+            }
+        }
+    }, {
+        show: false,
+        data: xAxisData
+    }],
+    tooltip: {},
+    visualMap: {
+        show: false,
+        min: 0,
+        max: 50,
+        dimension: 0,
+        inRange: {
+            color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
+        }
+    },
+    yAxis: {
+        axisLine: {
+            show: false
+        },
+        axisLabel: {
+            textStyle: {
+                color: '#ccc'
+            }
+        },
+        splitLine: {
+            show: true,
+            lineStyle: {
+                color: '#08263f'
+            }
+        },
+        axisTick: {
+            show: false
+        }
+    },
+    series: [
+        {
+        name: 'Simulate Shadow',
+        type: 'line',
+        data: data,
+        z: 2,
+        showSymbol: false,
+        animationDelay: 0,
+        animationEasing: 'linear',
+        animationDuration: 1200,
+        lineStyle: {
+            normal: {
+                color: 'transparent'
+            }
+        },
+        areaStyle: {
+            normal: {
+                color: '#08263a',
+                shadowBlur: 50,
+                shadowColor: '#000'
+            }
+        }
+    }, {
+        name: '完成项目数',
+        type: 'bar',
+        data: data,
+        xAxisIndex: 1,
+        z: 3,
+        itemStyle: {
+            normal: {
+                barBorderRadius: 5
+            }
+        }
+    }],
+    animationEasing: 'elasticOut',
+    animationEasingUpdate: 'elasticOut',
+    animationDelay: function (idx) {
+        return idx * 20;
+    },
+    animationDelayUpdate: function (idx) {
+        return idx * 20;
+    }
+};
+const EchartsProjects = () => (
+    <ReactEcharts
+        option={option}
+        style={{height: '212px', width: '100%'}}
+        className={'react_for_echarts'}
+    />
+);
+
+export default EchartsProjects;

+ 121 - 0
src/components/dashboard/EchartsViews.jsx

@@ -0,0 +1,121 @@
+/**
+ * Created by hao.cheng on 2017/5/5.
+ */
+import React from 'react';
+import ReactEcharts from 'echarts-for-react';
+import echarts from 'echarts';
+
+const option = {
+    title: {
+        text: '最近7天用户访问量',
+        left: '50%',
+        show: false,
+        textAlign: 'center'
+    },
+    tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+            lineStyle: {
+                color: '#ddd'
+            }
+        },
+        backgroundColor: 'rgba(255,255,255,1)',
+        padding: [5, 10],
+        textStyle: {
+            color: '#7588E4',
+        },
+        extraCssText: 'box-shadow: 0 0 5px rgba(0,0,0,0.3)'
+    },
+    legend: {
+        right: 20,
+        orient: 'vertical',
+    },
+    xAxis: {
+        type: 'category',
+        data: ['2017-05-01', '2017-05-02', '2017-05-03', '2017-05-04', '2017-05-05', '2017-05-06','2017-05-07'],
+        boundaryGap: false,
+        splitLine: {
+            show: true,
+            interval: 'auto',
+            lineStyle: {
+                color: ['#D4DFF5']
+            }
+        },
+        axisTick: {
+            show: false
+        },
+        axisLine: {
+            lineStyle: {
+                color: '#609ee9'
+            }
+        },
+        axisLabel: {
+            margin: 10,
+            textStyle: {
+                fontSize: 10
+            }
+        }
+    },
+    yAxis: {
+        type: 'value',
+        splitLine: {
+            lineStyle: {
+                color: ['#D4DFF5']
+            }
+        },
+        axisTick: {
+            show: false
+        },
+        axisLine: {
+            lineStyle: {
+                color: '#609ee9'
+            }
+        },
+        axisLabel: {
+            margin: 0,
+            textStyle: {
+                fontSize: 8
+            }
+        }
+    },
+    series: [{
+        name: '昨日',
+        type: 'line',
+        smooth: true,
+        showSymbol: false,
+        symbol: 'circle',
+        symbolSize: 6,
+        data: ['1200', '1400', '808', '811', '626', '488', '1600'],
+        areaStyle: {
+            normal: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
+                    offset: 0,
+                    color: 'rgba(216, 244, 247,1)'
+                }, {
+                    offset: 1,
+                    color: 'rgba(216, 244, 247,1)'
+                }], false)
+            }
+        },
+        itemStyle: {
+            normal: {
+                color: '#58c8da'
+            }
+        },
+        lineStyle: {
+            normal: {
+                width: 3
+            }
+        }
+    }]
+};
+
+const EchartsViews = () => (
+    <ReactEcharts
+        option={option}
+        style={{height: '350px', width: '100%'}}
+        className={'react_for_echarts'}
+    />
+);
+
+export default EchartsViews;

+ 39 - 0
src/components/extension/QueryParams.js

@@ -0,0 +1,39 @@
+/*
+ * File: QueryParams.js
+ * Desc: query参数demo
+ * File Created: 2018-11-25 23:18:09
+ * Author: chenghao
+ * Copyright 2018 - present, chenghao
+ */
+import React, { Component } from 'react';
+import { Row, Col, Card } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+
+class QueryParams extends Component {
+    render() {
+        const { query } = this.props;
+        return (
+            <div>
+                <BreadcrumbCustom first="queryParams" />
+                <Row gutter={16}>
+                    <Col md={24}>
+                        <Card title="query参数Demo" bordered={false}>
+                            <div>参数1: {query.param1}</div>
+                            <div>参数2: {query.param2}</div>
+                            <div>
+                                其他参数:{' '}
+                                {query.others || (
+                                    <a href="#/app/extension/queryParams?others=nothing">
+                                        点击查看
+                                    </a>
+                                )}
+                            </div>
+                        </Card>
+                    </Col>
+                </Row>
+            </div>
+        );
+    }
+}
+
+export default QueryParams;

+ 255 - 0
src/components/forms/BasicForm.jsx

@@ -0,0 +1,255 @@
+/**
+ * Created by hao.cheng on 2017/4/13.
+ */
+import React, { Component } from 'react';
+import { Card, Form, Input, Tooltip, Icon, Cascader, Select, Row, Col, Checkbox, Button } from 'antd';
+import LoginForm from './LoginForm';
+import ModalForm from './ModalForm';
+import HorizontalForm from './HorizontalForm';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+const FormItem = Form.Item;
+const Option = Select.Option;
+
+const residences = [{
+    value: 'zhejiang',
+    label: 'Zhejiang',
+    children: [{
+        value: 'hangzhou',
+        label: 'Hangzhou',
+        children: [{
+            value: 'xihu',
+            label: 'West Lake',
+        }],
+    }],
+}, {
+    value: 'jiangsu',
+    label: 'Jiangsu',
+    children: [{
+        value: 'nanjing',
+        label: 'Nanjing',
+        children: [{
+            value: 'zhonghuamen',
+            label: 'Zhong Hua Men',
+        }],
+    }],
+}];
+
+class BasicForms extends Component {
+    state = {
+        confirmDirty: false,
+    };
+    handleSubmit = (e) => {
+        e.preventDefault();
+        this.props.form.validateFieldsAndScroll((err, values) => {
+            if (!err) {
+                console.log('Received values of form: ', values);
+            }
+        });
+    };
+    handleConfirmBlur = (e) => {
+        const value = e.target.value;
+        this.setState({ confirmDirty: this.state.confirmDirty || !!value });
+    };
+    checkPassword = (rule, value, callback) => {
+        const form = this.props.form;
+        if (value && value !== form.getFieldValue('password')) {
+            callback('Two passwords that you enter is inconsistent!');
+        } else {
+            callback();
+        }
+    };
+    checkConfirm = (rule, value, callback) => {
+        const form = this.props.form;
+        if (value && this.state.confirmDirty) {
+            form.validateFields(['confirm'], { force: true });
+        }
+        callback();
+    };
+    render() {
+        const { getFieldDecorator } = this.props.form;
+        const formItemLayout = {
+            labelCol: {
+                xs: { span: 24 },
+                sm: { span: 8 },
+            },
+            wrapperCol: {
+                xs: { span: 24 },
+                sm: { span: 14 },
+            },
+        };
+        const tailFormItemLayout = {
+            wrapperCol: {
+                xs: {
+                    span: 24,
+                    offset: 0,
+                },
+                sm: {
+                    span: 14,
+                    offset: 8,
+                },
+            },
+        };
+        const prefixSelector = getFieldDecorator('prefix', {
+            initialValue: '86',
+        })(
+            <Select className="icp-selector" style={{width: '60px'}}>
+                <Option value="86">+86</Option>
+            </Select>
+        );
+        return (
+        <div className="gutter-example">
+            <BreadcrumbCustom first="表单" second="基础表单" />
+            <Row gutter={16}>
+                <Col className="gutter-row" md={12}>
+                    <div className="gutter-box">
+                        <Card title="注册表单" bordered={false}>
+                            <Form onSubmit={this.handleSubmit}>
+                                <FormItem
+                                    {...formItemLayout}
+                                    label="邮箱"
+                                    hasFeedback
+                                >
+                                    {getFieldDecorator('email', {
+                                        rules: [{
+                                            type: 'email', message: '请输入合理的邮箱地址!',
+                                        }, {
+                                            required: true, message: '请输入邮箱地址!',
+                                        }],
+                                    })(
+                                        <Input />
+                                    )}
+                                </FormItem>
+                                <FormItem
+                                    {...formItemLayout}
+                                    label="密码"
+                                    hasFeedback
+                                >
+                                    {getFieldDecorator('password', {
+                                        rules: [{
+                                            required: true, message: '请输入密码!',
+                                        }, {
+                                            validator: this.checkConfirm,
+                                        }],
+                                    })(
+                                        <Input type="password" />
+                                    )}
+                                </FormItem>
+                                <FormItem
+                                    {...formItemLayout}
+                                    label="确认密码"
+                                    hasFeedback
+                                >
+                                    {getFieldDecorator('confirm', {
+                                        rules: [{
+                                            required: true, message: '请确认你的密码!',
+                                        }, {
+                                            validator: this.checkPassword,
+                                        }],
+                                    })(
+                                        <Input type="password" onBlur={this.handleConfirmBlur} />
+                                    )}
+                                </FormItem>
+                                <FormItem
+                                    {...formItemLayout}
+                                    label={(
+                                        <span>
+                                            昵称&nbsp;
+                                            <Tooltip title="别人怎么称呼你?">
+                                            <Icon type="question-circle-o" />
+                                          </Tooltip>
+                                        </span>
+                                    )}
+                                    hasFeedback
+                                >
+                                    {getFieldDecorator('nickname', {
+                                        rules: [{ required: true, message: '请输入昵称!', whitespace: true }],
+                                    })(
+                                        <Input />
+                                    )}
+                                </FormItem>
+                                <FormItem
+                                    {...formItemLayout}
+                                    label="常住地址"
+                                >
+                                    {getFieldDecorator('residence', {
+                                        initialValue: ['zhejiang', 'hangzhou', 'xihu'],
+                                        rules: [{ type: 'array', required: true, message: '请选择你的常住地址!' }],
+                                    })(
+                                        <Cascader options={residences} />
+                                    )}
+                                </FormItem>
+                                <FormItem
+                                    {...formItemLayout}
+                                    label="电话号码"
+                                >
+                                    {getFieldDecorator('phone', {
+                                        rules: [{ required: true, message: '请输入你的电话号码!' }],
+                                    })(
+                                        <Input addonBefore={prefixSelector} />
+
+                                    )}
+                                </FormItem>
+                                <FormItem
+                                    {...formItemLayout}
+                                    label="验证码"
+                                    extra="我们必须确认你不是机器人."
+                                >
+                                    <Row gutter={8}>
+                                        <Col span={12}>
+                                            {getFieldDecorator('captcha', {
+                                                rules: [{ required: true, message: '请输入你获取的验证码!' }],
+                                            })(
+                                                <Input size="large" />
+                                            )}
+                                        </Col>
+                                        <Col span={12}>
+                                            <Button size="large">获取验证码</Button>
+                                        </Col>
+                                    </Row>
+                                </FormItem>
+                                <FormItem {...tailFormItemLayout} style={{ marginBottom: 8 }}>
+                                    {getFieldDecorator('agreement', {
+                                        valuePropName: 'checked',
+                                    })(
+                                        <Checkbox>我已经阅读过 <span>协议</span></Checkbox>
+                                    )}
+                                </FormItem>
+                                <FormItem {...tailFormItemLayout}>
+                                    <Button type="primary" htmlType="submit" size="large">注册</Button>
+                                </FormItem>
+                            </Form>
+                        </Card>
+                    </div>
+                </Col>
+                <Col className="gutter-row" md={12}>
+                    <div className="gutter-box">
+                        <Card title="登录表单" bordered={false}>
+                            <LoginForm />
+                        </Card>
+                    </div>
+                </Col>
+            </Row>
+            <Row gutter={16}>
+                <Col className="gutter-row" md={14}>
+                    <div className="gutter-box">
+                        <Card title="水平表单" bordered={false}>
+                            <HorizontalForm />
+                        </Card>
+                    </div>
+                </Col>
+                <Col className="gutter-row" md={10}>
+                    <div className="gutter-box">
+                        <Card title="弹层表单" bordered={false}>
+                            <ModalForm />
+                        </Card>
+                    </div>
+                </Col>
+            </Row>
+        </div>
+        )
+    }
+}
+
+const BasicForm = Form.create()(BasicForms);
+
+export default BasicForm;

+ 69 - 0
src/components/forms/HorizontalForm.jsx

@@ -0,0 +1,69 @@
+/**
+ * Created by hao.cheng on 2017/4/15.
+ */
+import React, { Component } from 'react';
+
+import { Form, Icon, Input, Button } from 'antd';
+const FormItem = Form.Item;
+
+function hasErrors(fieldsError) {
+    return Object.keys(fieldsError).some(field => fieldsError[field]);
+}
+
+class HorizontalLoginForm extends Component {
+    componentDidMount() {
+        // To disabled submit button at the beginning.
+        this.props.form.validateFields();
+    }
+    handleSubmit = (e) => {
+        e.preventDefault();
+        this.props.form.validateFields((err, values) => {
+            if (!err) {
+                console.log('Received values of form: ', values);
+            }
+        });
+    };
+    render() {
+        const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
+
+        // Only show error after a field is touched.
+        const userNameError = isFieldTouched('userName') && getFieldError('userName');
+        const passwordError = isFieldTouched('password') && getFieldError('password');
+        return (
+            <Form layout="inline" onSubmit={this.handleSubmit}>
+                <FormItem
+                    validateStatus={userNameError ? 'error' : ''}
+                    help={userNameError || ''}
+                >
+                    {getFieldDecorator('userName', {
+                        rules: [{ required: true, message: '请输入用户名!' }],
+                    })(
+                        <Input prefix={<Icon type="user" style={{ fontSize: 13 }} />} placeholder="用户名" />
+                    )}
+                </FormItem>
+                <FormItem
+                    validateStatus={passwordError ? 'error' : ''}
+                    help={passwordError || ''}
+                >
+                    {getFieldDecorator('password', {
+                        rules: [{ required: true, message: '请输入密码!' }],
+                    })(
+                        <Input prefix={<Icon type="lock" style={{ fontSize: 13 }} />} type="password" placeholder="密码" />
+                    )}
+                </FormItem>
+                <FormItem>
+                    <Button
+                        type="primary"
+                        htmlType="submit"
+                        disabled={hasErrors(getFieldsError())}
+                    >
+                       登录
+                    </Button>
+                </FormItem>
+            </Form>
+        );
+    }
+}
+
+
+export default Form.create()(HorizontalLoginForm);

+ 55 - 0
src/components/forms/LoginForm.jsx

@@ -0,0 +1,55 @@
+/**
+ * Created by hao.cheng on 2017/4/14.
+ */
+import React, { Component } from 'react';
+import { Form, Icon, Input, Button, Checkbox } from 'antd';
+const FormItem = Form.Item;
+
+class NormalLoginForm extends Component {
+    handleSubmit = (e) => {
+        e.preventDefault();
+        this.props.form.validateFields((err, values) => {
+            if (!err) {
+                console.log('Received values of form: ', values);
+            }
+        });
+    };
+    render() {
+        const { getFieldDecorator } = this.props.form;
+        return (
+            <Form onSubmit={this.handleSubmit} style={{maxWidth: '300px'}}>
+                <FormItem>
+                    {getFieldDecorator('userName', {
+                        rules: [{ required: true, message: '请输入用户名!' }],
+                    })(
+                        <Input prefix={<Icon type="user" style={{ fontSize: 13 }} />} placeholder="用户名" />
+                    )}
+                </FormItem>
+                <FormItem>
+                    {getFieldDecorator('password', {
+                        rules: [{ required: true, message: '请输入密码!' }],
+                    })(
+                        <Input prefix={<Icon type="lock" style={{ fontSize: 13 }} />} type="password" placeholder="密码" />
+                    )}
+                </FormItem>
+                <FormItem>
+                    {getFieldDecorator('remember', {
+                        valuePropName: 'checked',
+                        initialValue: true,
+                    })(
+                        <Checkbox>记住我</Checkbox>
+                    )}
+                    <span className="login-form-forgot" style={{float: 'right'}}>忘记密码</span>
+                    <Button type="primary" htmlType="submit" className="login-form-button" style={{width: '100%'}}>
+                        登录
+                    </Button>
+                    或 <span>现在就去注册!</span>
+                </FormItem>
+            </Form>
+        );
+    }
+}
+
+const LoginForm = Form.create()(NormalLoginForm);
+
+export default LoginForm;

+ 87 - 0
src/components/forms/ModalForm.jsx

@@ -0,0 +1,87 @@
+/**
+ * Created by hao.cheng on 2017/4/15.
+ */
+import React, { Component } from 'react';
+import { Button, Modal, Form, Input, Radio } from 'antd';
+const FormItem = Form.Item;
+
+const CollectionCreateForm = Form.create()(
+    (props) => {
+        const { visible, onCancel, onCreate, form } = props;
+        const { getFieldDecorator } = form;
+        return (
+            <Modal
+                visible={visible}
+                title="创建新收藏"
+                okText="创建"
+                onCancel={onCancel}
+                onOk={onCreate}
+            >
+                <Form layout="vertical">
+                    <FormItem label="标题">
+                        {getFieldDecorator('title', {
+                            rules: [{ required: true, message: '请输入收藏的标题!' }],
+                        })(
+                            <Input />
+                        )}
+                    </FormItem>
+                    <FormItem label="描述">
+                        {getFieldDecorator('description')(<Input type="textarea" />)}
+                    </FormItem>
+                    <FormItem className="collection-create-form_last-form-item" style={{marginBottom: 0}}>
+                        {getFieldDecorator('modifier', {
+                            initialValue: 'public',
+                        })(
+                            <Radio.Group>
+                                <Radio value="public">公开</Radio>
+                                <Radio value="private">私有</Radio>
+                            </Radio.Group>
+                        )}
+                    </FormItem>
+                </Form>
+            </Modal>
+        );
+    }
+);
+
+class ModalForm extends Component {
+    state = {
+        visible: false,
+    };
+    showModal = () => {
+        this.setState({ visible: true });
+    };
+    handleCancel = () => {
+        this.setState({ visible: false });
+    };
+    handleCreate = () => {
+        const form = this.form;
+        form.validateFields((err, values) => {
+            if (err) {
+                return;
+            }
+
+            console.log('Received values of form: ', values);
+            form.resetFields();
+            this.setState({ visible: false });
+        });
+    };
+    saveFormRef = (form) => {
+        this.form = form;
+    };
+    render() {
+        return (
+            <div>
+                <Button type="primary" onClick={this.showModal}>新建收藏</Button>
+                <CollectionCreateForm
+                    ref={this.saveFormRef}
+                    visible={this.state.visible}
+                    onCancel={this.handleCancel}
+                    onCreate={this.handleCreate}
+                />
+            </div>
+        );
+    }
+}
+
+export default ModalForm;

+ 62 - 0
src/components/index.js

@@ -0,0 +1,62 @@
+/**
+ * 路由组件出口文件
+ * yezi 2018年6月24日
+ */
+import Loadable from 'react-loadable';
+import Loading from './widget/Loading';
+import BasicForm from './forms/BasicForm';
+import BasicTable from './tables/BasicTables';
+import AdvancedTable from './tables/AdvancedTables';
+import AsynchronousTable from './tables/AsynchronousTable';
+import Echarts from './charts/Echarts';
+import Recharts from './charts/Recharts';
+import Icons from './ui/Icons';
+import Buttons from './ui/Buttons';
+import Spins from './ui/Spins';
+import Modals from './ui/Modals';
+import Notifications from './ui/Notifications';
+import Tabs from './ui/Tabs';
+import Banners from './ui/banners';
+import Drags from './ui/Draggable';
+import Dashboard from './dashboard/Dashboard';
+import Gallery from './ui/Gallery';
+import BasicAnimations from './animation/BasicAnimations';
+import ExampleAnimations from './animation/ExampleAnimations';
+import AuthBasic from './auth/Basic';
+import RouterEnter from './auth/RouterEnter';
+import Cssmodule from './cssmodule';
+import MapUi from './ui/map';
+import QueryParams from './extension/QueryParams';
+
+const WysiwygBundle = Loadable({
+    // 按需加载富文本配置
+    loader: () => import('./ui/Wysiwyg'),
+    loading: Loading,
+});
+
+export default {
+    BasicForm,
+    BasicTable,
+    AdvancedTable,
+    AsynchronousTable,
+    Echarts,
+    Recharts,
+    Icons,
+    Buttons,
+    Spins,
+    Modals,
+    Notifications,
+    Tabs,
+    Banners,
+    Drags,
+    Dashboard,
+    Gallery,
+    BasicAnimations,
+    ExampleAnimations,
+    AuthBasic,
+    RouterEnter,
+    WysiwygBundle,
+    Cssmodule,
+    MapUi,
+    QueryParams,
+};

+ 85 - 0
src/components/pages/Login.jsx

@@ -0,0 +1,85 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+import React from 'react';
+import { Form, Icon, Input, Button, Checkbox } from 'antd';
+import { PwaInstaller } from '../widget';
+import { connectAlita } from 'redux-alita';
+
+const FormItem = Form.Item;
+
+class Login extends React.Component {
+    componentDidMount() {
+        const { setAlitaState } = this.props;
+        setAlitaState({ stateName: 'auth', data: null });
+    }
+    componentDidUpdate(prevProps) { // React 16.3+弃用componentWillReceiveProps
+        const { auth: nextAuth = {}, history } = this.props;
+        // const { history } = this.props;
+        if (nextAuth.data && nextAuth.data.uid) { // 判断是否登陆
+            localStorage.setItem('user', JSON.stringify(nextAuth.data));
+            history.push('/');
+        }
+    }
+    handleSubmit = (e) => {
+        e.preventDefault();
+        this.props.form.validateFields((err, values) => {
+            if (!err) {
+                console.log('Received values of form: ', values);
+                const { setAlitaState } = this.props;
+                if (values.userName === 'admin' && values.password === 'admin') setAlitaState({ funcName: 'admin', stateName: 'auth' });
+                if (values.userName === 'guest' && values.password === 'guest') setAlitaState({ funcName: 'guest', stateName: 'auth' });
+            }
+        });
+    };
+    gitHub = () => {
+        window.location.href = 'https://github.com/login/oauth/authorize?client_id=792cdcd244e98dcd2dee&redirect_uri=http://localhost:3006/&scope=user&state=reactAdmin';
+    };
+    render() {
+        const { getFieldDecorator } = this.props.form;
+        return (
+            <div className="login">
+                <div className="login-form" >
+                    <div className="login-logo">
+                        <span>React Admin</span>
+                        <PwaInstaller />
+                    </div>
+                    <Form onSubmit={this.handleSubmit} style={{maxWidth: '300px'}}>
+                        <FormItem>
+                            {getFieldDecorator('userName', {
+                                rules: [{ required: true, message: '请输入用户名!' }],
+                            })(
+                                <Input prefix={<Icon type="user" style={{ fontSize: 13 }} />} placeholder="管理员输入admin, 游客输入guest" />
+                            )}
+                        </FormItem>
+                        <FormItem>
+                            {getFieldDecorator('password', {
+                                rules: [{ required: true, message: '请输入密码!' }],
+                            })(
+                                <Input prefix={<Icon type="lock" style={{ fontSize: 13 }} />} type="password" placeholder="管理员输入admin, 游客输入guest" />
+                            )}
+                        </FormItem>
+                        <FormItem>
+                            {getFieldDecorator('remember', {
+                                valuePropName: 'checked',
+                                initialValue: true,
+                            })(
+                                <Checkbox>记住我</Checkbox>
+                            )}
+                            <span className="login-form-forgot" href="" style={{float: 'right'}}>忘记密码</span>
+                            <Button type="primary" htmlType="submit" className="login-form-button" style={{width: '100%'}}>
+                                登录
+                            </Button>
+                            <p style={{display: 'flex', justifyContent: 'space-between'}}>
+                                <span >或 现在就去注册!</span>
+                                <span onClick={this.gitHub} ><Icon type="github" />(第三方登录)</span>
+                            </p>
+                        </FormItem>
+                    </Form>
+                </div>
+            </div>
+        );
+    }
+}
+
+export default connectAlita(['auth'])(Form.create()(Login));

+ 24 - 0
src/components/pages/NotFound.jsx

@@ -0,0 +1,24 @@
+/**
+ * Created by hao.cheng on 2017/5/7.
+ */
+import React from 'react';
+import img from '../../style/imgs/404.png';
+
+
+class NotFound extends React.Component {
+    state = {
+        animated: ''
+    };
+    enter = () => {
+        this.setState({animated: 'hinge'})
+    };
+    render() {
+        return (
+            <div className="center" style={{height: '100%', background: '#ececec', overflow: 'hidden'}}>
+                <img src={img} alt="404" className={`animated swing ${this.state.animated}`} onMouseEnter={this.enter} />
+            </div>
+        )
+    }
+}
+
+export default NotFound;

+ 46 - 0
src/components/tables/AdvancedTables.jsx

@@ -0,0 +1,46 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+import React from 'react';
+import { Row, Col, Card } from 'antd';
+import FixedTable from './FixedTable';
+import ExpandedTable from './ExpandedTable';
+import EditableTable from './EditableTable';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+
+class AdvancedTables extends React.Component {
+    render() {
+        return (
+            <div className="gutter-example">
+                <BreadcrumbCustom first="表格" second="高级表格" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="固定列" bordered={false}>
+                                <FixedTable />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card title="可展开" bordered={false}>
+                                <ExpandedTable />
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card title="可编辑" bordered={false}>
+                                <EditableTable />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        );
+    }
+}
+
+export default AdvancedTables;

+ 80 - 0
src/components/tables/AsynchronousTable.jsx

@@ -0,0 +1,80 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+import React from 'react';
+import { Table, Button, Row, Col, Card } from 'antd';
+import { getBbcNews } from '../../axios';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+
+const columns = [{
+    title: '新闻标题',
+    dataIndex: 'title',
+    width: 100,
+    render: (text, record) => <a href={record.url} target="_blank" rel="noopener noreferrer">{text}</a>
+}, {
+    title: '作者',
+    dataIndex: 'author',
+    width: 80
+}, {
+    title: '发布时间',
+    dataIndex: 'publishedAt',
+    width: 80
+}, {
+    title: '描述',
+    dataIndex: 'description',
+    width: 200
+}];
+
+class AsynchronousTable extends React.Component {
+    state = {
+        selectedRowKeys: [], // Check here to configure the default column
+        loading: false,
+        data: []
+    };
+    componentDidMount() {
+        this.start();
+    }
+    start = () => {
+        this.setState({ loading: true });
+        getBbcNews().then(({ articles }) => {
+            this.setState({
+                data: articles,
+                loading: false
+            });
+        });
+    };
+    onSelectChange = (selectedRowKeys) => {
+        console.log('selectedRowKeys changed: ', selectedRowKeys);
+        this.setState({ selectedRowKeys });
+    };
+    render() {
+        const { loading, selectedRowKeys } = this.state;
+        const rowSelection = {
+            selectedRowKeys,
+            onChange: this.onSelectChange,
+        };
+        const hasSelected = selectedRowKeys.length > 0;
+        return (
+            <div className="gutter-example">
+                <BreadcrumbCustom first="表格" second="异步表格" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="异步表格--BBC新闻今日热门" bordered={false}>
+                                <div style={{ marginBottom: 16 }}>
+                                    <Button type="primary" onClick={this.start}
+                                            disabled={loading} loading={loading}
+                                    >Reload</Button>
+                                    <span style={{ marginLeft: 8 }}>{hasSelected ? `Selected ${selectedRowKeys.length} items` : ''}</span>
+                                </div>
+                                <Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        );
+    }
+}
+
+export default AsynchronousTable;

+ 57 - 0
src/components/tables/BasicTable.jsx

@@ -0,0 +1,57 @@
+/**
+ * Created by hao.cheng on 2017/4/15.
+ */
+import React from 'react';
+import { Table, Icon, Button } from 'antd';
+
+const columns = [{
+    title: 'Name',
+    dataIndex: 'name',
+    key: 'name',
+    render: text => <span>{text}</span>,
+}, {
+    title: 'Age',
+    dataIndex: 'age',
+    key: 'age',
+}, {
+    title: 'Address',
+    dataIndex: 'address',
+    key: 'address',
+}, {
+    title: 'Action',
+    key: 'action',
+    render: (text, record) => (
+        <span>
+            <Button>Action 一 {record.name}</Button>
+            <span className="ant-divider" />
+            <Button>Delete</Button>
+            <span className="ant-divider" />
+            <Button className="ant-dropdown-link">
+                More actions <Icon type="down" />
+            </Button>
+        </span>
+    ),
+}];
+
+const data = [{
+    key: '1',
+    name: 'John Brown',
+    age: 32,
+    address: 'New York No. 1 Lake Park',
+}, {
+    key: '2',
+    name: 'Jim Green',
+    age: 42,
+    address: 'London No. 1 Lake Park',
+}, {
+    key: '3',
+    name: 'Joe Black',
+    age: 32,
+    address: 'Sidney No. 1 Lake Park',
+}];
+
+const BasicTable = () => (
+    <Table columns={columns} dataSource={data} />
+);
+
+export default BasicTable;

+ 52 - 0
src/components/tables/BasicTables.jsx

@@ -0,0 +1,52 @@
+/**
+ * Created by hao.cheng on 2017/4/15.
+ */
+import React from 'react';
+import { Row, Col, Card } from 'antd';
+import BasicTable from './BasicTable';
+import SelectTable from './SelectTable';
+import SortTable from './SortTable';
+import SearchTable from './SearchTable';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+
+const BasicTables = () => (
+    <div className="gutter-example">
+        <BreadcrumbCustom first="表格" second="基础表格" />
+        <Row gutter={16}>
+            <Col className="gutter-row" md={24}>
+                <div className="gutter-box">
+                    <Card title="基础表格" bordered={false}>
+                        <BasicTable />
+                    </Card>
+                </div>
+            </Col>
+        </Row>
+        <Row gutter={16}>
+            <Col className="gutter-row" md={24}>
+                <div className="gutter-box">
+                    <Card title="基础表格" bordered={false}>
+                        <SelectTable />
+                    </Card>
+                </div>
+            </Col>
+        </Row>
+        <Row gutter={16}>
+            <Col className="gutter-row" md={12}>
+                <div className="gutter-box">
+                    <Card title="可控的筛选和排序" bordered={false}>
+                        <SortTable />
+                    </Card>
+                </div>
+            </Col>
+            <Col className="gutter-row" md={12}>
+                <div className="gutter-box">
+                    <Card title="自定义筛选" bordered={false}>
+                        <SearchTable />
+                    </Card>
+                </div>
+            </Col>
+        </Row>
+    </div>
+);
+
+export default BasicTables;

+ 191 - 0
src/components/tables/EditableTable.jsx

@@ -0,0 +1,191 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+
+import React from 'react';
+import { Table, Input, InputNumber, Popconfirm, Form, Button } from 'antd';
+
+const data = [];
+for (let i = 0; i < 100; i++) {
+	data.push({
+		key: i.toString(),
+		name: `Edrward ${i}`,
+		age: 32,
+		address: `London Park no. ${i}`,
+	});
+}
+const FormItem = Form.Item;
+const EditableContext = React.createContext();
+
+const EditableRow = ({ form, index, ...props }) => (
+	<EditableContext.Provider value={form}>
+		<tr {...props} />
+	</EditableContext.Provider>
+);
+
+const EditableFormRow = Form.create()(EditableRow);
+
+class EditableCell extends React.Component {
+	getInput = () => {
+		if (this.props.inputType === 'number') {
+			return <InputNumber />;
+		}
+		return <Input />;
+	};
+	render() {
+		const {
+			editing,
+			dataIndex,
+			title,
+			inputType,
+			record,
+			index,
+			...restProps
+		} = this.props;
+		return (
+			<EditableContext.Consumer>
+				{(form) => {
+					const { getFieldDecorator } = form;
+					return (
+						<td {...restProps}>
+							{editing ? (
+								<FormItem style={{ margin: 0 }}>
+									{getFieldDecorator(dataIndex, {
+										rules: [{
+											required: true,
+											message: `Please Input ${title}!`,
+										}],
+										initialValue: record[dataIndex],
+									})(this.getInput())}
+								</FormItem>
+							) : restProps.children}
+						</td>
+					);
+				}}
+			</EditableContext.Consumer>
+		);
+	}
+}
+
+export default class EditableTable extends React.Component {
+	constructor(props) {
+		super(props);
+		this.state = { data, editingKey: '' };
+		this.columns = [
+			{
+				title: 'name',
+				dataIndex: 'name',
+				width: '25%',
+				editable: true,
+			},
+			{
+				title: 'age',
+				dataIndex: 'age',
+				width: '15%',
+				editable: true,
+			},
+			{
+				title: 'address',
+				dataIndex: 'address',
+				width: '40%',
+				editable: true,
+			},
+			{
+				title: 'operation',
+				dataIndex: 'operation',
+				render: (text, record) => {
+					const editable = this.isEditing(record);
+					return (
+						<div>
+							{editable ? (
+								<span>
+									<EditableContext.Consumer>
+										{form => (
+											<Button
+												onClick={() => this.save(form, record.key)}
+												style={{ marginRight: 8 }}
+											>
+												Save
+                      						</Button>
+										)}
+									</EditableContext.Consumer>
+									<Popconfirm
+										title="Sure to cancel?"
+										onConfirm={() => this.cancel(record.key)}
+									>
+										<Button>Cancel</Button>
+									</Popconfirm>
+								</span>
+							) : (
+									<Button onClick={() => this.edit(record.key)}>Edit</Button>
+								)}
+						</div>
+					);
+				},
+			},
+		];
+	}
+	isEditing = (record) => {
+		return record.key === this.state.editingKey;
+	};
+	edit(key) {
+		this.setState({ editingKey: key });
+	}
+	save(form, key) {
+		form.validateFields((error, row) => {
+			if (error) {
+				return;
+			}
+			const newData = [...this.state.data];
+			const index = newData.findIndex(item => key === item.key);
+			if (index > -1) {
+				const item = newData[index];
+				newData.splice(index, 1, {
+					...item,
+					...row,
+				});
+				this.setState({ data: newData, editingKey: '' });
+			} else {
+				newData.push(data);
+				this.setState({ data: newData, editingKey: '' });
+			}
+		});
+	}
+	cancel = () => {
+		this.setState({ editingKey: '' });
+	};
+	render() {
+		const components = {
+			body: {
+				row: EditableFormRow,
+				cell: EditableCell,
+			},
+		};
+
+		const columns = this.columns.map((col) => {
+			if (!col.editable) {
+				return col;
+			}
+			return {
+				...col,
+				onCell: record => ({
+					record,
+					inputType: col.dataIndex === 'age' ? 'number' : 'text',
+					dataIndex: col.dataIndex,
+					title: col.title,
+					editing: this.isEditing(record),
+				}),
+			};
+		});
+
+		return (
+			<Table
+				components={components}
+				bordered
+				dataSource={this.state.data}
+				columns={columns}
+				rowClassName="editable-row"
+			/>
+		);
+	}
+}

+ 28 - 0
src/components/tables/ExpandedTable.jsx

@@ -0,0 +1,28 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+import React from 'react';
+import { Table, Button } from 'antd';
+
+const columns = [
+    { title: 'Name', dataIndex: 'name', key: 'name' },
+    { title: 'Age', dataIndex: 'age', key: 'age' },
+    { title: 'Address', dataIndex: 'address', key: 'address' },
+    { title: 'Action', dataIndex: '', key: 'x', render: () => <Button>Delete</Button> },
+];
+
+const data = [
+    { key: 1, name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.' },
+    { key: 2, name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.' },
+    { key: 3, name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.' },
+];
+
+const ExpandedTable = () => (
+    <Table
+        columns={columns}
+        expandedRowRender={record => <p>{record.description}</p>}
+        dataSource={data}
+    />
+);
+
+export default ExpandedTable;

+ 43 - 0
src/components/tables/FixedTable.jsx

@@ -0,0 +1,43 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+import React from 'react';
+import { Table } from 'antd';
+
+const columns = [
+    { title: 'Full Name', width: 100, dataIndex: 'name', key: 'name', fixed: 'left' },
+    { title: 'Age', width: 100, dataIndex: 'age', key: 'age', fixed: 'left' },
+    { title: 'Column 1', dataIndex: 'address', key: '1' },
+    { title: 'Column 2', dataIndex: 'address', key: '2' },
+    { title: 'Column 3', dataIndex: 'address', key: '3' },
+    { title: 'Column 4', dataIndex: 'address', key: '4' },
+    { title: 'Column 5', dataIndex: 'address', key: '5' },
+    { title: 'Column 6', dataIndex: 'address', key: '6' },
+    { title: 'Column 7', dataIndex: 'address', key: '7' },
+    { title: 'Column 8', dataIndex: 'address', key: '8' },
+    {
+        title: 'Action',
+        key: 'operation',
+        fixed: 'right',
+        width: 100,
+        render: () => <span>action</span>,
+    },
+];
+
+const data = [{
+    key: '1',
+    name: 'John Brown',
+    age: 32,
+    address: 'New York Park',
+}, {
+    key: '2',
+    name: 'Jim Green',
+    age: 40,
+    address: 'London Park',
+}];
+
+const FixedTable = () => (
+    <Table columns={columns} dataSource={data} scroll={{ x: 1300 }} />
+);
+
+export default FixedTable;

+ 123 - 0
src/components/tables/SearchTable.jsx

@@ -0,0 +1,123 @@
+/**
+ * Created by hao.cheng on 2017/4/16.
+ */
+import React from 'react';
+import { Table, Input, Button, Icon } from 'antd';
+
+const data = [{
+    key: '1',
+    name: 'John Brown',
+    age: 32,
+    address: 'New York No. 1 Lake Park',
+}, {
+    key: '2',
+    name: 'Joe Black',
+    age: 42,
+    address: 'London No. 1 Lake Park',
+}, {
+    key: '3',
+    name: 'Jim Green',
+    age: 32,
+    address: 'Sidney No. 1 Lake Park',
+}, {
+    key: '4',
+    name: 'Jim Red',
+    age: 32,
+    address: 'London No. 2 Lake Park',
+}];
+
+class SearchTable extends React.Component {
+    state = {
+        filterDropdownVisible: false,
+        data,
+        searchText: '',
+        filtered: false,
+    };
+    onInputChange = (e) => {
+        this.setState({ searchText: e.target.value });
+    };
+    onSearch = () => {
+        const { searchText } = this.state;
+        const reg = new RegExp(searchText, 'gi');
+        this.setState({
+            filterDropdownVisible: false,
+            filtered: !!searchText,
+            data: data.map((record) => {
+                const match = record.name.match(reg);
+                if (!match) {
+                    return null;
+                }
+                return {
+                    ...record,
+                    name: (
+                        <span>
+              {record.name.split(reg).map((text, i) => (
+                  i > 0 ? [<span className="highlight">{match[0]}</span>, text] : text
+              ))}
+            </span>
+                    ),
+                };
+            }).filter(record => !!record),
+        });
+    };
+    render() {
+        const columns = [{
+            title: 'Name',
+            dataIndex: 'name',
+            key: 'name',
+            filterDropdown: (
+                <div className="custom-filter-dropdown">
+                    <Input
+                        ref={ele => this.searchInput = ele}
+                        placeholder="Search name"
+                        value={this.state.searchText}
+                        onChange={this.onInputChange}
+                        onPressEnter={this.onSearch}
+                    />
+                    <Button type="primary" onClick={this.onSearch}>Search</Button>
+                </div>
+            ),
+            filterIcon: <Icon type="smile-o" style={{ color: this.state.filtered ? '#108ee9' : '#aaa' }} />,
+            filterDropdownVisible: this.state.filterDropdownVisible,
+            onFilterDropdownVisibleChange: visible => this.setState({ filterDropdownVisible: visible }, () => this.searchInput.focus()),
+        }, {
+            title: 'Age',
+            dataIndex: 'age',
+            key: 'age',
+        }, {
+            title: 'Address',
+            dataIndex: 'address',
+            key: 'address',
+            filters: [{
+                text: 'London',
+                value: 'London',
+            }, {
+                text: 'New York',
+                value: 'New York',
+            }],
+            onFilter: (value, record) => record.address.indexOf(value) === 0,
+        }];
+        return (
+            <div>
+                <Table columns={columns} dataSource={this.state.data} />
+                <style>{`
+                    .custom-filter-dropdown {
+                      padding: 8px;
+                      border-radius: 6px;
+                      background: #fff;
+                      box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
+                    }
+                    .custom-filter-dropdown input {
+                      width: 130px;
+                      margin-right: 8px;
+                    }
+                    .highlight {
+                      color: #f50;
+                    }
+                `}</style>
+            </div>
+        );
+    }
+}
+
+export default SearchTable;

+ 76 - 0
src/components/tables/SelectTable.jsx

@@ -0,0 +1,76 @@
+/**
+ * Created by hao.cheng on 2017/4/15.
+ */
+import React from 'react';
+import { Table } from 'antd';
+
+const columns = [{
+    title: 'Name',
+    dataIndex: 'name',
+}, {
+    title: 'Age',
+    dataIndex: 'age',
+}, {
+    title: 'Address',
+    dataIndex: 'address',
+}];
+
+const data = [];
+for (let i = 0; i < 46; i++) {
+    data.push({
+        key: i,
+        name: `Edward King ${i}`,
+        age: 32,
+        address: `London, Park Lane no. ${i}`,
+    });
+}
+
+class SelectTable extends React.Component {
+    state = {
+        selectedRowKeys: [], // Check here to configure the default column
+    };
+    onSelectChange = (selectedRowKeys) => {
+        console.log('selectedRowKeys changed: ', selectedRowKeys);
+        this.setState({ selectedRowKeys });
+    };
+    render() {
+        const { selectedRowKeys } = this.state;
+        const rowSelection = {
+            selectedRowKeys,
+            onChange: this.onSelectChange,
+            selections: [{
+                key: 'odd',
+                text: '选择奇数列',
+                onSelect: (changableRowKeys) => {
+                    let newSelectedRowKeys = [];
+                    newSelectedRowKeys = changableRowKeys.filter((key, index) => {
+                        if (index % 2 !== 0) {
+                            return false;
+                        }
+                        return true;
+                    });
+                    this.setState({ selectedRowKeys: newSelectedRowKeys });
+                },
+            }, {
+                key: 'even',
+                text: '选择偶数列',
+                onSelect: (changableRowKeys) => {
+                    let newSelectedRowKeys = [];
+                    newSelectedRowKeys = changableRowKeys.filter((key, index) => {
+                        if (index % 2 !== 0) {
+                            return true;
+                        }
+                        return false;
+                    });
+                    this.setState({ selectedRowKeys: newSelectedRowKeys });
+                },
+            }],
+            onSelection: this.onSelection,
+        };
+        return (
+            <Table rowSelection={rowSelection} columns={columns} dataSource={data} />
+        );
+    }
+}
+
+export default SelectTable;

+ 106 - 0
src/components/tables/SortTable.jsx

@@ -0,0 +1,106 @@
+/**
+ * Created by hao.cheng on 2017/4/15.
+ */
+import React from 'react';
+import { Table, Button } from 'antd';
+
+const data = [{
+    key: '1',
+    name: 'John Brown',
+    age: 32,
+    address: 'New York No. 1 Lake Park',
+}, {
+    key: '2',
+    name: 'Jim Green',
+    age: 42,
+    address: 'London No. 1 Lake Park',
+}, {
+    key: '3',
+    name: 'Joe Black',
+    age: 32,
+    address: 'Sidney No. 1 Lake Park',
+}, {
+    key: '4',
+    name: 'Jim Red',
+    age: 32,
+    address: 'London No. 2 Lake Park',
+}];
+
+class SortTable extends React.Component {
+    state = {
+        filteredInfo: null,
+        sortedInfo: null,
+    };
+    handleChange = (pagination, filters, sorter) => {
+        console.log('Various parameters', pagination, filters, sorter);
+        this.setState({
+            filteredInfo: filters,
+            sortedInfo: sorter,
+        });
+    };
+    clearFilters = () => {
+        this.setState({ filteredInfo: null });
+    };
+    clearAll = () => {
+        this.setState({
+            filteredInfo: null,
+            sortedInfo: null,
+        });
+    };
+    setAgeSort = () => {
+        this.setState({
+            sortedInfo: {
+                order: 'descend',
+                columnKey: 'age',
+            },
+        });
+    };
+    render() {
+        let { sortedInfo, filteredInfo } = this.state;
+        sortedInfo = sortedInfo || {};
+        filteredInfo = filteredInfo || {};
+        const columns = [{
+            title: 'Name',
+            dataIndex: 'name',
+            key: 'name',
+            filters: [
+                { text: 'Joe', value: 'Joe' },
+                { text: 'Jim', value: 'Jim' },
+            ],
+            filteredValue: filteredInfo.name || null,
+            onFilter: (value, record) => record.name.includes(value),
+            sorter: (a, b) => a.name.length - b.name.length,
+            sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
+        }, {
+            title: 'Age',
+            dataIndex: 'age',
+            key: 'age',
+            sorter: (a, b) => a.age - b.age,
+            sortOrder: sortedInfo.columnKey === 'age' && sortedInfo.order,
+        }, {
+            title: 'Address',
+            dataIndex: 'address',
+            key: 'address',
+            filters: [
+                { text: 'London', value: 'London' },
+                { text: 'New York', value: 'New York' },
+            ],
+            filteredValue: filteredInfo.address || null,
+            onFilter: (value, record) => record.address.includes(value),
+            sorter: (a, b) => a.address.length - b.address.length,
+            sortOrder: sortedInfo.columnKey === 'address' && sortedInfo.order,
+        }];
+        return (
+            <div>
+                <div className="table-operations">
+                    <Button onClick={this.setAgeSort}>Sort age</Button>
+                    <Button onClick={this.clearFilters}>Clear filters</Button>
+                    <Button onClick={this.clearAll}>Clear filters and sorters</Button>
+                </div>
+                <Table columns={columns} dataSource={data} onChange={this.handleChange} />
+            </div>
+        );
+    }
+}
+
+export default SortTable;

+ 136 - 0
src/components/ui/Buttons.jsx

@@ -0,0 +1,136 @@
+/**
+ * Created by hao.cheng on 2017/4/23.
+ */
+import React from 'react';
+import { Row, Col, Card, Button, Radio, Icon, Menu, Dropdown } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+
+class Buttons extends React.Component {
+    state = {
+        size: 'default',
+        loading: false,
+        iconLoading: false,
+    };
+
+    handleSizeChange = (e) => {
+        this.setState({ size: e.target.value });
+    };
+    handleMenuClick = (e) => {
+        console.log('click', e);
+    };
+    enterLoading = () => {
+        this.setState({ loading: true });
+    };
+    enterIconLoading = () => {
+        this.setState({ iconLoading: true });
+    };
+    render() {
+        const size = this.state.size;
+        const menu = (
+            <Menu onClick={this.handleMenuClick}>
+                <Menu.Item key="1">1st item</Menu.Item>
+                <Menu.Item key="2">2nd item</Menu.Item>
+                <Menu.Item key="3">3rd item</Menu.Item>
+            </Menu>
+        );
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="按钮" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary">Primary</Button>
+                                <Button>Default</Button>
+                                <Button type="dashed">Dashed</Button>
+                                <Button type="danger">Danger</Button>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary" shape="circle" icon="search" />
+                                <Button type="primary" icon="search">Search</Button>
+                                <Button shape="circle" icon="search" />
+                                <Button icon="search">Search</Button>
+                                <br />
+                                <Button shape="circle" icon="search" />
+                                <Button icon="search">Search</Button>
+                                <Button type="dashed" shape="circle" icon="search" />
+                                <Button type="dashed" icon="search">Search</Button>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Radio.Group value={size} onChange={this.handleSizeChange}>
+                                    <Radio.Button value="large">Large</Radio.Button>
+                                    <Radio.Button value="default">Default</Radio.Button>
+                                    <Radio.Button value="small">Small</Radio.Button>
+                                </Radio.Group>
+                                <br /><br />
+                                <Button type="primary" shape="circle" icon="download" size={size} />
+                                <Button type="primary" icon="download" size={size}>Download</Button>
+                                <Button type="primary" size={size}>Normal</Button>
+                                <br />
+                                <Button.Group size={size}>
+                                    <Button type="primary">
+                                        <Icon type="left" />Backward
+                                    </Button>
+                                    <Button type="primary">
+                                        Forward<Icon type="right" />
+                                    </Button>
+                                </Button.Group>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary">primary</Button>
+                                <Button>secondary</Button>
+                                <Dropdown overlay={menu}>
+                                    <Button>
+                                        more <Icon type="down" />
+                                    </Button>
+                                </Dropdown>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary" loading>
+                                    Loading
+                                </Button>
+                                <Button type="primary" size="small" loading>
+                                    Loading
+                                </Button>
+                                <br />
+                                <Button type="primary" loading={this.state.loading} onClick={this.enterLoading}>
+                                    Click me!
+                                </Button>
+                                <Button type="primary" icon="poweroff" loading={this.state.iconLoading} onClick={this.enterIconLoading}>
+                                    Click me!
+                                </Button>
+                                <br />
+                                <Button shape="circle" loading />
+                                <Button type="primary" shape="circle" loading />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+                <style>{`
+                    .button-demo .ant-btn {
+                        margin-right: 8px;
+                        margin-bottom: 12px;
+                    }
+                `}</style>
+            </div>
+        )
+    }
+}
+
+export default Buttons;

+ 122 - 0
src/components/ui/Draggable.jsx

@@ -0,0 +1,122 @@
+/**
+ * Created by hao.cheng on 2017/4/28.
+ */
+import React from 'react';
+import { Row, Col, Card } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+import Draggable from 'react-draggable';
+
+class Drags extends React.Component {
+    state = {
+        activeDrags: 0,
+        deltaPosition: {
+            x: 0, y: 0
+        },
+        controlledPosition: {
+            x: -400, y: 200
+        }
+    };
+    onStart = () => {
+        let { activeDrags } = this.state;
+        this.setState({ activeDrags: ++activeDrags });
+    };
+    onStop = () => {
+        let { activeDrags } = this.state;
+        this.setState({ activeDrags: --activeDrags });
+    };
+    handleDrag = (e, ui) => {
+        const {x, y} = this.state.deltaPosition;
+        this.setState({
+            deltaPosition: {
+                x: x + ui.deltaX,
+                y: y + ui.deltaY,
+            }
+        });
+    };
+    render() {
+        const dragHandlers = {onStart: this.onStart, onStop: this.onStop};
+        const {deltaPosition} = this.state;
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="拖拽" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={6}>
+                        <div className="gutter-box">
+                            <Draggable zIndex={100} {...dragHandlers}>
+                                <Card bordered={false} className={'dragDemo'}>
+
+                                    I can be dragged anywhere
+                                </Card>
+                            </Draggable>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={6}>
+                        <div className="gutter-box">
+                            <Draggable axis="x" {...dragHandlers}>
+                                <Card bordered={false} className={'dragDemo'}>
+                                    I can only be dragged horizonally (x axis)
+                                </Card>
+                            </Draggable>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={6}>
+                        <div className="gutter-box">
+                            <Draggable axis="y" {...dragHandlers}>
+                                <Card bordered={false} className={'dragDemo'}>
+                                    I can only be dragged vertically (y axis)
+                                </Card>
+                            </Draggable>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={6}>
+                        <div className="gutter-box">
+                            <Draggable onDrag={this.handleDrag} {...dragHandlers}>
+                                <Card bordered={false} className={'dragDemo'}>
+                                    <div>I track my deltas</div>
+                                    <div>x: {deltaPosition.x.toFixed(0)}, y: {deltaPosition.y.toFixed(0)}</div>
+                                </Card>
+                            </Draggable>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={6}>
+                        <div className="gutter-box">
+                            <Draggable handle="strong" {...dragHandlers}>
+                                <Card bordered={false} className={'dragDemo no-cursor'}>
+                                    <strong className="cursor-move"><div>Drag here</div></strong>
+                                    <div>You must click my handle to drag me</div>
+                                </Card>
+                            </Draggable>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={6}>
+                        <div className="gutter-box">
+                            <Draggable cancel="strong" {...dragHandlers}>
+                                <Card bordered={false} className={'dragDemo'}>
+                                    <strong className="no-cursor"><div>Can't drag here</div></strong>
+                                    <div>Dragging here works</div>
+                                </Card>
+                            </Draggable>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={6}>
+                        <div className="gutter-box">
+                            <Draggable bounds={{top: -100, left: -100, right: 100, bottom: 100}} zIndex={5} {...dragHandlers}>
+                                <Card bordered={false} className={'dragDemo'}>
+                                    <div>I can only be moved 100px in any direction.</div>
+                                </Card>
+                            </Draggable>
+                        </div>
+                    </Col>
+                </Row>
+                <style>{`
+                    .dragDemo {
+                        height: 180px;
+                    }
+                `}</style>
+            </div>
+        )
+    }
+}
+
+
+export default Drags;

+ 187 - 0
src/components/ui/Gallery.jsx

@@ -0,0 +1,187 @@
+/**
+ * Created by hao.cheng on 2017/5/6.
+ */
+import React from 'react';
+import { Row, Col, Card } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+import PhotoSwipe from 'photoswipe';
+import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default';
+import 'photoswipe/dist/photoswipe.css';
+import 'photoswipe/dist/default-skin/default-skin.css';
+
+class Gallery extends React.Component {
+    state = {
+        gallery: null
+    };
+    componentDidMount() {
+    }
+    componentWillUnmount = () => {
+        this.closeGallery();
+    };
+    openGallery = (item) => {
+        const items = [
+            {
+                src: item,
+                w: 0,
+                h: 0,
+            }
+        ];
+        const pswpElement = this.pswpElement;
+        const options = {index: 0};
+        this.gallery = new PhotoSwipe( pswpElement, PhotoswipeUIDefault, items, options);
+        this.gallery.listen('gettingData', (index, item) => {
+            const _this = this;
+            if (item.w < 1 || item.h < 1) { // unknown size
+                var img = new Image();
+                img.onload = function() { // will get size after load
+                    item.w = this.width; // set image width
+                    item.h = this.height; // set image height
+                    _this.gallery.invalidateCurrItems(); // reinit Items
+                    _this.gallery.updateSize(true); // reinit Items
+                };
+                img.src = item.src; // let's download image
+            }
+        });
+        this.gallery.init();
+    };
+    closeGallery = () => {
+        if (!this.gallery) return;
+        this.gallery.close();
+    };
+    render() {
+        const imgs = [
+            [
+                'http://img.hb.aicdn.com/1cad414972c5db2b8c1942289e3aeef37175006a8bb16-CBtjtX_fw',
+                'http://img.hb.aicdn.com/016f2e13934397e17c3482a4529f3da1149d37fd2a99c-RVM1Gi_fw',
+                'http://img.hb.aicdn.com/8c5d5f2bf6427d1b5ed8657a7ae0c9938d3465e367899-AJ0zVA_fw',
+                'http://img.hb.aicdn.com/bd71ccac0b16bbcade255a1a8a63504d71c7dee9a8652-zBCN9d_fw',
+                'http://img.hb.aicdn.com/37a40cb04345463858d45418ae6ed9ef319e30dc37a45-o4pQ0j_fw',
+
+            ],
+            [
+                'http://img.hb.aicdn.com/5fad6c3a14a9b80c4448835bb6b23ab895d18e234eff3-BPGmox_fw',
+                'http://img.hb.aicdn.com/a1a19de5dac212a646ba6967ef565786399fb1665bd04-EEvwzR_fw',
+                'http://img.hb.aicdn.com/06595f8044e881de3a82d691768bc8c21a2a9f3633d60-XKjC2s_fw',
+                'http://img.hb.aicdn.com/880787b36d45efbe05aa409c867db29a3028e02da7f9b-qxGib9_fw',
+                'http://img.hb.aicdn.com/4964b97f6f6eb61a20922b40842adf0169c44e491c4b60-azX1S7_fw'
+            ],
+            [
+                'http://img.hb.aicdn.com/ff97d00944edfc706c62dd5c0e955c4099a37b407534f-BcUqf0_fw',
+                'http://img.hb.aicdn.com/0e22be22b08c6f78b94283b6cfa890093ac3cae8401e7-b1ftfi_fw',
+                'http://img.hb.aicdn.com/879f870e15f7cc0847c8ae19a5fcbe974d5904bb181d7-RGmtNU_fw',
+                'http://img.hb.aicdn.com/b4a8e62958555a97dc3de9ccb03284bf556c042925522-x50qGv_fw',
+                'http://img.hb.aicdn.com/1ef493a15674e9fd523b248ea4ec43d2ea9ce6952ff3e-WavWKc_fw'
+            ],
+            [
+                'http://img.hb.aicdn.com/8e16efec78ac4a3684fc8999d18e3661af40fd4510a25-DDvQON_fw',
+                'http://img.hb.aicdn.com/61dfa024c8040e6a5bcb03d42928fbcb0c87c1a54e731-yc4lvV_fw',
+                'http://img.hb.aicdn.com/6783b4d7811ad7fb87b1446c5488b91179f7608118289-hpEyP3_fw',
+                'http://img.hb.aicdn.com/7be61ba6bdb20a73be63edc387b16eec72d0bbb51c7ef-XafA07_fw',
+                'http://img.hb.aicdn.com/bd3ba3f907fe098b911947e0020615b50fc340ed2df72-WsuHuM_fw'
+            ],
+            [
+                'http://img.hb.aicdn.com/71471aaac95eade66400a390863b37c76d9addcd14982-0H6sak_fw',
+                'http://img.hb.aicdn.com/cb16c68c4d3b7a08b5e91cd351f6b723634ca3fc27d4d-m1JD8z_fw',
+                'http://img.hb.aicdn.com/e3559b6e8d7237857382050e5659a64cc0b7d696a2869-stcRXA_fw',
+                'http://img.hb.aicdn.com/4ea229436fcf2077502953907a6afb16d3c5cd611b8e2-0dVIeH_fw',
+                'http://img.hb.aicdn.com/98c786f4314736f95a42bf927bf65a82d305a532c6258-njI6id_fw'
+            ]
+        ];
+        const imgsTag = imgs.map(v1 => (
+            v1.map(v2 => (
+                <div className="gutter-box" key={v2}>
+                    <Card bordered={false} bodyStyle={{ padding: 0 }}>
+                        <div>
+                            <img onClick={() => this.openGallery(v2)} alt="example" width="100%" src={v2} />
+                        </div>
+                        <div className="pa-m">
+                            <h3>React Admin</h3>
+                            <small><a href="https://yezihaohao.github.io/" target="_blank" rel="noopener noreferrer">https://yezihaohao.github.io/</a></small>
+                        </div>
+                    </Card>
+                </div>
+            ))
+        ));
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="画廊(图片来自花瓣网,仅学习,若侵权请联系删除)" />
+                <Row gutter={10}>
+                    <Col className="gutter-row" md={5}>
+                        {imgsTag[0]}
+                    </Col>
+                    <Col className="gutter-row" md={5}>
+                        {imgsTag[1]}
+                    </Col>
+                    <Col className="gutter-row" md={5}>
+                        {imgsTag[2]}
+                    </Col>
+                    <Col className="gutter-row" md={5}>
+                        {imgsTag[3]}
+                    </Col>
+                    <Col className="gutter-row" md={4}>
+                        {imgsTag[4]}
+                    </Col>
+                </Row>
+                <div className="pswp" tabIndex="-1" role="dialog" aria-hidden="true" ref={(div) => {this.pswpElement = div;} }>
+
+                    <div className="pswp__bg" />
+
+                    <div className="pswp__scroll-wrap">
+
+                        <div className="pswp__container">
+                            <div className="pswp__item" />
+                            <div className="pswp__item" />
+                            <div className="pswp__item" />
+                        </div>
+
+                        <div className="pswp__ui pswp__ui--hidden">
+
+                            <div className="pswp__top-bar">
+
+                                <div className="pswp__counter" />
+
+                                <button className="pswp__button pswp__button--close" title="Close (Esc)" />
+
+                                <button className="pswp__button pswp__button--share" title="Share" />
+
+                                <button className="pswp__button pswp__button--fs" title="Toggle fullscreen" />
+
+                                <button className="pswp__button pswp__button--zoom" title="Zoom in/out" />
+
+                                <div className="pswp__preloader">
+                                    <div className="pswp__preloader__icn">
+                                        <div className="pswp__preloader__cut">
+                                            <div className="pswp__preloader__donut" />
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div className="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
+                                <div className="pswp__share-tooltip" />
+                            </div>
+
+                            <button className="pswp__button pswp__button--arrow--left" title="Previous (arrow left)" />
+
+                            <button className="pswp__button pswp__button--arrow--right" title="Next (arrow right)" />
+
+                            <div className="pswp__caption">
+                                <div className="pswp__caption__center" />
+                            </div>
+
+                        </div>
+
+                    </div>
+
+                </div>
+                <style>{`
+                    .ant-card-body img {
+                        cursor: pointer;
+                    }
+                `}</style>
+            </div>
+        )
+    }
+}
+
+export default Gallery;

File diff suppressed because it is too large
+ 21 - 0
src/components/ui/Icons.jsx


+ 221 - 0
src/components/ui/Modals.jsx

@@ -0,0 +1,221 @@
+/**
+ * Created by hao.cheng on 2017/4/23.
+ */
+import React, { Component } from 'react';
+import { Row, Col, Card, Modal, Button } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+const confirm = Modal.confirm;
+
+class S extends Component {
+    state = {
+        visible: false,
+        ModalText2: 'Content of the modal dialog',
+        visible2: false,
+        loading3: false,
+        visible3: false,
+        modal1Visible: false,
+        modal2Visible: false,
+    };
+    showModal = () => {
+        this.setState({
+            visible: true,
+        });
+    };
+    handleOk = (e) => {
+        console.log(e);
+        this.setState({
+            visible: false,
+        });
+    };
+    handleCancel = (e) => {
+        console.log(e);
+        this.setState({
+            visible: false,
+        });
+    };
+    showModal2 = () => {
+        this.setState({
+            visible2: true,
+        });
+    };
+    handleOk2 = () => {
+        this.setState({
+            ModalText2: 'The modal dialog will be closed after two seconds',
+            confirmLoading2: true,
+        });
+        setTimeout(() => {
+            this.setState({
+                visible2: false,
+                confirmLoading2: false,
+            });
+        }, 2000);
+    };
+    setModal1Visible = (modal1Visible) => {
+        this.setState({ modal1Visible });
+    };
+    setModal2Visible = (modal2Visible) => {
+        this.setState({ modal2Visible });
+    };
+    handleCancel2 = () => {
+        console.log('Clicked cancel button');
+        this.setState({
+            visible2: false,
+        });
+    };
+    showModal3 = () => {
+        this.setState({
+            visible3: true,
+        });
+    };
+    handleOk3 = () => {
+        this.setState({ loading3: true });
+        setTimeout(() => {
+            this.setState({ loading3: false, visible3: false });
+        }, 3000);
+    };
+    handleCancel3 = () => {
+        this.setState({ visible3: false });
+    };
+    showConfirm4 = () => {
+        confirm({
+            title: 'Want to delete these items?',
+            content: 'some descriptions',
+            onOk() {
+                console.log('OK');
+            },
+            onCancel() {
+                console.log('Cancel');
+            },
+        });
+    };
+    info = () => {
+        Modal.info({
+            title: 'This is a notification message',
+            content: (
+                <div>
+                    <p>some messages...some messages...</p>
+                    <p>some messages...some messages...</p>
+                </div>
+            ),
+            onOk() {},
+        });
+    };
+    success = () => {
+        Modal.success({
+            title: 'This is a success message',
+            content: 'some messages...some messages...',
+        });
+    };
+    error = () => {
+        Modal.error({
+            title: 'This is an error message',
+            content: 'some messages...some messages...',
+        });
+    };
+    warning = () => {
+        Modal.warning({
+            title: 'This is a warning message',
+            content: 'some messages...some messages...',
+        });
+    };
+    render() {
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="对话框" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <p>
+                                    <Button type="primary" onClick={this.showModal}>基本用法</Button>
+                                    <Modal title="Basic Modal" visible={this.state.visible}
+                                           onOk={this.handleOk} onCancel={this.handleCancel}
+                                    >
+                                        <p>some contents...</p>
+                                        <p>some contents...</p>
+                                        <p>some contents...</p>
+                                    </Modal>
+                                </p>
+                                <p>
+                                    <Button type="primary" onClick={this.showModal2}>异步关闭</Button>
+                                    <Modal title="Title of the modal dialog"
+                                           visible={this.state.visible2}
+                                           onOk={this.handleOk2}
+                                           confirmLoading={this.state.confirmLoading2}
+                                           onCancel={this.handleCancel2}
+                                    >
+                                        <p>{this.state.ModalText2}</p>
+                                    </Modal>
+                                </p>
+                                <p>
+                                    <Button type="primary" onClick={this.showModal3}>
+                                        自定义页脚
+                                    </Button>
+                                    <Modal
+                                        visible={this.state.visible3}
+                                        title="Title"
+                                        onOk={this.handleOk3}
+                                        onCancel={this.handleCancel3}
+                                        footer={[
+                                            <Button key="back" size="large" onClick={this.handleCancel3}>Return</Button>,
+                                            <Button key="submit" type="primary" size="large" loading={this.state.loading3} onClick={this.handleOk3}>
+                                                Submit
+                                            </Button>,
+                                        ]}
+                                    >
+                                        <p>Some contents...</p>
+                                        <p>Some contents...</p>
+                                        <p>Some contents...</p>
+                                        <p>Some contents...</p>
+                                        <p>Some contents...</p>
+                                    </Modal>
+                                </p>
+                                <p>
+                                    <Button onClick={this.showConfirm4}>
+                                        确认框
+                                    </Button>
+                                </p>
+                                <p>
+                                    <Button onClick={this.info}>信息提示</Button>
+                                    <Button onClick={this.success}>成功</Button>
+                                    <Button onClick={this.error}>失败</Button>
+                                    <Button onClick={this.warning}>警告</Button>
+                                </p>
+                                <p>
+                                    <Button type="primary" onClick={() => this.setModal1Visible(true)}>距离顶部20px</Button>
+                                    <Modal
+                                        title="20px to Top"
+                                        style={{ top: 20 }}
+                                        visible={this.state.modal1Visible}
+                                        onOk={() => this.setModal1Visible(false)}
+                                        onCancel={() => this.setModal1Visible(false)}
+                                    >
+                                        <p>some contents...</p>
+                                        <p>some contents...</p>
+                                        <p>some contents...</p>
+                                    </Modal>
+                                    <br /><br />
+                                    <Button type="primary" onClick={() => this.setModal2Visible(true)}>垂直居中</Button>
+                                    <Modal
+                                        title="Vertically centered modal dialog"
+                                        wrapClassName="vertical-center-modal"
+                                        visible={this.state.modal2Visible}
+                                        onOk={() => this.setModal2Visible(false)}
+                                        onCancel={() => this.setModal2Visible(false)}
+                                    >
+                                        <p>some contents...</p>
+                                        <p>some contents...</p>
+                                        <p>some contents...</p>
+                                    </Modal>
+                                </p>
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        )
+    }
+}
+
+
+export default S;

+ 128 - 0
src/components/ui/Notifications.jsx

@@ -0,0 +1,128 @@
+/**
+ * Created by hao.cheng on 2017/4/25.
+ */
+import React, { Component } from 'react';
+import { Row, Col, Card, Button, notification, Icon, Select } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+const { Option } = Select;
+const options = ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'];
+class Notifications extends Component {
+    openNotification = () => {
+        notification.open({
+            message: 'Notification Title',
+            description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.'
+        });
+    };
+    openNotification2 = () => {
+        const args = {
+            message: 'Notification Title',
+            description: 'I will never close automatically. I will be close automatically. I will never close automatically.',
+            duration: 0,
+        };
+        notification.open(args);
+    };
+    openNotificationWithIcon = (type) => {
+        notification[type]({
+            message: 'Notification Title',
+            description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
+        });
+    };
+    openNotification3 = () => {
+        const key = `open${Date.now()}`;
+        const btnClick = function () {
+            // to hide notification box
+            notification.close(key);
+        };
+        const btn = (
+            <Button type="primary" size="small" onClick={btnClick}>
+                Confirm
+            </Button>
+        );
+        notification.open({
+            message: 'Notification Title',
+            description: 'A function will be be called after the notification is closed (automatically after the "duration" time of manually).',
+            btn,
+            key,
+            onClose: this.close,
+        });
+    };
+    close = () => {
+        console.log('Notification was closed. Either the close button was clicked or duration time elapsed.');
+    };
+    openNotification4 = () => {
+        notification.open({
+            message: 'Notification Title',
+            description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
+            icon: <Icon type="smile-circle" style={{ color: '#108ee9' }} />,
+        });
+    };
+
+    render() {
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="通知提醒框" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary" onClick={this.openNotification}>基本用法-4.5秒关闭</Button>
+                            </Card>
+                        </div>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary" onClick={this.openNotification2}>取消自动关闭</Button>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary" onClick={() => this.openNotificationWithIcon('success')}>成功</Button>
+                                <Button type="primary" onClick={() => this.openNotificationWithIcon('info')}>提醒</Button>
+                                <Button type="primary" onClick={() => this.openNotificationWithIcon('warning')}>警告</Button>
+                                <Button type="primary" onClick={() => this.openNotificationWithIcon('error')}>失败</Button>
+                            </Card>
+                        </div>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary" onClick={this.openNotification3}>自定义按钮</Button>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Button type="primary" onClick={this.openNotification4}>自定义通知图标</Button>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Select
+                                    defaultValue="topRight"
+                                    style={{ width: 120, marginRight: 10 }}
+                                    onChange={(val) => {
+                                        notification.config({
+                                            placement: val,
+                                        });
+                                    }}
+                                >
+                                    {options.map(val => <Option key={val} value={val}>{val}</Option>)}
+                                </Select>
+                                <Button
+                                    type="primary"
+                                    onClick={this.openNotification}
+                                >
+                                    打开消息通知
+                                </Button>
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default Notifications;

+ 94 - 0
src/components/ui/Spins.jsx

@@ -0,0 +1,94 @@
+/**
+ * Created by hao.cheng on 2017/4/23.
+ */
+import React from 'react';
+import { Row, Col, Card, Spin, Alert, Switch, Button } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+import NProgress from 'nprogress';
+import 'nprogress/nprogress.css';
+
+class Spins extends React.Component {
+    state = { loading: false };
+    toggle = (value) => {
+        this.setState({ loading: value });
+    };
+    nprogressStart = () => {
+        NProgress.start();
+    };
+    nprogressDone = () => {
+        NProgress.done();
+    };
+    render() {
+        const container = (
+            <Alert
+                message="Alert message title"
+                description="Further details about the context of this alert."
+                type="info"
+            />
+        );
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="加载中" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Spin />
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card>
+                                <Spin size="small" />
+                                <Spin />
+                                <Spin size="large" />
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Spin tip="Loading...">
+                                    <Alert
+                                        message="Alert message title"
+                                        description="Further details about the context of this alert."
+                                        type="info"
+                                    />
+                                </Spin>
+                            </Card>
+                        </div>
+                    </Col>
+
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <Spin spinning={this.state.loading}>{container}</Spin>
+                                Loading state:<Switch checked={this.state.loading} onChange={this.toggle} />
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card bordered={false}>
+                                <h4>顶部进度条</h4>
+                                <p>
+                                    <Button icon="caret-right" onClick={this.nprogressStart} />
+                                    <span> NProgress.start() — 显示进度条</span>
+                                </p>
+                                <p style={{marginTop: 20}}>
+                                    <Button icon="caret-right" onClick={this.nprogressDone} />
+                                    <span>  NProgress.done() — 进度条完成</span>
+                                </p>
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default Spins;

+ 152 - 0
src/components/ui/Tabs.jsx

@@ -0,0 +1,152 @@
+/**
+ * Created by hao.cheng on 2017/4/25.
+ */
+import React, { Component } from 'react';
+import { Row, Col, Card, Tabs, Icon, Radio, Button } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+const TabPane = Tabs.TabPane;
+
+class TabsCustom extends Component {
+    constructor(props) {
+        super(props);
+        this.newTabIndex = 0;
+        const panes = [
+            { title: 'Tab 1', content: 'Content of Tab Pane 1', key: '1' },
+            { title: 'Tab 2', content: 'Content of Tab Pane 2', key: '2' },
+        ];
+        this.state = {
+            activeKey: panes[0].key,
+            panes,
+            mode: 'top'
+        };
+    }
+    callback = (key) => {
+        console.log(key);
+    };
+    handleModeChange = (e) => {
+        const mode = e.target.value;
+        this.setState({ mode });
+    };
+    onChange = (activeKey) => {
+        this.setState({ activeKey });
+    };
+    onEdit = (targetKey, action) => {
+        this[action](targetKey);
+    };
+    add = () => {
+        const panes = this.state.panes;
+        const activeKey = `newTab${this.newTabIndex++}`;
+        panes.push({ title: 'New Tab', content: 'New Tab Pane', key: activeKey });
+        this.setState({ panes, activeKey });
+    };
+    remove = (targetKey) => {
+        let activeKey = this.state.activeKey;
+        let lastIndex;
+        this.state.panes.forEach((pane, i) => {
+            if (pane.key === targetKey) {
+                lastIndex = i - 1;
+            }
+        });
+        const panes = this.state.panes.filter(pane => pane.key !== targetKey);
+        if (lastIndex >= 0 && activeKey === targetKey) {
+            activeKey = panes[lastIndex].key;
+        }
+        this.setState({ panes, activeKey });
+    };
+    render() {
+        const { mode } = this.state;
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="标签页" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card title="基本-默认选中第一项" bordered={false}>
+                                <Tabs defaultActiveKey="1" onChange={this.callback}>
+                                    <TabPane tab="Tab 1" key="1">Content of Tab Pane 1</TabPane>
+                                    <TabPane tab="Tab 2" key="2">Content of Tab Pane 2</TabPane>
+                                    <TabPane tab="Tab 3" key="3">Content of Tab Pane 3</TabPane>
+                                </Tabs>
+                            </Card>
+                        </div>
+                        <div className="gutter-box">
+                            <Card title="带图标" bordered={false}>
+                                <Tabs defaultActiveKey="2" style={{height: 150}}>
+                                    <TabPane tab={<span><Icon type="apple" />Tab 1</span>} key="1">
+                                        Tab 1
+                                    </TabPane>
+                                    <TabPane tab={<span><Icon type="android" />Tab 2</span>} key="2">
+                                        Tab 2
+                                    </TabPane>
+                                </Tabs>
+                            </Card>
+                        </div>
+                        <div className="gutter-box">
+                            <Card title="卡片式风格" bordered={false}>
+                                <Tabs onChange={this.callback} type="card">
+                                    <TabPane tab="Tab 1" key="1">Content of Tab Pane 1</TabPane>
+                                    <TabPane tab="Tab 2" key="2">Content of Tab Pane 2</TabPane>
+                                    <TabPane tab="Tab 3" key="3">Content of Tab Pane 3</TabPane>
+                                </Tabs>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={12}>
+                        <div className="gutter-box">
+                            <Card title="禁用某项" bordered={false}>
+                                <Tabs defaultActiveKey="1">
+                                    <TabPane tab="Tab 1" key="1">Tab 1</TabPane>
+                                    <TabPane tab="Tab 2" disabled key="2">Tab 2</TabPane>
+                                    <TabPane tab="Tab 3" key="3">Tab 3</TabPane>
+                                </Tabs>
+                            </Card>
+                        </div>
+                        <div className="gutter-box">
+                            <Card title="带滚动" bordered={false}>
+                                <Radio.Group onChange={this.handleModeChange} value={mode} style={{ marginBottom: 8 }}>
+                                    <Radio.Button value="top">Horizontal</Radio.Button>
+                                    <Radio.Button value="left">Vertical</Radio.Button>
+                                </Radio.Group>
+                                <Tabs
+                                    defaultActiveKey="1"
+                                    tabPosition={mode}
+                                    style={{height: 150}}
+                                >
+                                    <TabPane tab="Tab 1" key="1">Content of tab 1</TabPane>
+                                    <TabPane tab="Tab 2" key="2">Content of tab 2</TabPane>
+                                    <TabPane tab="Tab 3" key="3">Content of tab 3</TabPane>
+                                    <TabPane tab="Tab 4" key="4">Content of tab 4</TabPane>
+                                    <TabPane tab="Tab 5" key="5">Content of tab 5</TabPane>
+                                    <TabPane tab="Tab 6" key="6">Content of tab 6</TabPane>
+                                    <TabPane tab="Tab 7" key="7">Content of tab 7</TabPane>
+                                    <TabPane tab="Tab 8" key="8">Content of tab 8</TabPane>
+                                    <TabPane tab="Tab 9" key="9">Content of tab 9</TabPane>
+                                </Tabs>
+                            </Card>
+                        </div>
+                        <div className="gutter-box">
+                            <Card title="带删除和新增" bordered={false}>
+                                <div style={{ marginBottom: 16 }}>
+                                    <Button onClick={this.add}>ADD</Button>
+                                </div>
+                                <Tabs
+                                    hideAdd
+                                    onChange={this.onChange}
+                                    activeKey={this.state.activeKey}
+                                    type="editable-card"
+                                    onEdit={this.onEdit}
+                                >
+
+                                    {this.state.panes.map(pane => <TabPane tab={pane.title} key={pane.key}>{pane.content}</TabPane>)}
+                                </Tabs>
+                            </Card>
+                        </div>
+                    </Col>
+
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default TabsCustom;

+ 136 - 0
src/components/ui/Wysiwyg.jsx

@@ -0,0 +1,136 @@
+/**
+ * Created by hao.cheng on 2017/4/26.
+ */
+import React, { Component } from 'react';
+import { Row, Col, Card } from 'antd';
+import BreadcrumbCustom from '../BreadcrumbCustom';
+import { Editor } from 'react-draft-wysiwyg';
+import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
+import draftToHtml from 'draftjs-to-html';
+import draftToMarkdown from 'draftjs-to-markdown';
+
+const rawContentState = {"entityMap":{"0":{"type":"IMAGE","mutability":"MUTABLE","data":{"src":"http://i.imgur.com/aMtBIep.png","height":"auto","width":"100%"}}},"blocks":[{"key":"9unl6","text":"","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}},{"key":"95kn","text":" ","type":"atomic","depth":0,"inlineStyleRanges":[],"entityRanges":[{"offset":0,"length":1,"key":0}],"data":{}},{"key":"7rjes","text":"","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}]};
+
+class Wysiwyg extends Component {
+    state = {
+        editorContent: undefined,
+        contentState: rawContentState,
+        editorState: '',
+    };
+
+    onEditorChange = (editorContent) => {
+        this.setState({
+            editorContent,
+        });
+    };
+
+    clearContent = () => {
+        this.setState({
+            contentState: '',
+        });
+    };
+
+    onContentStateChange = (contentState) => {
+        console.log('contentState', contentState);
+    };
+
+    onEditorStateChange = (editorState) => {
+        this.setState({
+            editorState,
+        });
+    };
+
+    imageUploadCallBack = file => new Promise(
+        (resolve, reject) => {
+            const xhr = new XMLHttpRequest(); // eslint-disable-line no-undef
+            xhr.open('POST', 'https://api.imgur.com/3/image');
+            xhr.setRequestHeader('Authorization', 'Client-ID 8d26ccd12712fca');
+            const data = new FormData(); // eslint-disable-line no-undef
+            data.append('image', file);
+            xhr.send(data);
+            xhr.addEventListener('load', () => {
+                const response = JSON.parse(xhr.responseText);
+                resolve(response);
+            });
+            xhr.addEventListener('error', () => {
+                const error = JSON.parse(xhr.responseText);
+                reject(error);
+            });
+        }
+    );
+
+    render() {
+        const { editorContent, editorState } = this.state;
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="富文本" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="富文本编辑器" bordered={false} >
+                                <Editor
+                                    editorState={editorState}
+                                    toolbarClassName="home-toolbar"
+                                    wrapperClassName="home-wrapper"
+                                    editorClassName="home-editor"
+                                    onEditorStateChange={this.onEditorStateChange}
+                                    toolbar={{
+                                        history: { inDropdown: true },
+                                        inline: { inDropdown: false },
+                                        list: { inDropdown: true },
+                                        textAlign: { inDropdown: true },
+                                        image: { uploadCallback: this.imageUploadCallBack },
+                                    }}
+                                    onContentStateChange={this.onEditorChange}
+                                    placeholder="请输入正文~~尝试@哦,有惊喜"
+                                    spellCheck
+                                    onFocus={() => {console.log('focus')}}
+                                    onBlur={() => {console.log('blur')}}
+                                    onTab={() => {console.log('tab'); return true;}}
+                                    localization={{ locale: 'zh', translations: {'generic.add': 'Test-Add'} }}
+                                    mention={{
+                                        separator: ' ',
+                                        trigger: '@',
+                                        caseSensitive: true,
+                                        suggestions: [
+                                            { text: 'A', value: 'AB', url: 'href-a' },
+                                            { text: 'AB', value: 'ABC', url: 'href-ab' },
+                                            { text: 'ABC', value: 'ABCD', url: 'href-abc' },
+                                            { text: 'ABCD', value: 'ABCDDDD', url: 'href-abcd' },
+                                            { text: 'ABCDE', value: 'ABCDE', url: 'href-abcde' },
+                                            { text: 'ABCDEF', value: 'ABCDEF', url: 'href-abcdef' },
+                                            { text: 'ABCDEFG', value: 'ABCDEFG', url: 'href-abcdefg' },
+                                        ],
+                                    }}
+                                />
+
+                                <style>{`
+                                    .home-editor {
+                                        min-height: 300px;
+                                    }
+                                `}</style>
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={8}>
+                        <Card title="同步转换HTML" bordered={false}>
+                            <pre>{draftToHtml(editorContent)}</pre>
+                        </Card>
+                    </Col>
+                    <Col className="gutter-row" md={8}>
+                        <Card title="同步转换MarkDown" bordered={false}>
+                            <pre style={{whiteSpace: 'pre-wrap'}}>{draftToMarkdown(editorContent)}</pre>
+                        </Card>
+                    </Col>
+                    <Col className="gutter-row" md={8}>
+                        <Card title="同步转换JSON" bordered={false}>
+                            <pre style={{whiteSpace: 'normal'}}>{JSON.stringify(editorContent)}</pre>
+                        </Card>
+                    </Col>
+                </Row>
+            </div>
+        );
+    }
+}
+
+export default Wysiwyg;

+ 57 - 0
src/components/ui/banners/AutoPlay.jsx

@@ -0,0 +1,57 @@
+/**
+ * Created by hao.cheng on 2017/4/26.
+ */
+import React from 'react';
+import BannerAnim, { Element } from 'rc-banner-anim';
+import TweenOne from 'rc-tween-one';
+import 'rc-banner-anim/assets/index.css';
+const BgElement = Element.BgElement;
+class AutoPlay extends React.Component {
+    render(){
+        return (
+            <BannerAnim prefixCls="banner-user" autoPlay>
+                <Element
+                    prefixCls="banner-user-elem"
+                    key="0"
+                >
+                    <BgElement
+                        key="bg"
+                        className="bg"
+                        style={{
+                            background: '#364D79',
+                        }}
+                    />
+                    <TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
+                        Ant Motion Banner
+                    </TweenOne>
+                    <TweenOne className="banner-user-text"
+                              animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
+                    >
+                        The Fast Way Use Animation In React
+                    </TweenOne>
+                </Element>
+                <Element
+                    prefixCls="banner-user-elem"
+                    key="1"
+                >
+                    <BgElement
+                        key="bg"
+                        className="bg"
+                        style={{
+                            background: '#64CBCC',
+                        }}
+                    />
+                    <TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
+                        Ant Motion Banner
+                    </TweenOne>
+                    <TweenOne className="banner-user-text"
+                              animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
+                    >
+                        The Fast Way Use Animation In React
+                    </TweenOne>
+                </Element>
+            </BannerAnim>);
+    }
+}
+
+export default AutoPlay;

+ 60 - 0
src/components/ui/banners/Basic.jsx

@@ -0,0 +1,60 @@
+/**
+ * Created by hao.cheng on 2017/4/26.
+ */
+import React from 'react';
+import BannerAnim, { Element } from 'rc-banner-anim';
+import TweenOne from 'rc-tween-one';
+import 'rc-banner-anim/assets/index.css';
+const BgElement = Element.BgElement;
+class Basic extends React.Component {
+    render(){
+        return (
+            <BannerAnim prefixCls="banner-user">
+                <Element
+                    prefixCls="banner-user-elem"
+                    key="0"
+                >
+                    <BgElement
+                        key="bg"
+                        className="bg"
+                        style={{
+                            background: '#364D79',
+                        }}
+                    />
+                    <TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
+                        Ant Motion Banner
+                    </TweenOne>
+                    <TweenOne
+                        className="banner-user-text"
+                        animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
+                    >
+                        The Fast Way Use Animation In React
+                    </TweenOne>
+                </Element>
+                <Element
+                    prefixCls="banner-user-elem"
+                    key="1"
+                >
+                    <BgElement
+                        key="bg"
+                        className="bg"
+                        style={{
+                            background: '#64CBCC',
+                        }}
+                    />
+                    <TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
+                        Ant Motion Banner
+                    </TweenOne>
+                    <TweenOne
+                        className="banner-user-text"
+                        animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
+                    >
+                        The Fast Way Use Animation In React
+                    </TweenOne>
+                </Element>
+            </BannerAnim>
+        );
+    }
+}
+
+export default Basic;

+ 188 - 0
src/components/ui/banners/Custom.jsx

@@ -0,0 +1,188 @@
+/**
+ * Created by hao.cheng on 2017/4/26.
+ */
+import React from 'react';
+import BannerAnim from 'rc-banner-anim';
+import TweenOne, { TweenOneGroup } from 'rc-tween-one';
+import 'rc-banner-anim/assets/index.css';
+const { Element, Arrow, Thumb } = BannerAnim;
+const BgElement = Element.BgElement;
+class Custom extends React.Component {
+    constructor() {
+        super(...arguments);
+        this.imgArray = [
+            'https://zos.alipayobjects.com/rmsportal/hzPBTkqtFpLlWCi.jpg',
+            'https://zos.alipayobjects.com/rmsportal/gGlUMYGEIvjDOOw.jpg',
+        ];
+        this.state = {
+            intShow: 0,
+            prevEnter: false,
+            nextEnter: false,
+            thumbEnter: false,
+        };
+        [
+            'onChange',
+            'prevEnter',
+            'prevLeave',
+            'nextEnter',
+            'nextLeave',
+            'onMouseEnter',
+            'onMouseLeave',
+        ].forEach((method) => this[method] = this[method].bind(this));
+    }
+
+    onChange(type, int) {
+        if (type === 'before') {
+            this.setState({
+                intShow: int,
+            });
+        }
+    }
+
+    getNextPrevNumber() {
+        let nextInt = this.state.intShow + 1;
+        let prevInt = this.state.intShow - 1;
+        if (nextInt >= this.imgArray.length) {
+            nextInt = 0;
+        }
+        if (prevInt < 0) {
+            prevInt = this.imgArray.length - 1;
+        }
+
+        return [prevInt, nextInt];
+    }
+
+    prevEnter() {
+        this.setState({
+            prevEnter: true,
+        });
+    }
+
+    prevLeave() {
+        this.setState({
+            prevEnter: false,
+        });
+    }
+
+    nextEnter() {
+        this.setState({
+            nextEnter: true,
+        });
+    }
+
+    nextLeave() {
+        this.setState({
+            nextEnter: false,
+        });
+    }
+
+    onMouseEnter() {
+        this.setState({
+            thumbEnter: true,
+        });
+    }
+
+    onMouseLeave() {
+        this.setState({
+            thumbEnter: false,
+        });
+    }
+
+    render() {
+        const intArray = this.getNextPrevNumber();
+        const thumbChildren = this.imgArray.map((img, i) =>
+            <span key={i}><i style={{ backgroundImage: `url(${img})` }} /></span>
+        );
+        return (
+            <BannerAnim
+                onChange={this.onChange}
+                onMouseEnter={this.onMouseEnter}
+                onMouseLeave={this.onMouseLeave}
+                prefixCls="custom-arrow-thumb"
+            >
+                <Element key="aaa"
+                         prefixCls="banner-user-elem"
+                >
+                    <BgElement
+                        key="bg"
+                        className="bg"
+                        style={{
+                            backgroundImage: `url(${this.imgArray[0]})`,
+                            backgroundSize: 'cover',
+                            backgroundPosition: 'center',
+                        }}
+                    />
+                    <TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
+                        Ant Motion Banner
+                    </TweenOne>
+                    <TweenOne
+                        className="banner-user-text"
+                        animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
+                    >
+                        The Fast Way Use Animation In React
+                    </TweenOne>
+                </Element>
+                <Element key="bbb"
+                         prefixCls="banner-user-elem"
+                >
+                    <BgElement
+                        key="bg"
+                        className="bg"
+                        style={{
+                            backgroundImage: `url(${this.imgArray[1]})`,
+                            backgroundSize: 'cover',
+                            backgroundPosition: 'center',
+                        }}
+                    />
+                    <TweenOne className="banner-user-title" animation={{ y: 30, opacity: 0, type: 'from' }}>
+                        Ant Motion Banner
+                    </TweenOne>
+                    <TweenOne
+                        className="banner-user-text"
+                        animation={{ y: 30, opacity: 0, type: 'from', delay: 100 }}
+                    >
+                        The Fast Way Use Animation In React
+                    </TweenOne>
+                </Element>
+                <Arrow arrowType="prev" key="prev" prefixCls="user-arrow" component={TweenOne}
+                       onMouseEnter={this.prevEnter}
+                       onMouseLeave={this.prevLeave}
+                       animation={{ left: this.state.prevEnter ? 0 : -120 }}
+                >
+                    <div className="arrow" />
+                    <TweenOneGroup
+                        enter={{ opacity: 0, type: 'from' }}
+                        leave={{ opacity: 0 }}
+                        appear={false}
+                        className="img-wrapper" component="ul"
+                    >
+                        <li style={{ backgroundImage: `url(${this.imgArray[intArray[0]]})`}} key={intArray[0]} />
+                    </TweenOneGroup>
+                </Arrow>
+                <Arrow arrowType="next" key="next" prefixCls="user-arrow" component={TweenOne}
+                       onMouseEnter={this.nextEnter}
+                       onMouseLeave={this.nextLeave}
+                       animation={{ right: this.state.nextEnter ? 0 : -120 }}
+                >
+                    <div className="arrow" />
+                    <TweenOneGroup
+                        enter={{ opacity: 0, type: 'from' }}
+                        leave={{ opacity: 0 }}
+                        appear={false}
+                        className="img-wrapper"
+                        component="ul"
+                    >
+                        <li style={{ backgroundImage: `url(${this.imgArray[intArray[1]]})`}} key={intArray[1]} />
+                    </TweenOneGroup>
+                </Arrow>
+                <Thumb prefixCls="user-thumb" key="thumb" component={TweenOne}
+                       animation={{ bottom: this.state.thumbEnter ? 0 : -70 }}
+                >
+                    {thumbChildren}
+                </Thumb>
+            </BannerAnim>
+        );
+    }
+}
+
+export default Custom;

+ 45 - 0
src/components/ui/banners/index.jsx

@@ -0,0 +1,45 @@
+/**
+ * Created by hao.cheng on 2017/4/26.
+ */
+import React from 'react';
+import { Row, Col, Card } from 'antd';
+import BreadcrumbCustom from '../../BreadcrumbCustom';
+import Basic from './Basic';
+import AutoPlay from './AutoPlay';
+// import Custom from './Custom';
+
+class Banners extends React.Component {
+    render() {
+        return (
+            <div className="gutter-example button-demo">
+                <BreadcrumbCustom first="UI" second="轮播图" />
+                <Row gutter={16}>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="基本用法" bordered={false}>
+                                <Basic />
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="自动轮播-默认5秒" bordered={false}>
+                                <AutoPlay />
+                            </Card>
+                        </div>
+                    </Col>
+                    <Col className="gutter-row" md={24}>
+                        <div className="gutter-box">
+                            <Card title="自定义左右箭头与缩略图" bordered={false}>
+                                {/*引入自定义会导致组件冲突不显示*/}
+                                {/*<Custom />*/}
+                            </Card>
+                        </div>
+                    </Col>
+                </Row>
+            </div>
+        )
+    }
+}
+
+export default Banners;

File diff suppressed because it is too large
+ 2 - 0
src/components/ui/emoji/iconfont.js


+ 35 - 0
src/components/ui/emoji/index.jsx

@@ -0,0 +1,35 @@
+/**
+ * Created by hao.cheng on 2017/4/22.
+ */
+import React from 'react';
+import PropTypes from 'prop-types';
+import './iconfont';
+
+const Emoji = ({type}) => {
+    const useTag = `<use xlink:href=${'#icon-' + type} />`;
+    return (
+        <i className="emoji">
+            <svg className="emoji" dangerouslySetInnerHTML={{__html: useTag }} />
+            <style>{`
+            .emoji {
+                display: inline-block;
+                overflow: hidden;
+            }
+            .emoji svg {
+                width: 3em;
+                height: 3em;
+                vertical-align: -0.15em;
+                fill: currentColor;
+                overflow: hidden;
+            }
+        `}</style>
+        </i>
+
+    );
+};
+
+Emoji.propTypes = {
+    type: PropTypes.string.isRequired
+};
+
+export default Emoji;

Some files were not shown because too many files changed in this diff