first commit
4
.commitlintrc.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
1
.env.ra.dev
Normal file
@@ -0,0 +1 @@
|
|||||||
|
REACT_ADMIN_ENV=我是本地开发环境配置
|
||||||
2
.env.ra.production
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
REACT_ADMIN_ENV=我是打包线上环境配置
|
||||||
|
REACT_ADMIN_TEST=我是其他环境配置
|
||||||
1
.env.ra.starandsea
Normal file
@@ -0,0 +1 @@
|
|||||||
|
REACT_ADMIN_ENV=我是星辰大海,你运行yarn starandsea试试
|
||||||
19
.eslintrc
Normal file
@@ -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模块名使用帕斯卡命名,实例使用骆驼式命名
|
||||||
|
}
|
||||||
|
}
|
||||||
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
public/theme.less
|
||||||
7
.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true,
|
||||||
|
"jsxBracketSameLine": false,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
6
.travis.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "10"
|
||||||
|
script:
|
||||||
|
- yarn
|
||||||
|
- yarn build
|
||||||
125
CHANGELOG.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
### 更新日志
|
||||||
|
|
||||||
|
#### 2017-07-08
|
||||||
|
|
||||||
|
- 依赖包版本升级
|
||||||
|
- react@15.6.1
|
||||||
|
- antd@2.11.2
|
||||||
|
- webpack@2.6.1
|
||||||
|
- 等等
|
||||||
|
|
||||||
|
#### 2017-08-01
|
||||||
|
|
||||||
|
- 引入 redux 系列
|
||||||
|
- redux@3.7.2
|
||||||
|
- redux-thunk@2.2.0
|
||||||
|
- react-redux@5.0.5
|
||||||
|
- 增加权限管理模块
|
||||||
|
- 使用 easy-mock 模拟数据模拟登录接口
|
||||||
|
- 使用 redux 系列将登录用户数据传递给权限组件
|
||||||
|
- 权限组件采用 Render Callback 的方式传递权限给需要受控制的组件(具体做法请查看源代码。)
|
||||||
|
- 用户状态保存在 localStorage 中
|
||||||
|
- 具体做法请运行项目查看
|
||||||
|
- PS:以上管理权限只是一种方式,但这绝对不是唯一的方式,也不是最好的方式。如果你有更好的方式,不妨加上面的群和大家一起分享下。😄😄
|
||||||
|
- 增加路径别名
|
||||||
|
- 使用@别名处理引入组件相对路径过长问题。
|
||||||
|
- 缺点:编辑器不能使用快捷提示和快捷跳转到相应的文件
|
||||||
|
|
||||||
|
#### 2017-08-13
|
||||||
|
|
||||||
|
- 权限管理模块增加页面跳转权限验证
|
||||||
|
- 点击权限管理的路由拦截,若没有访问权限则会跳转到 404 页面。
|
||||||
|
- 大致实现方式(非常简单):通过向自定义 router 组件传入 store,登录之后可获取到 redux 中的权限 state 数据,并通过判断是否包含权限进行跳转。ps: 该 demo 的效果是管理员登录之后才能跳转到路由拦截页面。具体操作请拉取代码尝试。
|
||||||
|
|
||||||
|
#### 2017-08-26
|
||||||
|
|
||||||
|
- 增加响应式布局 - 替换 antd Col 组件的响应式栅格为 md(具体参数用法请查看 antd 官方文档) - 初始化页面是获取当前浏览器宽度设置菜单显示类型 - 监听 window 的 onresize 函数,设置菜单显示类型。PS:浏览器宽度存入 redux 中,方便组件之间传递。
|
||||||
|

|
||||||
|
|
||||||
|
#### 2017-09-13
|
||||||
|
|
||||||
|
- 依赖包版本升级
|
||||||
|
- antd@2.13.1(目前最新版)
|
||||||
|
|
||||||
|
#### 2017-10-21
|
||||||
|
|
||||||
|
- 开发环境增加 react-hot-loader-保持状态刷新组件(译:实时调整组件),可参考以下相关项目
|
||||||
|
- [react-hot-loader](https://github.com/gaearon/react-hot-loader)
|
||||||
|
|
||||||
|
#### 2017-12-12
|
||||||
|
|
||||||
|
- 依赖包版本升级
|
||||||
|
- antd@3.0.1(目前最新版)
|
||||||
|
- react-router-dom@4.2.2
|
||||||
|
- 大改动
|
||||||
|
- react-router 切换 4.x 版本,切换响应的版本路由写法(具体见代码更新日志)
|
||||||
|
- ps: react-router 3.x 的版本请查看代码分支 router3.x
|
||||||
|
|
||||||
|
#### 2018-01-12
|
||||||
|
|
||||||
|
- 增加 cssmodule 的支持(css, less)
|
||||||
|
|
||||||
|
- 建议用 css 预处理器,文件名为 xxx.module.less,引入相应组件即可使用。
|
||||||
|
|
||||||
|
- 具体做法参见新增模块,路由后缀:/app/cssModule。[点击访问](http://cheng_haohao.oschina.io/reactadmin/#/app/cssModule)
|
||||||
|
|
||||||
|
#### 2018-10-13
|
||||||
|
|
||||||
|
- 重大更新 :sparkles:
|
||||||
|
- 升级 create-react-app 2.x,详情文档见[官方文档](https://reactjs.org/blog/2018/10/01/create-react-app-v2.html)
|
||||||
|
- 升级大部分第三方库,升级版本见[commit](https://github.com/yezihaohao/react-admin/commit/d8dc0ff0c6517c57a46d731adba69112a55145a9#diff-b9cfc7f2cdf78a7f4b91a753d10865a2)
|
||||||
|
- 增加自定义主题功能 - 主题基础样式配置见[variables.less](https://github.com/yezihaohao/react-admin/blob/master/src/style/antd/variables.less) - 修改主题基础样式后执行`yarn theme 或 npm run theme`,默认主题即可生效 - 页面上可自定义主题颜色配置(根据此可添加字体大小等其他 antd 的默认样式)
|
||||||
|

|
||||||
|
|
||||||
|
#### 2018-11-07
|
||||||
|
|
||||||
|
- 完善 PWA 的 manifest.json 文件,增加按钮手动触发安装 PWA 应用
|
||||||
|
- 最新版的 chrome 浏览器访问[ReactAdmin](https://admiring-dijkstra-34cb29.netlify.com/)即可体验
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 2018-11-26
|
||||||
|
|
||||||
|
- 增加问号形式的路由参数扩展
|
||||||
|
|
||||||
|
#### 2018-12-28
|
||||||
|
|
||||||
|
- 增加[react-document-title](https://github.com/gaearon/react-document-title)组件,根据路由设置页面 title
|
||||||
|
|
||||||
|
#### 2019-03-20
|
||||||
|
|
||||||
|
- 增加[redux-alita](https://github.com/yezihaohao/redux-alita),极简的 redux 工具用法,详情见其代码仓库
|
||||||
|
|
||||||
|
#### 2019-05-10
|
||||||
|
|
||||||
|
- 升级 react,react-dom,增加 hooks 支持(去掉 react-hot-loader,老版本 hot-loader 使用 hook 有点问题)
|
||||||
|
- 增加菜单可拖拽
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 2019-09-04
|
||||||
|
|
||||||
|
- 增加 Git 提交 message 规范约束工具[commitizen](https://github.com/commitizen/cz-cli)
|
||||||
|
- Git 提交规范往往是团队编码必需,借助工具能形成更好的约束,如果你不喜欢用,可参照提交记录去掉[bd426fd](https://github.com/yezihaohao/react-admin/commit/a9401d191edd077bc3e59c8dbeeb61e5029cde95)
|
||||||
|
|
||||||
|
#### 2019-09-26
|
||||||
|
|
||||||
|
- 更新 create-react-app3.x 版本,升级部分依赖 lib,详情请查看提交记录(有问题请提 issue)
|
||||||
|
|
||||||
|
#### 2019-10-26
|
||||||
|
|
||||||
|
- 新增访客模式的路由配置+demo(主路由配置)
|
||||||
|
- [在线 Demo](https://admiring-dijkstra-34cb29.netlify.com/#/app/extension/visitor)
|
||||||
|
|
||||||
|
#### 2019-12-18
|
||||||
|
|
||||||
|
- 新增多级菜单配置功能(菜单可配置成无限的树状菜单,菜单嵌套过多时,样式问题可能需要你调整)
|
||||||
|
|
||||||
|
#### 2020-01-21
|
||||||
|
|
||||||
|
- 新增服务端异步菜单功能
|
||||||
|
|
||||||
|
#### 2020-08-02
|
||||||
|
|
||||||
|
- 新增多环境配置方案,环境配置任你加,源码详情请查看[提交记录](https://github.com/yezihaohao/react-admin/commit/d2cb53dca7e7179c794dc9e699d057ed549aec62)
|
||||||
|
- 根目录增加 .env.ra.xxx,其中 xxx 是 package.json 中运行的脚本命令第三个参数,请结合项目查看
|
||||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 yezihaohao
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
248
README.md
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
# 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" />
|
||||||
|
|
||||||
|

|
||||||
|
[](http://makeapullrequest.com)
|
||||||
|
|
||||||
|
<!-- ### 🔥🔥🔥 默认主分支切换成 TypeScript 开发,如果你希望继续使用 JS 开发,请使用[JS](https://github.com/yezihaohao/react-admin/tree/js)分支代码(继续维护) -->
|
||||||
|
|
||||||
|
### 文档地址:[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)😁(重要!对于了解项目部分功能和代码很有用!)
|
||||||
|
|
||||||
|
### cli 安装和使用 react-admin 🔥
|
||||||
|
|
||||||
|
使用 [saigao](https://github.com/yezihaohao/saigao) 快速安装和下载 react-admin 模板来开发项目:
|
||||||
|
|
||||||
|
```js
|
||||||
|
npm i -g saigao
|
||||||
|
|
||||||
|
saigao myapp
|
||||||
|
// 或者使用npx 下载模板
|
||||||
|
npx saigao myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前言
|
||||||
|
|
||||||
|
> 网上 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 页面
|
||||||
|
|
||||||
|
### 功能截图
|
||||||
|
|
||||||
|
#### 首页
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 按钮图标等
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 轮播图
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 富文本
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 拖拽
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 画廊
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 动画
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 表格
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 表单
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 图表
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 页面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 菜单拖拽
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 代码目录
|
||||||
|
|
||||||
|
```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 安装依赖(国内建议增加淘宝镜像源,不然很慢,你懂的 😁)
|
||||||
|
|
||||||
|
> 有些老铁遇到运行时报错,首先确定下是不是最新稳定版的 nodejs 和 yarn,切记不要用 cnpm
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 首推荐使用yarn装包,避免自动升级依赖包
|
||||||
|
yarn;
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 3.启动项目
|
||||||
|
|
||||||
|
```js
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 4.打包项目
|
||||||
|
|
||||||
|
```js
|
||||||
|
yarn 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)
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
### 结尾
|
||||||
|
|
||||||
|
该项目会不定时更新,后续时间会添加更多的模块
|
||||||
|
|
||||||
|
欢迎和感谢大家 PR~~👏👏
|
||||||
|
|
||||||
|
若有问题,可加 QQ 群与我交流
|
||||||
|
|
||||||
|
- 1 群:264591039(已满)
|
||||||
|
- 2 群:592688854(已满)
|
||||||
|
- 3 群:743490497 (已满)
|
||||||
|
- 4 群:150131600 (已满)
|
||||||
|
|
||||||
|
如果对你有帮助,给个 star 哟~~❤️❤️❤️❤️
|
||||||
94
config/env.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
'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;
|
||||||
|
const REACT_ADMIN = process.env.REACT_ADMIN;
|
||||||
|
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
|
||||||
|
const dotenvFiles = [
|
||||||
|
`${paths.dotenv}.${NODE_ENV}.local`,
|
||||||
|
`${paths.dotenv}.${NODE_ENV}`,
|
||||||
|
`${paths.dotenv}.ra.${REACT_ADMIN}`,
|
||||||
|
// 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;
|
||||||
|
const REACT_ADMIN_REG = /^REACT_ADMIN_/i;
|
||||||
|
|
||||||
|
function getClientEnvironment(publicUrl) {
|
||||||
|
const raw = Object.keys(process.env)
|
||||||
|
.filter((key) => REACT_APP.test(key) || REACT_ADMIN_REG.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
config/jest/cssTransform.js
Normal file
@@ -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';
|
||||||
|
},
|
||||||
|
};
|
||||||
40
config/jest/fileTransform.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const camelcase = require('camelcase');
|
||||||
|
|
||||||
|
// 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$/)) {
|
||||||
|
// Based on how SVGR generates a component name:
|
||||||
|
// https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
|
||||||
|
const pascalCaseFilename = camelcase(path.parse(filename).name, {
|
||||||
|
pascalCase: true,
|
||||||
|
});
|
||||||
|
const componentName = `Svg${pascalCaseFilename}`;
|
||||||
|
return `const React = require('react');
|
||||||
|
module.exports = {
|
||||||
|
__esModule: true,
|
||||||
|
default: ${assetFilename},
|
||||||
|
ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
|
||||||
|
return {
|
||||||
|
$$typeof: Symbol.for('react.element'),
|
||||||
|
type: 'svg',
|
||||||
|
ref: ref,
|
||||||
|
key: null,
|
||||||
|
props: Object.assign({}, props, {
|
||||||
|
children: ${assetFilename}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `module.exports = ${assetFilename};`;
|
||||||
|
},
|
||||||
|
};
|
||||||
141
config/modules.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const paths = require('./paths');
|
||||||
|
const chalk = require('react-dev-utils/chalk');
|
||||||
|
const resolve = require('resolve');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get additional module paths based on the baseUrl of a compilerOptions object.
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
*/
|
||||||
|
function getAdditionalModulePaths(options = {}) {
|
||||||
|
const baseUrl = options.baseUrl;
|
||||||
|
|
||||||
|
// We need to explicitly check for null and undefined (and not a falsy value) because
|
||||||
|
// TypeScript treats an empty string as `.`.
|
||||||
|
if (baseUrl == null) {
|
||||||
|
// If there's no baseUrl set we respect NODE_PATH
|
||||||
|
// Note that NODE_PATH is deprecated and will be removed
|
||||||
|
// in the next major release of create-react-app.
|
||||||
|
|
||||||
|
const nodePath = process.env.NODE_PATH || '';
|
||||||
|
return nodePath.split(path.delimiter).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||||
|
|
||||||
|
// We don't need to do anything if `baseUrl` is set to `node_modules`. This is
|
||||||
|
// the default behavior.
|
||||||
|
if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow the user set the `baseUrl` to `appSrc`.
|
||||||
|
if (path.relative(paths.appSrc, baseUrlResolved) === '') {
|
||||||
|
return [paths.appSrc];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the path is equal to the root directory we ignore it here.
|
||||||
|
// We don't want to allow importing from the root directly as source files are
|
||||||
|
// not transpiled outside of `src`. We do allow importing them with the
|
||||||
|
// absolute path (e.g. `src/Components/Button.js`) but we set that up with
|
||||||
|
// an alias.
|
||||||
|
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, throw an error.
|
||||||
|
throw new Error(
|
||||||
|
chalk.red.bold(
|
||||||
|
"Your project's `baseUrl` can only be set to `src` or `node_modules`." +
|
||||||
|
' Create React App does not support other values at this time.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get webpack aliases based on the baseUrl of a compilerOptions object.
|
||||||
|
*
|
||||||
|
* @param {*} options
|
||||||
|
*/
|
||||||
|
function getWebpackAliases(options = {}) {
|
||||||
|
const baseUrl = options.baseUrl;
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||||
|
|
||||||
|
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||||
|
return {
|
||||||
|
src: paths.appSrc,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get jest aliases based on the baseUrl of a compilerOptions object.
|
||||||
|
*
|
||||||
|
* @param {*} options
|
||||||
|
*/
|
||||||
|
function getJestAliases(options = {}) {
|
||||||
|
const baseUrl = options.baseUrl;
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
|
||||||
|
|
||||||
|
if (path.relative(paths.appPath, baseUrlResolved) === '') {
|
||||||
|
return {
|
||||||
|
'src/(.*)$': '<rootDir>/src/$1',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModules() {
|
||||||
|
// Check if TypeScript is setup
|
||||||
|
const hasTsConfig = fs.existsSync(paths.appTsConfig);
|
||||||
|
const hasJsConfig = fs.existsSync(paths.appJsConfig);
|
||||||
|
|
||||||
|
if (hasTsConfig && hasJsConfig) {
|
||||||
|
throw new Error(
|
||||||
|
'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config;
|
||||||
|
|
||||||
|
// If there's a tsconfig.json we assume it's a
|
||||||
|
// TypeScript project and set up the config
|
||||||
|
// based on tsconfig.json
|
||||||
|
if (hasTsConfig) {
|
||||||
|
const ts = require(resolve.sync('typescript', {
|
||||||
|
basedir: paths.appNodeModules,
|
||||||
|
}));
|
||||||
|
config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
|
||||||
|
// Otherwise we'll check if there is jsconfig.json
|
||||||
|
// for non TS projects.
|
||||||
|
} else if (hasJsConfig) {
|
||||||
|
config = require(paths.appJsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
config = config || {};
|
||||||
|
const options = config.compilerOptions || {};
|
||||||
|
|
||||||
|
const additionalModulePaths = getAdditionalModulePaths(options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
additionalModulePaths: additionalModulePaths,
|
||||||
|
webpackAliases: getWebpackAliases(options),
|
||||||
|
jestAliases: getJestAliases(options),
|
||||||
|
hasTsConfig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getModules();
|
||||||
90
config/paths.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
'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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleFileExtensions = [
|
||||||
|
'web.mjs',
|
||||||
|
'mjs',
|
||||||
|
'web.js',
|
||||||
|
'js',
|
||||||
|
'web.ts',
|
||||||
|
'ts',
|
||||||
|
'web.tsx',
|
||||||
|
'tsx',
|
||||||
|
'json',
|
||||||
|
'web.jsx',
|
||||||
|
'jsx',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Resolve file paths in the same order as webpack
|
||||||
|
const resolveModule = (resolveFn, filePath) => {
|
||||||
|
const extension = moduleFileExtensions.find(extension =>
|
||||||
|
fs.existsSync(resolveFn(`${filePath}.${extension}`))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (extension) {
|
||||||
|
return resolveFn(`${filePath}.${extension}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveFn(`${filePath}.js`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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: resolveModule(resolveApp, 'src/index'),
|
||||||
|
appPackageJson: resolveApp('package.json'),
|
||||||
|
appSrc: resolveApp('src'),
|
||||||
|
appTsConfig: resolveApp('tsconfig.json'),
|
||||||
|
appJsConfig: resolveApp('jsconfig.json'),
|
||||||
|
yarnLockFile: resolveApp('yarn.lock'),
|
||||||
|
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
|
||||||
|
proxySetup: resolveApp('src/setupProxy.js'),
|
||||||
|
appNodeModules: resolveApp('node_modules'),
|
||||||
|
publicUrl: getPublicUrl(resolveApp('package.json')),
|
||||||
|
servedPath: getServedPath(resolveApp('package.json')),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports.moduleFileExtensions = moduleFileExtensions;
|
||||||
35
config/pnpTs.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { resolveModuleName } = require('ts-pnp');
|
||||||
|
|
||||||
|
exports.resolveModuleName = (
|
||||||
|
typescript,
|
||||||
|
moduleName,
|
||||||
|
containingFile,
|
||||||
|
compilerOptions,
|
||||||
|
resolutionHost
|
||||||
|
) => {
|
||||||
|
return resolveModuleName(
|
||||||
|
moduleName,
|
||||||
|
containingFile,
|
||||||
|
compilerOptions,
|
||||||
|
resolutionHost,
|
||||||
|
typescript.resolveModuleName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.resolveTypeReferenceDirective = (
|
||||||
|
typescript,
|
||||||
|
moduleName,
|
||||||
|
containingFile,
|
||||||
|
compilerOptions,
|
||||||
|
resolutionHost
|
||||||
|
) => {
|
||||||
|
return resolveModuleName(
|
||||||
|
moduleName,
|
||||||
|
containingFile,
|
||||||
|
compilerOptions,
|
||||||
|
resolutionHost,
|
||||||
|
typescript.resolveTypeReferenceDirective
|
||||||
|
);
|
||||||
|
};
|
||||||
713
config/webpack.config.js
Normal file
@@ -0,0 +1,713 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const isWsl = require('is-wsl');
|
||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const resolve = require('resolve');
|
||||||
|
const PnpWebpackPlugin = require('pnp-webpack-plugin');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const CaseSensitivePathsPlugin = require('case-sensitive-paths-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 WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
|
||||||
|
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||||
|
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
|
||||||
|
const paths = require('./paths');
|
||||||
|
const modules = require('./modules');
|
||||||
|
const getClientEnvironment = require('./env');
|
||||||
|
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
|
||||||
|
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
|
||||||
|
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
|
||||||
|
const eslint = require('eslint');
|
||||||
|
|
||||||
|
const postcssNormalize = require('postcss-normalize');
|
||||||
|
|
||||||
|
const appPackageJson = require(paths.appPackageJson);
|
||||||
|
|
||||||
|
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||||
|
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
|
||||||
|
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
|
||||||
|
// makes for a smoother build process.
|
||||||
|
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
|
||||||
|
|
||||||
|
const imageInlineSizeLimit = parseInt(
|
||||||
|
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if TypeScript is setup
|
||||||
|
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||||
|
|
||||||
|
// 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/;
|
||||||
|
|
||||||
|
// This is the production and development configuration.
|
||||||
|
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
|
||||||
|
module.exports = function(webpackEnv) {
|
||||||
|
const isEnvDevelopment = webpackEnv === 'development';
|
||||||
|
const isEnvProduction = webpackEnv === 'production';
|
||||||
|
|
||||||
|
// Variable used for enabling profiling in Production
|
||||||
|
// passed into alias object. Uses a flag if passed into the build command
|
||||||
|
const isEnvProductionProfile =
|
||||||
|
isEnvProduction && process.argv.includes('--profile');
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// In development, we always serve from the root. This makes config easier.
|
||||||
|
const publicPath = isEnvProduction
|
||||||
|
? paths.servedPath
|
||||||
|
: isEnvDevelopment && '/';
|
||||||
|
// 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 === './';
|
||||||
|
|
||||||
|
// `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 = isEnvProduction
|
||||||
|
? publicPath.slice(0, -1)
|
||||||
|
: isEnvDevelopment && '';
|
||||||
|
// Get environment variables to inject into our app.
|
||||||
|
const env = getClientEnvironment(publicUrl);
|
||||||
|
|
||||||
|
// common function to get style loaders
|
||||||
|
const getStyleLoaders = (cssOptions, preProcessor) => {
|
||||||
|
const loaders = [
|
||||||
|
isEnvDevelopment && require.resolve('style-loader'),
|
||||||
|
isEnvProduction && {
|
||||||
|
loader: MiniCssExtractPlugin.loader,
|
||||||
|
options: shouldUseRelativeAssetPaths ? { publicPath: '../../' } : {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
// Adds PostCSS Normalize as the reset css with default options,
|
||||||
|
// so that it honors browserslist config in package.json
|
||||||
|
// which in turn let's users customize the target behavior as per their needs.
|
||||||
|
postcssNormalize(),
|
||||||
|
],
|
||||||
|
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
].filter(Boolean);
|
||||||
|
if (preProcessor) {
|
||||||
|
loaders.push(
|
||||||
|
{
|
||||||
|
loader: require.resolve('resolve-url-loader'),
|
||||||
|
options: {
|
||||||
|
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: require.resolve(preProcessor),
|
||||||
|
options: {
|
||||||
|
sourceMap: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return loaders;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
|
||||||
|
// Stop compilation early in production
|
||||||
|
bail: isEnvProduction,
|
||||||
|
devtool: isEnvProduction
|
||||||
|
? shouldUseSourceMap
|
||||||
|
? 'source-map'
|
||||||
|
: false
|
||||||
|
: isEnvDevelopment && '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'),
|
||||||
|
isEnvDevelopment &&
|
||||||
|
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.
|
||||||
|
].filter(Boolean),
|
||||||
|
output: {
|
||||||
|
// The build folder.
|
||||||
|
path: isEnvProduction ? paths.appBuild : undefined,
|
||||||
|
// Add /* filename */ comments to generated require()s in the output.
|
||||||
|
pathinfo: isEnvDevelopment,
|
||||||
|
// There will be one main bundle, and one file per asynchronous chunk.
|
||||||
|
// In development, it does not produce real files.
|
||||||
|
filename: isEnvProduction
|
||||||
|
? 'static/js/[name].[contenthash:8].js'
|
||||||
|
: isEnvDevelopment && 'static/js/bundle.js',
|
||||||
|
// TODO: remove this when upgrading to webpack 5
|
||||||
|
futureEmitAssets: true,
|
||||||
|
// There are also additional JS chunk files if you use code splitting.
|
||||||
|
chunkFilename: isEnvProduction
|
||||||
|
? 'static/js/[name].[contenthash:8].chunk.js'
|
||||||
|
: isEnvDevelopment && 'static/js/[name].chunk.js',
|
||||||
|
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||||
|
// We use "/" in development.
|
||||||
|
publicPath: publicPath,
|
||||||
|
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||||
|
devtoolModuleFilenameTemplate: isEnvProduction
|
||||||
|
? info =>
|
||||||
|
path
|
||||||
|
.relative(paths.appSrc, info.absoluteResourcePath)
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
: isEnvDevelopment &&
|
||||||
|
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
|
||||||
|
// Prevents conflicts when multiple Webpack runtimes (from different apps)
|
||||||
|
// are used on the same page.
|
||||||
|
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
|
||||||
|
// this defaults to 'window', but by setting it to 'this' then
|
||||||
|
// module chunks which are built will work in web workers as well.
|
||||||
|
globalObject: 'this',
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: isEnvProduction,
|
||||||
|
minimizer: [
|
||||||
|
// This is only used in production mode
|
||||||
|
new TerserPlugin({
|
||||||
|
terserOptions: {
|
||||||
|
parse: {
|
||||||
|
// We want terser to parse ecma 8 code. However, we don't want it
|
||||||
|
// to apply any minification 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 further investigation:
|
||||||
|
// https://github.com/terser-js/terser/issues/120
|
||||||
|
inline: 2,
|
||||||
|
},
|
||||||
|
mangle: {
|
||||||
|
safari10: true,
|
||||||
|
},
|
||||||
|
// Added for profiling in devtools
|
||||||
|
keep_classnames: isEnvProductionProfile,
|
||||||
|
keep_fnames: isEnvProductionProfile,
|
||||||
|
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
|
||||||
|
// Disabled on WSL (Windows Subsystem for Linux) due to an issue with Terser
|
||||||
|
// https://github.com/webpack-contrib/terser-webpack-plugin/issues/21
|
||||||
|
parallel: !isWsl,
|
||||||
|
// Enable file caching
|
||||||
|
cache: true,
|
||||||
|
sourceMap: shouldUseSourceMap,
|
||||||
|
}),
|
||||||
|
// This is only used in production mode
|
||||||
|
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 separated to enable long term caching
|
||||||
|
// https://twitter.com/wSokra/status/969679223278505985
|
||||||
|
// https://github.com/facebook/create-react-app/issues/5358
|
||||||
|
runtimeChunk: {
|
||||||
|
name: entrypoint => `runtime-${entrypoint.name}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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', paths.appNodeModules].concat(
|
||||||
|
modules.additionalModulePaths || []
|
||||||
|
),
|
||||||
|
// 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: paths.moduleFileExtensions
|
||||||
|
.map(ext => `.${ext}`)
|
||||||
|
.filter(ext => useTypeScript || !ext.includes('ts')),
|
||||||
|
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',
|
||||||
|
// Allows for better profiling with ReactDevTools
|
||||||
|
...(isEnvProductionProfile && {
|
||||||
|
'react-dom$': 'react-dom/profiling',
|
||||||
|
'scheduler/tracing': 'scheduler/tracing-profiling',
|
||||||
|
}),
|
||||||
|
...(modules.webpackAliases || {}),
|
||||||
|
'@': 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|ts|tsx)$/,
|
||||||
|
enforce: 'pre',
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
cache: true,
|
||||||
|
formatter: require.resolve('react-dev-utils/eslintFormatter'),
|
||||||
|
eslintPath: require.resolve('eslint'),
|
||||||
|
resolvePluginsRelativeTo: __dirname,
|
||||||
|
|
||||||
|
},
|
||||||
|
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: imageInlineSizeLimit,
|
||||||
|
name: 'static/media/[name].[hash:8].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Process application JS with Babel.
|
||||||
|
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
|
||||||
|
{
|
||||||
|
test: /\.(js|mjs|jsx|ts|tsx)$/,
|
||||||
|
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?-svgo,+titleProp,+ref![path]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// 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,
|
||||||
|
// See #6846 for context on why cacheCompression is disabled
|
||||||
|
cacheCompression: false,
|
||||||
|
compact: isEnvProduction,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 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,
|
||||||
|
// See #6846 for context on why cacheCompression is disabled
|
||||||
|
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 MiniCSSExtractPlugin 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,
|
||||||
|
sourceMap: isEnvProduction && 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,
|
||||||
|
use: getStyleLoaders({
|
||||||
|
importLoaders: 1,
|
||||||
|
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||||
|
modules: true,
|
||||||
|
getLocalIdent: getCSSModuleLocalIdent,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
// Opt-in support for SASS (using .scss or .sass extensions).
|
||||||
|
// By default we support SASS Modules with the
|
||||||
|
// extensions .module.scss or .module.sass
|
||||||
|
{
|
||||||
|
test: sassRegex,
|
||||||
|
exclude: sassModuleRegex,
|
||||||
|
use: getStyleLoaders(
|
||||||
|
{
|
||||||
|
importLoaders: 2,
|
||||||
|
sourceMap: isEnvProduction && 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,
|
||||||
|
use: getStyleLoaders(
|
||||||
|
{
|
||||||
|
importLoaders: 2,
|
||||||
|
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||||
|
modules: true,
|
||||||
|
getLocalIdent: getCSSModuleLocalIdent,
|
||||||
|
},
|
||||||
|
'sass-loader'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: lessRegex,
|
||||||
|
exclude: lessModuleRegex,
|
||||||
|
use: getStyleLoaders(
|
||||||
|
{
|
||||||
|
importLoaders: 2,
|
||||||
|
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||||
|
},
|
||||||
|
'less-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: lessModuleRegex,
|
||||||
|
use: getStyleLoaders(
|
||||||
|
{
|
||||||
|
importLoaders: 2,
|
||||||
|
sourceMap: isEnvProduction && shouldUseSourceMap,
|
||||||
|
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.
|
||||||
|
{
|
||||||
|
loader: require.resolve('file-loader'),
|
||||||
|
// 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|ts|tsx)$/, /\.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(
|
||||||
|
Object.assign(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
inject: true,
|
||||||
|
template: paths.appHtml,
|
||||||
|
},
|
||||||
|
isEnvProduction
|
||||||
|
? {
|
||||||
|
minify: {
|
||||||
|
removeComments: true,
|
||||||
|
collapseWhitespace: true,
|
||||||
|
removeRedundantAttributes: true,
|
||||||
|
useShortDoctype: true,
|
||||||
|
removeEmptyAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
keepClosingSlash: true,
|
||||||
|
minifyJS: true,
|
||||||
|
minifyCSS: true,
|
||||||
|
minifyURLs: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// Inlines the webpack runtime script. This script is too small to warrant
|
||||||
|
// a network request.
|
||||||
|
// https://github.com/facebook/create-react-app/issues/5358
|
||||||
|
isEnvProduction &&
|
||||||
|
shouldInlineRuntimeChunk &&
|
||||||
|
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="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.
|
||||||
|
// 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 === 'production') { ... }. See `./env.js`.
|
||||||
|
// It is absolutely essential that NODE_ENV is set to production
|
||||||
|
// during a production build.
|
||||||
|
// Otherwise React will be compiled in the very slow development mode.
|
||||||
|
new webpack.DefinePlugin(env.stringified),
|
||||||
|
// This is necessary to emit hot updates (currently CSS only):
|
||||||
|
isEnvDevelopment && 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
|
||||||
|
isEnvDevelopment && 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
|
||||||
|
isEnvDevelopment &&
|
||||||
|
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||||
|
isEnvProduction &&
|
||||||
|
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 an asset manifest file with the following content:
|
||||||
|
// - "files" key: Mapping of all asset filenames to their corresponding
|
||||||
|
// output file so that tools can pick it up without having to parse
|
||||||
|
// `index.html`
|
||||||
|
// - "entrypoints" key: Array of files which are included in `index.html`,
|
||||||
|
// can be used to reconstruct the HTML if necessary
|
||||||
|
new ManifestPlugin({
|
||||||
|
fileName: 'asset-manifest.json',
|
||||||
|
publicPath: publicPath,
|
||||||
|
generate: (seed, files, entrypoints) => {
|
||||||
|
const manifestFiles = files.reduce((manifest, file) => {
|
||||||
|
manifest[file.name] = file.path;
|
||||||
|
return manifest;
|
||||||
|
}, seed);
|
||||||
|
const entrypointFiles = entrypoints.main.filter(
|
||||||
|
fileName => !fileName.endsWith('.map')
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
files: manifestFiles,
|
||||||
|
entrypoints: entrypointFiles,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// 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.
|
||||||
|
isEnvProduction &&
|
||||||
|
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 any URLs whose last part seems to be a file extension
|
||||||
|
// as they're likely a resource and not a SPA route.
|
||||||
|
// URLs containing a "?" character won't be blacklisted as they're likely
|
||||||
|
// a route with query params (e.g. auth callbacks).
|
||||||
|
new RegExp('/[^/?]+\\.[^/]+$'),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
// TypeScript type checking
|
||||||
|
useTypeScript &&
|
||||||
|
new ForkTsCheckerWebpackPlugin({
|
||||||
|
typescript: resolve.sync('typescript', {
|
||||||
|
basedir: paths.appNodeModules,
|
||||||
|
}),
|
||||||
|
async: isEnvDevelopment,
|
||||||
|
useTypescriptIncrementalApi: true,
|
||||||
|
checkSyntacticErrors: true,
|
||||||
|
resolveModuleNameModule: process.versions.pnp
|
||||||
|
? `${__dirname}/pnpTs.js`
|
||||||
|
: undefined,
|
||||||
|
resolveTypeReferenceDirectiveModule: process.versions.pnp
|
||||||
|
? `${__dirname}/pnpTs.js`
|
||||||
|
: undefined,
|
||||||
|
tsconfig: paths.appTsConfig,
|
||||||
|
reportFiles: [
|
||||||
|
'**',
|
||||||
|
'!**/__tests__/**',
|
||||||
|
'!**/?(*.)(spec|test).*',
|
||||||
|
'!**/src/setupProxy.*',
|
||||||
|
'!**/src/setupTests.*',
|
||||||
|
],
|
||||||
|
silent: true,
|
||||||
|
// The formatter is invoked directly in WebpackDevServerUtils during development
|
||||||
|
formatter: isEnvProduction ? typescriptFormatter : undefined,
|
||||||
|
}),
|
||||||
|
].filter(Boolean),
|
||||||
|
// 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: {
|
||||||
|
module: 'empty',
|
||||||
|
dgram: 'empty',
|
||||||
|
dns: 'mock',
|
||||||
|
fs: 'empty',
|
||||||
|
http2: 'empty',
|
||||||
|
net: 'empty',
|
||||||
|
tls: 'empty',
|
||||||
|
child_process: 'empty',
|
||||||
|
},
|
||||||
|
// Turn off performance processing because we utilize
|
||||||
|
// our own hints via the FileSizeReporter
|
||||||
|
performance: false,
|
||||||
|
};
|
||||||
|
};
|
||||||
104
config/webpackDevServer.config.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
'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 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="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: '/',
|
||||||
|
// 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());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
214
package.json
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
{
|
||||||
|
"name": "react-admin",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"private": true,
|
||||||
|
"homepage": ".",
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^4.2.2",
|
||||||
|
"@babel/core": "7.6.0",
|
||||||
|
"@svgr/webpack": "4.3.2",
|
||||||
|
"@types/classnames": "^2.2.9",
|
||||||
|
"@types/echarts": "^4.1.15",
|
||||||
|
"@types/jest": "24.0.18",
|
||||||
|
"@types/node": "12.7.11",
|
||||||
|
"@types/nprogress": "^0.2.0",
|
||||||
|
"@types/photoswipe": "^4.0.29",
|
||||||
|
"@types/query-string": "^6.3.0",
|
||||||
|
"@types/react": "^16.9.49",
|
||||||
|
"@types/react-beautiful-dnd": "^12.1.2",
|
||||||
|
"@types/react-color": "^3.0.1",
|
||||||
|
"@types/react-document-title": "^2.0.3",
|
||||||
|
"@types/react-dom": "^16.9.8",
|
||||||
|
"@types/react-draft-wysiwyg": "^1.12.3",
|
||||||
|
"@types/react-loadable": "^5.5.1",
|
||||||
|
"@types/react-router-dom": "^5.1.0",
|
||||||
|
"@types/recharts": "^1.1.21",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^3.9.1",
|
||||||
|
"@typescript-eslint/parser": "^3.9.1",
|
||||||
|
"ahooks": "^2.6.0",
|
||||||
|
"antd": "^4.6.4",
|
||||||
|
"axios": "^0.19.0",
|
||||||
|
"babel-eslint": "10.0.3",
|
||||||
|
"babel-jest": "^24.9.0",
|
||||||
|
"babel-loader": "8.0.6",
|
||||||
|
"babel-plugin-named-asset-import": "^0.3.4",
|
||||||
|
"babel-preset-react-app": "^9.0.2",
|
||||||
|
"camelcase": "^5.2.0",
|
||||||
|
"case-sensitive-paths-webpack-plugin": "2.2.0",
|
||||||
|
"css-loader": "2.1.1",
|
||||||
|
"dotenv": "6.2.0",
|
||||||
|
"dotenv-expand": "5.1.0",
|
||||||
|
"draft-js": "^0.11.7",
|
||||||
|
"draftjs-to-html": "^0.8.4",
|
||||||
|
"draftjs-to-markdown": "^0.5.1",
|
||||||
|
"echarts": "^4.3.0",
|
||||||
|
"echarts-for-react": "^2.0.15-beta.1",
|
||||||
|
"eslint": "^6.1.0",
|
||||||
|
"eslint-config-react-app": "^5.0.2",
|
||||||
|
"eslint-loader": "3.0.2",
|
||||||
|
"eslint-plugin-flowtype": "3.13.0",
|
||||||
|
"eslint-plugin-import": "2.18.2",
|
||||||
|
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||||
|
"eslint-plugin-react": "7.14.3",
|
||||||
|
"eslint-plugin-react-hooks": "^1.6.1",
|
||||||
|
"file-loader": "3.0.1",
|
||||||
|
"fs-extra": "7.0.1",
|
||||||
|
"html-webpack-plugin": "4.0.0-beta.5",
|
||||||
|
"identity-obj-proxy": "3.0.0",
|
||||||
|
"is-wsl": "^1.1.0",
|
||||||
|
"jest": "24.9.0",
|
||||||
|
"jest-environment-jsdom-fourteen": "0.1.0",
|
||||||
|
"jest-resolve": "24.9.0",
|
||||||
|
"jest-watch-typeahead": "0.4.0",
|
||||||
|
"mini-css-extract-plugin": "0.8.0",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||||
|
"photoswipe": "^4.1.3",
|
||||||
|
"pnp-webpack-plugin": "1.5.0",
|
||||||
|
"postcss-flexbugs-fixes": "4.1.0",
|
||||||
|
"postcss-loader": "3.0.0",
|
||||||
|
"postcss-normalize": "7.0.1",
|
||||||
|
"postcss-preset-env": "6.7.0",
|
||||||
|
"postcss-safe-parser": "4.0.1",
|
||||||
|
"rc-banner-anim": "^2.4.2",
|
||||||
|
"react": "^16.10.2",
|
||||||
|
"react-app-polyfill": "^1.0.4",
|
||||||
|
"react-beautiful-dnd": "^13.0.0",
|
||||||
|
"react-color": "^2.17.3",
|
||||||
|
"react-dev-utils": "^9.1.0",
|
||||||
|
"react-document-title": "^2.0.3",
|
||||||
|
"react-dom": "^16.10.2",
|
||||||
|
"react-draft-wysiwyg": "^1.13.2",
|
||||||
|
"react-draggable": "^4.0.3",
|
||||||
|
"react-loadable": "^5.5.0",
|
||||||
|
"react-qmap": "^0.1.6",
|
||||||
|
"react-router-dom": "^5.1.2",
|
||||||
|
"recharts": "^1.7.1",
|
||||||
|
"redux-alita": "^2.3.3",
|
||||||
|
"resolve": "1.12.0",
|
||||||
|
"resolve-url-loader": "3.1.0",
|
||||||
|
"sass-loader": "7.2.0",
|
||||||
|
"screenfull": "^5.0.0",
|
||||||
|
"semver": "6.3.0",
|
||||||
|
"style-loader": "1.0.0",
|
||||||
|
"terser-webpack-plugin": "1.4.1",
|
||||||
|
"ts-pnp": "1.1.4",
|
||||||
|
"typescript": "3.9.7",
|
||||||
|
"umbrella-storage": "^1.0.2",
|
||||||
|
"url-loader": "2.1.0",
|
||||||
|
"webpack": "4.41.0",
|
||||||
|
"webpack-dev-server": "3.2.1",
|
||||||
|
"webpack-manifest-plugin": "2.1.1",
|
||||||
|
"workbox-webpack-plugin": "4.3.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node --openssl-legacy-provider scripts/start.js dev",
|
||||||
|
"build": "node scripts/build.js production",
|
||||||
|
"starandsea": "node scripts/build.js starandsea",
|
||||||
|
"test": "node scripts/test.js",
|
||||||
|
"commit": "git-cz",
|
||||||
|
"release": "standard-version"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"commitizen": {
|
||||||
|
"path": "node_modules/cz-conventional-changelog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"commit-msg": "commitlint -e $GIT_PARAMS",
|
||||||
|
"pre-commit": "pretty-quick --staged"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "react-app"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"roots": [
|
||||||
|
"<rootDir>/src"
|
||||||
|
],
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"src/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"!src/**/*.d.ts"
|
||||||
|
],
|
||||||
|
"setupFiles": [
|
||||||
|
"react-app-polyfill/jsdom"
|
||||||
|
],
|
||||||
|
"setupFilesAfterEnv": [],
|
||||||
|
"testMatch": [
|
||||||
|
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
|
||||||
|
],
|
||||||
|
"testEnvironment": "jest-environment-jsdom-fourteen",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
|
||||||
|
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
|
||||||
|
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
|
||||||
|
},
|
||||||
|
"transformIgnorePatterns": [
|
||||||
|
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
|
||||||
|
"^.+\\.module\\.(css|sass|scss)$"
|
||||||
|
],
|
||||||
|
"modulePaths": [],
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^react-native$": "react-native-web",
|
||||||
|
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
|
||||||
|
},
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"web.js",
|
||||||
|
"js",
|
||||||
|
"web.ts",
|
||||||
|
"ts",
|
||||||
|
"web.tsx",
|
||||||
|
"tsx",
|
||||||
|
"json",
|
||||||
|
"web.jsx",
|
||||||
|
"jsx",
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"watchPlugins": [
|
||||||
|
"jest-watch-typeahead/filename",
|
||||||
|
"jest-watch-typeahead/testname"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"babel": {
|
||||||
|
"presets": [
|
||||||
|
"react-app"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"import",
|
||||||
|
{
|
||||||
|
"libraryName": "antd",
|
||||||
|
"style": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^8.2.0",
|
||||||
|
"@commitlint/config-conventional": "^8.2.0",
|
||||||
|
"antd-theme-generator": "^1.1.7",
|
||||||
|
"babel-plugin-import": "^1.12.2",
|
||||||
|
"commitizen": "^4.0.3",
|
||||||
|
"cz-conventional-changelog": "^3.0.2",
|
||||||
|
"husky": "^3.0.5",
|
||||||
|
"less": "2.x",
|
||||||
|
"less-loader": "^5.0.0",
|
||||||
|
"prettier": "^2.0.4",
|
||||||
|
"pretty-quick": "^2.0.1",
|
||||||
|
"standard-version": "^7.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
8820
public/assets/less.min.js
vendored
Normal file
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
public/images/icons/icon-128x128.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/images/icons/icon-144x144.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/icons/icon-152x152.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/images/icons/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/images/icons/icon-384x384.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/images/icons/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
public/images/icons/icon-72x72.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/images/icons/icon-96x96.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
49
public/index.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!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="%PUBLIC_URL%/assets/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
Normal file
|
After Width: | Height: | Size: 67 KiB |
51
public/manifest.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
8817
public/theme.less
Normal file
BIN
screenshots/logo.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
screenshots/menu_draggable.gif
Normal file
|
After Width: | Height: | Size: 532 KiB |
BIN
screenshots/pwa.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
screenshots/themepicker.png
Normal file
|
After Width: | Height: | Size: 272 KiB |
193
scripts/build.js
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
'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';
|
||||||
|
process.env.REACT_ADMIN = process.argv.slice(2)[0];
|
||||||
|
|
||||||
|
// 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('react-dev-utils/chalk');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const configFactory = require('../config/webpack.config');
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate configuration
|
||||||
|
const config = configFactory('production');
|
||||||
|
|
||||||
|
// We require that you explicitly 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) => {
|
||||||
|
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
|
||||||
|
if (tscCompileOnError) {
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(
|
||||||
|
'Compiled with the following type errors (you may want to check these before deploying your app):\n'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
printBuildError(err);
|
||||||
|
} else {
|
||||||
|
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) {
|
||||||
|
// We used to support resolving modules according to `NODE_PATH`.
|
||||||
|
// This now has been deprecated in favor of jsconfig/tsconfig.json
|
||||||
|
// This lets you use absolute paths in imports inside large monorepos:
|
||||||
|
if (process.env.NODE_PATH) {
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(
|
||||||
|
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Creating an optimized production build...');
|
||||||
|
|
||||||
|
const 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')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve({
|
||||||
|
stats,
|
||||||
|
previousFileSizes,
|
||||||
|
warnings: messages.warnings,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyPublicFolder() {
|
||||||
|
fs.copySync(paths.appPublic, paths.appBuild, {
|
||||||
|
dereference: true,
|
||||||
|
filter: (file) => file !== paths.appHtml,
|
||||||
|
});
|
||||||
|
}
|
||||||
140
scripts/start.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
'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';
|
||||||
|
process.env.REACT_ADMIN = process.argv.slice(2)[0];
|
||||||
|
|
||||||
|
// 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('react-dev-utils/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 configFactory = require('../config/webpack.config');
|
||||||
|
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('https://bit.ly/CRA-advanced-config')}`);
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We require that you explicitly 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 config = configFactory('development');
|
||||||
|
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||||
|
const appName = require(paths.appPackageJson).name;
|
||||||
|
const useTypeScript = fs.existsSync(paths.appTsConfig);
|
||||||
|
const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
|
||||||
|
const urls = prepareUrls(protocol, HOST, port);
|
||||||
|
const devSocket = {
|
||||||
|
warnings: (warnings) => devServer.sockWrite(devServer.sockets, 'warnings', warnings),
|
||||||
|
errors: (errors) => devServer.sockWrite(devServer.sockets, 'errors', errors),
|
||||||
|
};
|
||||||
|
// Create a webpack compiler that is configured with custom messages.
|
||||||
|
const compiler = createCompiler({
|
||||||
|
appName,
|
||||||
|
config,
|
||||||
|
devSocket,
|
||||||
|
urls,
|
||||||
|
useYarn,
|
||||||
|
useTypeScript,
|
||||||
|
tscCompileOnError,
|
||||||
|
webpack,
|
||||||
|
});
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We used to support resolving modules according to `NODE_PATH`.
|
||||||
|
// This now has been deprecated in favor of jsconfig/tsconfig.json
|
||||||
|
// This lets you use absolute paths in imports inside large monorepos:
|
||||||
|
if (process.env.NODE_PATH) {
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(
|
||||||
|
'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
scripts/test.js
Normal file
@@ -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 or explicitly running all tests
|
||||||
|
if (
|
||||||
|
!process.env.CI &&
|
||||||
|
argv.indexOf('--watchAll') === -1 &&
|
||||||
|
argv.indexOf('--watchAll=false') === -1
|
||||||
|
) {
|
||||||
|
// https://github.com/facebook/create-react-app/issues/5210
|
||||||
|
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
|
||||||
|
argv.push(hasSourceControl ? '--watch' : '--watchAll');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
jest.run(argv);
|
||||||
9
src/App.test.tsx
Normal file
@@ -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);
|
||||||
|
});
|
||||||
136
src/App.tsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Layout, notification } from 'antd';
|
||||||
|
import umbrella from 'umbrella-storage';
|
||||||
|
import { useAlita } from 'redux-alita';
|
||||||
|
import Routes from './routes';
|
||||||
|
import SiderCustom from './components/SiderCustom';
|
||||||
|
import HeaderCustom from './components/HeaderCustom';
|
||||||
|
import { ThemePicker, Copyright } from './components/widget';
|
||||||
|
import { checkLogin } from './utils';
|
||||||
|
import { fetchMenu } from './service';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { SmileOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const { Content, Footer } = Layout;
|
||||||
|
|
||||||
|
type AppProps = {};
|
||||||
|
|
||||||
|
function checkIsMobile() {
|
||||||
|
const clientWidth = window.innerWidth;
|
||||||
|
return clientWidth <= 992;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _resizeThrottled = false;
|
||||||
|
function resizeListener(handler: (isMobile: boolean) => void) {
|
||||||
|
const delay = 250;
|
||||||
|
if (!_resizeThrottled) {
|
||||||
|
_resizeThrottled = true;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
handler(checkIsMobile());
|
||||||
|
_resizeThrottled = false;
|
||||||
|
clearTimeout(timer);
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleResize(handler: (isMobile: boolean) => void) {
|
||||||
|
window.addEventListener('resize', resizeListener.bind(null, handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFNotification() {
|
||||||
|
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: <SmileOutlined style={{ color: 'red' }} />,
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
umbrella.setLocalStorage('hideBlog', true);
|
||||||
|
};
|
||||||
|
const storageFirst = umbrella.getLocalStorage('hideBlog');
|
||||||
|
if (!storageFirst) {
|
||||||
|
openNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务端异步菜单
|
||||||
|
* @param handler 执行回调
|
||||||
|
*/
|
||||||
|
function fetchSmenu(handler: any) {
|
||||||
|
const setAlitaMenu = (menus: any) => {
|
||||||
|
handler(menus);
|
||||||
|
// this.props.setAlitaState({ stateName: 'smenus', data: menus });
|
||||||
|
};
|
||||||
|
setAlitaMenu(umbrella.getLocalStorage('smenus') || []);
|
||||||
|
fetchMenu().then((smenus) => {
|
||||||
|
setAlitaMenu(smenus);
|
||||||
|
umbrella.setLocalStorage('smenus', smenus);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = (props: AppProps) => {
|
||||||
|
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||||
|
const [auth, responsive, setAlita] = useAlita(
|
||||||
|
{ auth: { permissions: null } },
|
||||||
|
{ responsive: { isMobile: false } },
|
||||||
|
{ light: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let user = umbrella.getLocalStorage('user');
|
||||||
|
user && setAlita('auth', user);
|
||||||
|
setAlita('responsive', { isMobile: checkIsMobile() });
|
||||||
|
|
||||||
|
handleResize((isMobile: boolean) => setAlita('responsive', { isMobile }));
|
||||||
|
openFNotification();
|
||||||
|
fetchSmenu((smenus: any[]) => setAlita('smenus', smenus));
|
||||||
|
}, [setAlita]);
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
{!responsive.isMobile && checkLogin(auth.permissions) && (
|
||||||
|
<SiderCustom collapsed={collapsed} />
|
||||||
|
)}
|
||||||
|
<ThemePicker />
|
||||||
|
<Layout
|
||||||
|
className={classNames('app_layout', { 'app_layout-mobile': responsive.isMobile })}
|
||||||
|
>
|
||||||
|
<HeaderCustom toggle={toggle} collapsed={collapsed} user={auth || {}} />
|
||||||
|
<Content className="app_layout_content">
|
||||||
|
<Routes auth={auth} />
|
||||||
|
</Content>
|
||||||
|
<Footer className="app_layout_foot">
|
||||||
|
<Copyright />
|
||||||
|
</Footer>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
17
src/Page.tsx
Normal file
@@ -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>
|
||||||
|
);
|
||||||
137
src/components/HeaderCustom.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/13.
|
||||||
|
*/
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import screenfull from 'screenfull';
|
||||||
|
import avater from '../style/imgs/b1.jpg';
|
||||||
|
import SiderCustom from './SiderCustom';
|
||||||
|
import { Menu, Layout, Badge, Popover } from 'antd';
|
||||||
|
import { gitOauthToken, gitOauthInfo } from '../service';
|
||||||
|
import { parseQuery } from '../utils';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { PwaInstaller } from './widget';
|
||||||
|
import { useAlita } from 'redux-alita';
|
||||||
|
import umbrella from 'umbrella-storage';
|
||||||
|
import { useSwitch } from '../utils/hooks';
|
||||||
|
import {
|
||||||
|
ArrowsAltOutlined,
|
||||||
|
BarsOutlined,
|
||||||
|
MenuFoldOutlined,
|
||||||
|
MenuUnfoldOutlined,
|
||||||
|
NotificationOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
const { Header } = Layout;
|
||||||
|
const SubMenu = Menu.SubMenu;
|
||||||
|
const MenuItemGroup = Menu.ItemGroup;
|
||||||
|
|
||||||
|
type HeaderCustomProps = {
|
||||||
|
toggle: () => void;
|
||||||
|
collapsed: boolean;
|
||||||
|
user: any;
|
||||||
|
responsive?: any;
|
||||||
|
path?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderCustom = (props: HeaderCustomProps) => {
|
||||||
|
const [user, setUser] = useState<any>();
|
||||||
|
const [responsive] = useAlita('responsive', { light: true });
|
||||||
|
const [visible, turn] = useSwitch();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const query = parseQuery();
|
||||||
|
let storageUser = umbrella.getLocalStorage('user');
|
||||||
|
|
||||||
|
if (!storageUser && query.code) {
|
||||||
|
gitOauthToken(query.code as string).then((res: any) => {
|
||||||
|
gitOauthInfo(res.access_token).then((info: any) => {
|
||||||
|
setUser({
|
||||||
|
user: info,
|
||||||
|
});
|
||||||
|
umbrella.setLocalStorage('user', info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setUser({
|
||||||
|
user: storageUser,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const screenFull = () => {
|
||||||
|
if (screenfull.isEnabled) {
|
||||||
|
screenfull.toggle();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const menuClick = (e: any) => {
|
||||||
|
e.key === 'logout' && logout();
|
||||||
|
};
|
||||||
|
const logout = () => {
|
||||||
|
umbrella.removeLocalStorage('user');
|
||||||
|
history.push('/login');
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Header className="custom-theme header">
|
||||||
|
{responsive?.isMobile ? (
|
||||||
|
<Popover
|
||||||
|
content={<SiderCustom popoverHide={turn.turnOff} />}
|
||||||
|
trigger="click"
|
||||||
|
placement="bottomLeft"
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => (visible ? turn.turnOn() : turn.turnOff())}
|
||||||
|
>
|
||||||
|
<BarsOutlined className="header__trigger custom-trigger" />
|
||||||
|
</Popover>
|
||||||
|
) : props.collapsed ? (
|
||||||
|
<MenuUnfoldOutlined
|
||||||
|
className="header__trigger custom-trigger"
|
||||||
|
onClick={props.toggle}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MenuFoldOutlined
|
||||||
|
className="header__trigger custom-trigger"
|
||||||
|
onClick={props.toggle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Menu
|
||||||
|
mode="horizontal"
|
||||||
|
style={{ lineHeight: '64px', float: 'right' }}
|
||||||
|
onClick={menuClick}
|
||||||
|
>
|
||||||
|
<Menu.Item key="pwa">
|
||||||
|
<PwaInstaller />
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="full">
|
||||||
|
<ArrowsAltOutlined onClick={screenFull} />
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="1">
|
||||||
|
<Badge count={25} overflowCount={10} style={{ marginLeft: 10 }}>
|
||||||
|
<NotificationOutlined />
|
||||||
|
</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">你好 - {user?.userName}</Menu.Item>
|
||||||
|
<Menu.Item key="setting:2">个人信息</Menu.Item>
|
||||||
|
<Menu.Item key="logout">
|
||||||
|
<span onClick={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 HeaderCustom;
|
||||||
14
src/components/Page.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/16.
|
||||||
|
*/
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = (props: PageProps) => {
|
||||||
|
return <div style={{ height: '100%' }}>{props.children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
103
src/components/SiderCustom.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/13.
|
||||||
|
*/
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||||
|
import routes from '../routes/config';
|
||||||
|
import SiderMenu from './SiderMenu';
|
||||||
|
import { useAlita } from 'redux-alita';
|
||||||
|
import { useSwitch } from '../utils/hooks';
|
||||||
|
import { usePrevious } from 'ahooks';
|
||||||
|
const { Sider } = Layout;
|
||||||
|
|
||||||
|
type SiderCustomProps = RouteComponentProps<any> & {
|
||||||
|
popoverHide?: () => void;
|
||||||
|
collapsed?: boolean;
|
||||||
|
smenus?: any;
|
||||||
|
};
|
||||||
|
interface IMenu {
|
||||||
|
openKeys: string[];
|
||||||
|
selectedKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SiderCustom = (props: SiderCustomProps) => {
|
||||||
|
const [collapsed, tCollapsed] = useSwitch();
|
||||||
|
const [firstHide, tFirstHide] = useSwitch();
|
||||||
|
const [menu, setMenu] = useState<IMenu>({ openKeys: [''], selectedKey: '' });
|
||||||
|
// 异步菜单
|
||||||
|
const [smenus] = useAlita({ smenus: [] }, { light: true });
|
||||||
|
const { location, collapsed: pCollapsed } = props;
|
||||||
|
const prePathname = usePrevious(props.location.pathname);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const recombineOpenKeys = (openKeys: string[]) => {
|
||||||
|
let i = 0;
|
||||||
|
let strPlus = '';
|
||||||
|
let tempKeys: string[] = [];
|
||||||
|
// 多级菜单循环处理
|
||||||
|
while (i < openKeys.length) {
|
||||||
|
strPlus += openKeys[i];
|
||||||
|
tempKeys.push(strPlus);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return tempKeys;
|
||||||
|
};
|
||||||
|
const getOpenAndSelectKeys = () => {
|
||||||
|
return {
|
||||||
|
openKeys: recombineOpenKeys(location.pathname.match(/[/](\w+)/gi) || []),
|
||||||
|
selectedKey: location.pathname,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pCollapsed !== collapsed) {
|
||||||
|
setMenu(getOpenAndSelectKeys());
|
||||||
|
tCollapsed.setSwitcher(!!pCollapsed);
|
||||||
|
tFirstHide.setSwitcher(!!pCollapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prePathname !== location.pathname) {
|
||||||
|
setMenu(getOpenAndSelectKeys());
|
||||||
|
}
|
||||||
|
}, [prePathname, location.pathname, collapsed, tFirstHide, tCollapsed, pCollapsed]);
|
||||||
|
|
||||||
|
const menuClick = (e: any) => {
|
||||||
|
setMenu((state) => ({ ...state, selectedKey: e.key }));
|
||||||
|
props.popoverHide?.(); // 响应式布局控制小屏幕点击菜单时隐藏菜单操作
|
||||||
|
};
|
||||||
|
|
||||||
|
const openMenu: any = (v: string[]) => {
|
||||||
|
setMenu((state) => ({ ...state, openKeys: v }));
|
||||||
|
tFirstHide.turnOff();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sider
|
||||||
|
trigger={null}
|
||||||
|
breakpoint="lg"
|
||||||
|
collapsed={collapsed}
|
||||||
|
style={{ overflowY: 'auto' }}
|
||||||
|
className="sider-custom"
|
||||||
|
>
|
||||||
|
<div className="logo" />
|
||||||
|
<SiderMenu
|
||||||
|
menus={[...routes.menus, ...smenus]}
|
||||||
|
onClick={menuClick}
|
||||||
|
mode="inline"
|
||||||
|
selectedKeys={[menu.selectedKey]}
|
||||||
|
openKeys={firstHide ? [] : menu.openKeys}
|
||||||
|
onOpenChange={openMenu}
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
{`
|
||||||
|
#nprogress .spinner{
|
||||||
|
left: ${collapsed ? '70px' : '206px'};
|
||||||
|
right: 0 !important;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
</Sider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRouter(SiderCustom);
|
||||||
99
src/components/SiderMenu.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Menu } from 'antd';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||||
|
import { IFMenu } from '../routes/config';
|
||||||
|
import { MenuProps } from 'antd/lib/menu';
|
||||||
|
|
||||||
|
const renderMenuItem = (
|
||||||
|
item: IFMenu // 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: IFMenu) => {
|
||||||
|
return (
|
||||||
|
<Menu.SubMenu
|
||||||
|
key={item.key}
|
||||||
|
title={
|
||||||
|
<span>
|
||||||
|
{/* {item.icon && <Icon type={item.icon} />} */}
|
||||||
|
<span className="nav-text">{item.title}</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.subs!.map((sub) => (sub.subs ? renderSubMenu(sub) : renderMenuItem(sub)))}
|
||||||
|
</Menu.SubMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type SiderMenuProps = MenuProps & {
|
||||||
|
menus: any;
|
||||||
|
onClick: (e: any) => void;
|
||||||
|
selectedKeys: string[];
|
||||||
|
openKeys?: string[];
|
||||||
|
onOpenChange: (v: string[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SiderMenu = ({ menus, ...props }: SiderMenuProps) => {
|
||||||
|
const [dragItems, setDragItems] = useState<any>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDragItems(menus);
|
||||||
|
}, [menus]);
|
||||||
|
|
||||||
|
const reorder = (list: any, startIndex: number, endIndex: number) => {
|
||||||
|
const result = Array.from(list);
|
||||||
|
const [removed] = result.splice(startIndex, 1);
|
||||||
|
result.splice(endIndex, 0, removed);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const onDragEnd = (result: any) => {
|
||||||
|
// 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: IFMenu, index: number) => (
|
||||||
|
<Draggable key={item.key} draggableId={item.key} index={index}>
|
||||||
|
{(provided, snapshot) => (
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
onDragStart={(e: React.DragEvent<any>) =>
|
||||||
|
provided.dragHandleProps &&
|
||||||
|
provided.dragHandleProps.onDragStart(e as any)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Menu {...props}>
|
||||||
|
{item.subs!
|
||||||
|
? renderSubMenu(item)
|
||||||
|
: renderMenuItem(item)}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(SiderMenu);
|
||||||
137
src/components/animation/BasicAnimations.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/5/8.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Row, Col, Card, Switch } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
|
||||||
|
class BasicAnimations extends React.Component {
|
||||||
|
state = {
|
||||||
|
animated: false,
|
||||||
|
animatedOne: -1,
|
||||||
|
};
|
||||||
|
animatedAll = (checked: boolean) => {
|
||||||
|
checked && this.setState({ animated: true });
|
||||||
|
!checked && this.setState({ animated: false });
|
||||||
|
};
|
||||||
|
animatedOne = (i: number) => {
|
||||||
|
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 breads={['动画', '基础动画']} />
|
||||||
|
<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;
|
||||||
120
src/components/animation/ExampleAnimations.tsx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/5/8.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Row, Col, Card, Table, Popconfirm, Button } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
|
||||||
|
type ExampleAnimationsProps = {};
|
||||||
|
type ExampleAnimationsState = {
|
||||||
|
dataSource: any;
|
||||||
|
count: number;
|
||||||
|
deleteIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExampleAnimations extends React.Component<ExampleAnimationsProps, ExampleAnimationsState> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.columns = [
|
||||||
|
{
|
||||||
|
title: 'name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: '30%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'age',
|
||||||
|
dataIndex: 'age',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'address',
|
||||||
|
dataIndex: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'operation',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
render: (text: any, record: any, index: number) => {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
columns: any;
|
||||||
|
onDelete = (record: any, index: number) => {
|
||||||
|
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 breads={['动画', '动画案例']} />
|
||||||
|
<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: any, index: number) => {
|
||||||
|
if (this.state.deleteIndex === record.key)
|
||||||
|
return 'animated zoomOutLeft min-black';
|
||||||
|
return 'animated fadeInRight';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExampleAnimations;
|
||||||
62
src/components/auth/Basic.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Created by 叶子 on 2017/7/31.
|
||||||
|
*/
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
import beauty from '@/style/imgs/beauty.jpg';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { AuthWidget } from '../widget';
|
||||||
|
|
||||||
|
class Basic extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadcrumbCustom breads={['权限管理', '基础演示']} />
|
||||||
|
<AuthWidget
|
||||||
|
children={(auth: any) => (
|
||||||
|
<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
src/components/auth/RouterEnter.tsx
Normal file
@@ -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 '../widget/BreadcrumbCustom';
|
||||||
|
import { AuthWidget } from '../widget';
|
||||||
|
|
||||||
|
class RouterEnter extends Component {
|
||||||
|
componentDidMount() {
|
||||||
|
console.log('RouterEnter');
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadcrumbCustom breads={['权限管理', '路由拦截']} />
|
||||||
|
<AuthWidget
|
||||||
|
children={(auth: any) => (
|
||||||
|
<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
src/components/charts/Echarts.tsx
Normal file
@@ -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;
|
||||||
113
src/components/charts/EchartsArea.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* 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: any) {
|
||||||
|
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
src/components/charts/EchartsEffectScatter.tsx
Normal file
@@ -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: any) {
|
||||||
|
let res = [];
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
let geoCoord = (geoCoordMap as any)[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: any) {
|
||||||
|
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: any) {
|
||||||
|
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
src/components/charts/EchartsForce.tsx
Normal file
@@ -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
src/components/charts/EchartsGraphnpm.tsx
Normal file
@@ -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 '../../service';
|
||||||
|
|
||||||
|
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: any) {
|
||||||
|
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: any) {
|
||||||
|
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;
|
||||||
88
src/components/charts/EchartsPie.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 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: any) {
|
||||||
|
return Math.random() * 200;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const EchartsPie = () => (
|
||||||
|
<ReactEcharts
|
||||||
|
option={option}
|
||||||
|
style={{ height: '300px', width: '100%' }}
|
||||||
|
className={'react_for_echarts'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default EchartsPie;
|
||||||
138
src/components/charts/EchartsScatter.tsx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/21.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import ReactEcharts from 'echarts-for-react';
|
||||||
|
import { weibo } from '../../service';
|
||||||
|
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: any) {
|
||||||
|
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([parseInt(x.toFixed(2), 10), parseInt(y.toFixed(2), 10), 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
src/components/charts/Recharts.tsx
Normal file
@@ -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
src/components/charts/RechartsBarChart.tsx
Normal file
@@ -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
src/components/charts/RechartsRadarChart.tsx
Normal file
@@ -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;
|
||||||
42
src/components/charts/RechartsRadialBarChart.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/22.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { RadialBarChart, 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
src/components/charts/RechartsSimpleLineChart.tsx
Normal file
@@ -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;
|
||||||
32
src/components/cssmodule/index.module.less
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/components/cssmodule/index.tsx
Normal file
@@ -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 '../widget/BreadcrumbCustom';
|
||||||
|
import styles from './index.module.less';
|
||||||
|
|
||||||
|
class Cssmodule extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadcrumbCustom breads={['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;
|
||||||
204
src/components/dashboard/Dashboard.tsx
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/5/3.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Row, Col, Card, Timeline } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import EchartsViews from './EchartsViews';
|
||||||
|
import EchartsProjects from './EchartsProjects';
|
||||||
|
import b1 from '../../style/imgs/b1.jpg';
|
||||||
|
import {
|
||||||
|
CameraOutlined,
|
||||||
|
CloudOutlined,
|
||||||
|
HeartOutlined,
|
||||||
|
MailOutlined,
|
||||||
|
SyncOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
|
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">
|
||||||
|
<HeartOutlined 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">
|
||||||
|
<CloudOutlined 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">
|
||||||
|
<CameraOutlined 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">
|
||||||
|
<MailOutlined 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">
|
||||||
|
<SyncOutlined />
|
||||||
|
</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">
|
||||||
|
<SyncOutlined />
|
||||||
|
</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">
|
||||||
|
<SyncOutlined type="sync" />
|
||||||
|
</span>
|
||||||
|
<EchartsViews />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
116
src/components/dashboard/EchartsProjects.tsx
Normal file
@@ -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: number) {
|
||||||
|
return idx * 20;
|
||||||
|
},
|
||||||
|
animationDelayUpdate: function (idx: number) {
|
||||||
|
return idx * 20;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const EchartsProjects = () => (
|
||||||
|
<ReactEcharts
|
||||||
|
option={option}
|
||||||
|
style={{height: '212px', width: '100%'}}
|
||||||
|
className={'react_for_echarts'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default EchartsProjects;
|
||||||
121
src/components/dashboard/EchartsViews.tsx
Normal file
@@ -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;
|
||||||
37
src/components/env/index.tsx
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* File: index.tsx
|
||||||
|
* Desc: 环境配置
|
||||||
|
* File Created: 2020-08-02 23:00:28
|
||||||
|
* Author: yezi
|
||||||
|
* ------
|
||||||
|
* Copyright 2020 - present, yezi
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { Row, Col, Card, Descriptions } from 'antd';
|
||||||
|
|
||||||
|
const getEnvs = () => Object.keys(process.env).filter((key) => /^REACT_ADMIN_/i.test(key));
|
||||||
|
const Env = () => {
|
||||||
|
const envs = getEnvs();
|
||||||
|
console.log(process.env);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadcrumbCustom breads={['环境变量配置']} />
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col md={24}>
|
||||||
|
<Card title="环境变量配置" bordered={false}>
|
||||||
|
<Descriptions>
|
||||||
|
{envs.map((env) => (
|
||||||
|
<Descriptions.Item key={env} label={env}>
|
||||||
|
{process.env[env]}
|
||||||
|
</Descriptions.Item>
|
||||||
|
))}
|
||||||
|
</Descriptions>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Env;
|
||||||
29
src/components/extension/MultipleMenu.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* File: MultipleMenu.tsx
|
||||||
|
* Desc: 多级菜单组件
|
||||||
|
* File Created: 2019-12-18 23:15:35
|
||||||
|
* Author: chenghao
|
||||||
|
* ------
|
||||||
|
* Copyright 2019 - present, karakal
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
|
||||||
|
const MultipleMenu = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadcrumbCustom breads={['多级菜单']} />
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col md={24}>
|
||||||
|
<Card title="多级菜单" bordered={false}>
|
||||||
|
<div>多级菜单的功能扩展</div>
|
||||||
|
<div>菜单样式可能需要你来调整</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MultipleMenu;
|
||||||
43
src/components/extension/QueryParams.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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 '../widget/BreadcrumbCustom';
|
||||||
|
|
||||||
|
type QueryParamsProps = {
|
||||||
|
query: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QueryParams extends Component<QueryParamsProps> {
|
||||||
|
render() {
|
||||||
|
const { query } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadcrumbCustom breads={['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;
|
||||||
37
src/components/extension/Visitor.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* File: Visitor.tsx
|
||||||
|
* Desc: 访客
|
||||||
|
* File Created: 2019-10-25 22:31:37
|
||||||
|
* Author: chenghao
|
||||||
|
* ------
|
||||||
|
* Copyright 2019 - present, chenghao
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
|
||||||
|
const Visitor = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbCustom breads={['visitor']} />
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col md={24}>
|
||||||
|
<Card
|
||||||
|
title="访客模式"
|
||||||
|
bordered={false}
|
||||||
|
bodyStyle={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: 500,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
访客模式的页面,你不需要登录即可访问的页面
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Visitor;
|
||||||
70
src/components/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* 路由组件出口文件
|
||||||
|
* yezi 2018年6月24日
|
||||||
|
*/
|
||||||
|
import Loadable from 'react-loadable';
|
||||||
|
import Loading from './widget/Loading';
|
||||||
|
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';
|
||||||
|
import Visitor from './extension/Visitor';
|
||||||
|
import MultipleMenu from './extension/MultipleMenu';
|
||||||
|
import Sub1 from './smenu/Sub1';
|
||||||
|
import Sub2 from './smenu/Sub2';
|
||||||
|
import Env from './env';
|
||||||
|
|
||||||
|
const WysiwygBundle = Loadable({
|
||||||
|
// 按需加载富文本配置
|
||||||
|
loader: () => import('./ui/Wysiwyg'),
|
||||||
|
loading: Loading,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
BasicTable,
|
||||||
|
AdvancedTable,
|
||||||
|
AsynchronousTable,
|
||||||
|
Echarts,
|
||||||
|
Recharts,
|
||||||
|
Icons,
|
||||||
|
Buttons,
|
||||||
|
Spins,
|
||||||
|
Modals,
|
||||||
|
Notifications,
|
||||||
|
Tabs,
|
||||||
|
Banners,
|
||||||
|
Drags,
|
||||||
|
Dashboard,
|
||||||
|
Gallery,
|
||||||
|
BasicAnimations,
|
||||||
|
ExampleAnimations,
|
||||||
|
AuthBasic,
|
||||||
|
RouterEnter,
|
||||||
|
WysiwygBundle,
|
||||||
|
Cssmodule,
|
||||||
|
MapUi,
|
||||||
|
QueryParams,
|
||||||
|
Visitor,
|
||||||
|
MultipleMenu,
|
||||||
|
Sub1,
|
||||||
|
Sub2,
|
||||||
|
Env,
|
||||||
|
} as any;
|
||||||
113
src/components/pages/Login.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/16.
|
||||||
|
*/
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Button, Form, Input } from 'antd';
|
||||||
|
import { PwaInstaller } from '../widget';
|
||||||
|
import { useAlita } from 'redux-alita';
|
||||||
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
import { FormProps } from 'antd/lib/form';
|
||||||
|
import umbrella from 'umbrella-storage';
|
||||||
|
import { GithubOutlined, LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
|
import { useUpdateEffect } from 'ahooks';
|
||||||
|
import { trailwayLogin } from '../../service';
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
type LoginProps = {
|
||||||
|
setAlitaState: (param: any) => void;
|
||||||
|
auth: any;
|
||||||
|
} & RouteComponentProps &
|
||||||
|
FormProps;
|
||||||
|
|
||||||
|
const Login = (props: LoginProps) => {
|
||||||
|
const { history } = props;
|
||||||
|
const [auth, setAlita] = useAlita({ auth: {} }, { light: true });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAlita('auth', null);
|
||||||
|
}, [setAlita]);
|
||||||
|
|
||||||
|
useUpdateEffect(() => {
|
||||||
|
if (auth && auth.uid) {
|
||||||
|
// 判断是否登陆
|
||||||
|
umbrella.setLocalStorage('user', auth);
|
||||||
|
history.push('/');
|
||||||
|
}
|
||||||
|
}, [history, auth]);
|
||||||
|
|
||||||
|
const handleSubmit = (values: any) => {
|
||||||
|
if (checkUser(values)) {
|
||||||
|
setAlita({ funcName: values.userName, stateName: 'auth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const checkUser = (values: any) => {
|
||||||
|
// const users = [
|
||||||
|
// ['admin', 'admin'],
|
||||||
|
// ['guest', 'guest'],
|
||||||
|
// ];
|
||||||
|
// return users.some((user) => user[0] === values.userName && user[1] === values.password);
|
||||||
|
let success = trailwayLogin({ username: values.userName, password: values.password }).then(
|
||||||
|
(res) => {
|
||||||
|
console.log(res);
|
||||||
|
return res.data.success === true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log(success);
|
||||||
|
return success;
|
||||||
|
};
|
||||||
|
const gitHub = () => {
|
||||||
|
window.location.href =
|
||||||
|
'https://github.com/login/oauth/authorize?client_id=792cdcd244e98dcd2dee&redirect_uri=http://localhost:3006/&scope=user&state=reactAdmin';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="login">
|
||||||
|
<div className="login-form">
|
||||||
|
<div className="login-logo">
|
||||||
|
<span>React Admin</span>
|
||||||
|
<PwaInstaller />
|
||||||
|
</div>
|
||||||
|
<Form onFinish={handleSubmit} style={{ maxWidth: '300px' }}>
|
||||||
|
<FormItem
|
||||||
|
name="userName"
|
||||||
|
rules={[{ required: true, message: '请输入用户名!' }]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
prefix={<UserOutlined size={13} />}
|
||||||
|
placeholder="管理员输入admin, 游客输入guest"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="password" rules={[{ required: true, message: '请输入密码!' }]}>
|
||||||
|
<Input
|
||||||
|
prefix={<LockOutlined size={13} />}
|
||||||
|
type="password"
|
||||||
|
placeholder="管理员输入admin, 游客输入guest"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<span className="login-form-forgot" 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={gitHub}>
|
||||||
|
<GithubOutlined />
|
||||||
|
(第三方登录)
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Login;
|
||||||
31
src/components/pages/NotFound.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
28
src/components/smenu/Sub1.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* File: Sub1.tsx
|
||||||
|
* Desc: 异步子菜单
|
||||||
|
* File Created: 2020-01-21 11:31:15
|
||||||
|
* Author: chenghao at <865470087@qq.com>
|
||||||
|
* ------
|
||||||
|
* Copyright 2020 - present, chenghao
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
|
||||||
|
const SmenuSub1 = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadcrumbCustom breads={['异步菜单']} />
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col md={24}>
|
||||||
|
<Card title="异步子菜单" bordered={false}>
|
||||||
|
<div>异步子菜单1</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SmenuSub1;
|
||||||
28
src/components/smenu/Sub2.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* File: Sub2.tsx
|
||||||
|
* Desc: 异步子菜单
|
||||||
|
* File Created: 2020-01-21 11:31:15
|
||||||
|
* Author: chenghao at <865470087@qq.com>
|
||||||
|
* ------
|
||||||
|
* Copyright 2020 - present, chenghao
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
|
||||||
|
const SmenuSub2 = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BreadcrumbCustom breads={['异步菜单']} />
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col md={24}>
|
||||||
|
<Card title="异步子菜单" bordered={false}>
|
||||||
|
<div>异步子菜单2</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SmenuSub2;
|
||||||
45
src/components/tables/AdvancedTables.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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 BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
|
||||||
|
class AdvancedTables extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="gutter-example">
|
||||||
|
<BreadcrumbCustom breads={['表格', '高级表格']} />
|
||||||
|
<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;
|
||||||
102
src/components/tables/AsynchronousTable.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/16.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Table, Button, Row, Col, Card } from 'antd';
|
||||||
|
import { getBbcNews } from '../../service';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '新闻标题',
|
||||||
|
dataIndex: 'title',
|
||||||
|
width: 100,
|
||||||
|
render: (text: any, record: any) => (
|
||||||
|
<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 }: { articles: any }) => {
|
||||||
|
this.setState({
|
||||||
|
data: articles,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
onSelectChange = (selectedRowKeys: string[]) => {
|
||||||
|
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 breads={['表格', '异步表格']} />
|
||||||
|
<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 as any}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={this.state.data}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AsynchronousTable;
|
||||||
65
src/components/tables/BasicTable.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/15.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Table, Button } from 'antd';
|
||||||
|
import { DownOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
render: (text: any) => <span>{text}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Age',
|
||||||
|
dataIndex: 'age',
|
||||||
|
key: 'age',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Address',
|
||||||
|
dataIndex: 'address',
|
||||||
|
key: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Action',
|
||||||
|
key: 'action',
|
||||||
|
render: (text: any, record: any) => (
|
||||||
|
<span>
|
||||||
|
<Button>Action 一 {record.name}</Button>
|
||||||
|
<span className="ant-divider" />
|
||||||
|
<Button>Delete</Button>
|
||||||
|
<span className="ant-divider" />
|
||||||
|
<Button className="ant-dropdown-link">
|
||||||
|
More actions <DownOutlined />
|
||||||
|
</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
src/components/tables/BasicTables.tsx
Normal file
@@ -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 '../widget/BreadcrumbCustom';
|
||||||
|
|
||||||
|
const BasicTables = () => (
|
||||||
|
<div className="gutter-example">
|
||||||
|
<BreadcrumbCustom breads={['表格', '基础表格']} />
|
||||||
|
<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;
|
||||||
28
src/components/tables/ExpandedTable.tsx
Normal file
@@ -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;
|
||||||
45
src/components/tables/FixedTable.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/16.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Table } from 'antd';
|
||||||
|
import { ColumnProps } from 'antd/lib/table';
|
||||||
|
|
||||||
|
const columns: ColumnProps<any>[] = [
|
||||||
|
{ 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;
|
||||||
150
src/components/tables/SearchTable.tsx
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/16.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Table, Input, Button } from 'antd';
|
||||||
|
import { SmileOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
searchInput: any;
|
||||||
|
onInputChange = (e: any) => {
|
||||||
|
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: (
|
||||||
|
<SmileOutlined style={{ color: this.state.filtered ? '#108ee9' : '#aaa' }} />
|
||||||
|
),
|
||||||
|
filterDropdownVisible: this.state.filterDropdownVisible,
|
||||||
|
onFilterDropdownVisibleChange: (visible: boolean) =>
|
||||||
|
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: any, record: any) => 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;
|
||||||
82
src/components/tables/SelectTable.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/15.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Table } from 'antd';
|
||||||
|
// import { TableRowSelection } from 'antd/lib/table';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Age',
|
||||||
|
dataIndex: 'age',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Address',
|
||||||
|
dataIndex: 'address',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const data: any[] = [];
|
||||||
|
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: string[] | number[]) => {
|
||||||
|
console.log('selectedRowKeys changed: ', selectedRowKeys);
|
||||||
|
this.setState({ selectedRowKeys });
|
||||||
|
};
|
||||||
|
render() {
|
||||||
|
const { selectedRowKeys } = this.state;
|
||||||
|
const rowSelection: any = {
|
||||||
|
selectedRowKeys,
|
||||||
|
onChange: this.onSelectChange,
|
||||||
|
selections: [
|
||||||
|
{
|
||||||
|
key: 'odd',
|
||||||
|
text: '选择奇数列',
|
||||||
|
onSelect: (changableRowKeys: string[]) => {
|
||||||
|
let newSelectedRowKeys = [];
|
||||||
|
newSelectedRowKeys = changableRowKeys.filter((key, index) => {
|
||||||
|
if (index % 2 !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
this.setState({ selectedRowKeys: newSelectedRowKeys });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'even',
|
||||||
|
text: '选择偶数列',
|
||||||
|
onSelect: (changableRowKeys: string[]) => {
|
||||||
|
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;
|
||||||
117
src/components/tables/SortTable.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* 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',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type SortTableState = {
|
||||||
|
filteredInfo: any;
|
||||||
|
sortedInfo: any;
|
||||||
|
};
|
||||||
|
class SortTable extends React.Component<any, SortTableState> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
filteredInfo: {},
|
||||||
|
sortedInfo: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
handleChange = (pagination: any, filters: any, sorter: any) => {
|
||||||
|
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;
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
filters: [{ text: 'Joe', value: 'Joe' }, { text: 'Jim', value: 'Jim' }],
|
||||||
|
filteredValue: filteredInfo.name || null,
|
||||||
|
onFilter: (value: any, record: any) => record.name.includes(value),
|
||||||
|
sorter: (a: any, b: any) => a.name.length - b.name.length,
|
||||||
|
sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Age',
|
||||||
|
dataIndex: 'age',
|
||||||
|
key: 'age',
|
||||||
|
sorter: (a: any, b: any) => 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: any, record: any) => record.address.includes(value),
|
||||||
|
sorter: (a: any, b: any) => 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;
|
||||||
167
src/components/ui/Buttons.tsx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/23.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Row, Col, Card, Button, Radio, Menu, Dropdown } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { RadioChangeEvent } from 'antd/lib/radio';
|
||||||
|
import { ButtonSize } from 'antd/lib/button';
|
||||||
|
import { DownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
type ButtonsState = {
|
||||||
|
size: ButtonSize;
|
||||||
|
loading: boolean;
|
||||||
|
iconLoading: boolean;
|
||||||
|
};
|
||||||
|
class Buttons extends React.Component<any, ButtonsState> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
size: 'middle',
|
||||||
|
loading: false,
|
||||||
|
iconLoading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSizeChange = (e: RadioChangeEvent) => {
|
||||||
|
this.setState({ size: e.target.value });
|
||||||
|
};
|
||||||
|
handleMenuClick = (e: any) => {
|
||||||
|
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 breads={['UI', '按钮']} />
|
||||||
|
<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 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="middle">Middle</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">
|
||||||
|
<LeftOutlined />
|
||||||
|
Backward
|
||||||
|
</Button>
|
||||||
|
<Button type="primary">
|
||||||
|
Forward
|
||||||
|
<RightOutlined />
|
||||||
|
</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 <DownOutlined />
|
||||||
|
</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;
|
||||||
132
src/components/ui/Draggable.tsx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/28.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/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: any, ui: any) => {
|
||||||
|
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 breads={['UI', '拖拽']} />
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col className="gutter-row" md={6}>
|
||||||
|
<div className="gutter-box">
|
||||||
|
<Draggable {...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 }}
|
||||||
|
{...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;
|
||||||
223
src/components/ui/Gallery.tsx
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/5/6.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/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();
|
||||||
|
};
|
||||||
|
pswpElement: any;
|
||||||
|
gallery: any;
|
||||||
|
openGallery = (item: any) => {
|
||||||
|
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: number, item: any) => {
|
||||||
|
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 as any).width; // set image width
|
||||||
|
item.h = (this as any).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
|
||||||
|
breads={['UI', '画廊(图片来自花瓣网,仅学习,若侵权请联系删除)']}
|
||||||
|
/>
|
||||||
|
<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;
|
||||||
309
src/components/ui/Icons.tsx
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/22.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import Emoji from './emoji';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
|
||||||
|
const emojiList = () => {
|
||||||
|
let _elements = [];
|
||||||
|
for (let i = 1; i < 30; i++) {
|
||||||
|
_elements.push(
|
||||||
|
<li key={i}>
|
||||||
|
<Emoji type={'emoji-' + i} />
|
||||||
|
<span>{'emoji-' + i}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _elements;
|
||||||
|
};
|
||||||
|
|
||||||
|
const icons = {
|
||||||
|
direction: [
|
||||||
|
'step-backward',
|
||||||
|
'step-forward',
|
||||||
|
'fast-backward',
|
||||||
|
'fast-forward',
|
||||||
|
'shrink',
|
||||||
|
'arrows-alt',
|
||||||
|
'down',
|
||||||
|
'up',
|
||||||
|
'left',
|
||||||
|
'right',
|
||||||
|
'caret-up',
|
||||||
|
'caret-down',
|
||||||
|
'caret-left',
|
||||||
|
'caret-right',
|
||||||
|
'up-circle',
|
||||||
|
'down-circle',
|
||||||
|
'left-circle',
|
||||||
|
'right-circle',
|
||||||
|
'up-circle-o',
|
||||||
|
'down-circle-o',
|
||||||
|
'right-circle-o',
|
||||||
|
'left-circle-o',
|
||||||
|
'double-right',
|
||||||
|
'double-left',
|
||||||
|
'verticle-left',
|
||||||
|
'verticle-right',
|
||||||
|
'forward',
|
||||||
|
'backward',
|
||||||
|
'rollback',
|
||||||
|
'enter',
|
||||||
|
'retweet',
|
||||||
|
'swap',
|
||||||
|
'swap-left',
|
||||||
|
'swap-right',
|
||||||
|
'arrow-up',
|
||||||
|
'arrow-down',
|
||||||
|
'arrow-left',
|
||||||
|
'arrow-right',
|
||||||
|
'play-circle',
|
||||||
|
'play-circle-o',
|
||||||
|
'up-square',
|
||||||
|
'down-square',
|
||||||
|
'left-square',
|
||||||
|
'right-square',
|
||||||
|
'up-square-o',
|
||||||
|
'down-square-o',
|
||||||
|
'left-square-o',
|
||||||
|
'right-square-o',
|
||||||
|
'login',
|
||||||
|
'logout',
|
||||||
|
'menu-fold',
|
||||||
|
'menu-unfold',
|
||||||
|
],
|
||||||
|
suggestion: [
|
||||||
|
'question',
|
||||||
|
'question-circle-o',
|
||||||
|
'question-circle',
|
||||||
|
'plus',
|
||||||
|
'plus-circle-o',
|
||||||
|
'plus-circle',
|
||||||
|
'pause',
|
||||||
|
'pause-circle-o',
|
||||||
|
'pause-circle',
|
||||||
|
'minus',
|
||||||
|
'minus-circle-o',
|
||||||
|
'minus-circle',
|
||||||
|
'plus-square',
|
||||||
|
'plus-square-o',
|
||||||
|
'minus-square',
|
||||||
|
'minus-square-o',
|
||||||
|
'info',
|
||||||
|
'info-circle-o',
|
||||||
|
'info-circle',
|
||||||
|
'exclamation',
|
||||||
|
'exclamation-circle-o',
|
||||||
|
'exclamation-circle',
|
||||||
|
'close',
|
||||||
|
'close-circle',
|
||||||
|
'close-circle-o',
|
||||||
|
'close-square',
|
||||||
|
'close-square-o',
|
||||||
|
'check',
|
||||||
|
'check-circle',
|
||||||
|
'check-circle-o',
|
||||||
|
'check-square',
|
||||||
|
'check-square-o',
|
||||||
|
'clock-circle-o',
|
||||||
|
'clock-circle',
|
||||||
|
],
|
||||||
|
logo: [
|
||||||
|
'android',
|
||||||
|
'android-o',
|
||||||
|
'apple',
|
||||||
|
'apple-o',
|
||||||
|
'windows',
|
||||||
|
'windows-o',
|
||||||
|
'ie',
|
||||||
|
'chrome',
|
||||||
|
'github',
|
||||||
|
'aliwangwang',
|
||||||
|
'aliwangwang-o',
|
||||||
|
'dingding',
|
||||||
|
'dingding-o',
|
||||||
|
],
|
||||||
|
other: [
|
||||||
|
'lock',
|
||||||
|
'unlock',
|
||||||
|
'area-chart',
|
||||||
|
'pie-chart',
|
||||||
|
'bar-chart',
|
||||||
|
'dot-chart',
|
||||||
|
'bars',
|
||||||
|
'book',
|
||||||
|
'calendar',
|
||||||
|
'cloud',
|
||||||
|
'cloud-download',
|
||||||
|
'code',
|
||||||
|
'code-o',
|
||||||
|
'copy',
|
||||||
|
'credit-card',
|
||||||
|
'delete',
|
||||||
|
'desktop',
|
||||||
|
'download',
|
||||||
|
'edit',
|
||||||
|
'ellipsis',
|
||||||
|
'file',
|
||||||
|
'file-text',
|
||||||
|
'file-unknown',
|
||||||
|
'file-pdf',
|
||||||
|
'file-excel',
|
||||||
|
'file-jpg',
|
||||||
|
'file-ppt',
|
||||||
|
'file-add',
|
||||||
|
'folder',
|
||||||
|
'folder-open',
|
||||||
|
'folder-add',
|
||||||
|
'hdd',
|
||||||
|
'frown',
|
||||||
|
'frown-o',
|
||||||
|
'meh',
|
||||||
|
'meh-o',
|
||||||
|
'smile',
|
||||||
|
'smile-o',
|
||||||
|
'inbox',
|
||||||
|
'laptop',
|
||||||
|
'appstore-o',
|
||||||
|
'appstore',
|
||||||
|
'line-chart',
|
||||||
|
'link',
|
||||||
|
'mail',
|
||||||
|
'mobile',
|
||||||
|
'notification',
|
||||||
|
'paper-clip',
|
||||||
|
'picture',
|
||||||
|
'poweroff',
|
||||||
|
'reload',
|
||||||
|
'search',
|
||||||
|
'setting',
|
||||||
|
'share-alt',
|
||||||
|
'shopping-cart',
|
||||||
|
'tablet',
|
||||||
|
'tag',
|
||||||
|
'tag-o',
|
||||||
|
'tags',
|
||||||
|
'tags-o',
|
||||||
|
'to-top',
|
||||||
|
'upload',
|
||||||
|
'user',
|
||||||
|
'video-camera',
|
||||||
|
'home',
|
||||||
|
'loading',
|
||||||
|
'loading-3-quarters',
|
||||||
|
'cloud-upload-o',
|
||||||
|
'cloud-download-o',
|
||||||
|
'cloud-upload',
|
||||||
|
'cloud-o',
|
||||||
|
'star-o',
|
||||||
|
'star',
|
||||||
|
'heart-o',
|
||||||
|
'heart',
|
||||||
|
'environment',
|
||||||
|
'environment-o',
|
||||||
|
'eye',
|
||||||
|
'eye-o',
|
||||||
|
'camera',
|
||||||
|
'camera-o',
|
||||||
|
'save',
|
||||||
|
'team',
|
||||||
|
'solution',
|
||||||
|
'phone',
|
||||||
|
'filter',
|
||||||
|
'exception',
|
||||||
|
'export',
|
||||||
|
'customer-service',
|
||||||
|
'qrcode',
|
||||||
|
'scan',
|
||||||
|
'like',
|
||||||
|
'like-o',
|
||||||
|
'dislike',
|
||||||
|
'dislike-o',
|
||||||
|
'message',
|
||||||
|
'pay-circle',
|
||||||
|
'pay-circle-o',
|
||||||
|
'calculator',
|
||||||
|
'pushpin',
|
||||||
|
'pushpin-o',
|
||||||
|
'bulb',
|
||||||
|
'select',
|
||||||
|
'switcher',
|
||||||
|
'rocket',
|
||||||
|
'bell',
|
||||||
|
'disconnect',
|
||||||
|
'database',
|
||||||
|
'compass',
|
||||||
|
'barcode',
|
||||||
|
'hourglass',
|
||||||
|
'key',
|
||||||
|
'flag',
|
||||||
|
'layout',
|
||||||
|
'printer',
|
||||||
|
'sound',
|
||||||
|
'usb',
|
||||||
|
'skin',
|
||||||
|
'tool',
|
||||||
|
'sync',
|
||||||
|
'wifi',
|
||||||
|
'car',
|
||||||
|
'schedule',
|
||||||
|
'user-add',
|
||||||
|
'user-delete',
|
||||||
|
'usergroup-add',
|
||||||
|
'usergroup-delete',
|
||||||
|
'man',
|
||||||
|
'woman',
|
||||||
|
'shop',
|
||||||
|
'gift',
|
||||||
|
'idcard',
|
||||||
|
'medicine-box',
|
||||||
|
'red-envelope',
|
||||||
|
'coffee',
|
||||||
|
'copyright',
|
||||||
|
'trademark',
|
||||||
|
'safety',
|
||||||
|
'wallet',
|
||||||
|
'bank',
|
||||||
|
'trophy',
|
||||||
|
'contacts',
|
||||||
|
'global',
|
||||||
|
'shake',
|
||||||
|
'api',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const iconsList = Object.keys(icons).map((v) =>
|
||||||
|
(icons as any)[v].map((icon: any, i: number) => (
|
||||||
|
<li key={i}>
|
||||||
|
{/* <Icon type={icon} style={{ fontSize: 15 }} /> */}
|
||||||
|
<span>{icon}</span>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
const Icons = () => (
|
||||||
|
<div className="gutter-example">
|
||||||
|
<BreadcrumbCustom breads={['UI', '图标']} />
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col className="gutter-row" md={24}>
|
||||||
|
<div className="gutter-box">
|
||||||
|
<Card bordered={false}>
|
||||||
|
<ul className="icons-list">{emojiList()}</ul>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col className="gutter-row" md={24}>
|
||||||
|
<div className="gutter-box">
|
||||||
|
<Card bordered={false}>
|
||||||
|
<ul className="icons-list">{iconsList}</ul>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Icons;
|
||||||
250
src/components/ui/Modals.tsx
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/23.
|
||||||
|
*/
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Row, Col, Card, Modal, Button } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/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,
|
||||||
|
confirmLoading2: false,
|
||||||
|
};
|
||||||
|
showModal = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
handleOk = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
|
console.log(e);
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
handleCancel = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
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: boolean) => {
|
||||||
|
this.setState({ modal1Visible });
|
||||||
|
};
|
||||||
|
setModal2Visible = (modal2Visible: boolean) => {
|
||||||
|
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 breads={['UI', '对话框']} />
|
||||||
|
<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;
|
||||||
166
src/components/ui/Notifications.tsx
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/25.
|
||||||
|
*/
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Row, Col, Card, Button, notification, Select } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { IconType, ConfigProps } from 'antd/lib/notification';
|
||||||
|
import { SmileOutlined } from '@ant-design/icons';
|
||||||
|
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: IconType) => {
|
||||||
|
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: <SmileOutlined style={{ color: '#108ee9' }} />,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="gutter-example button-demo">
|
||||||
|
<BreadcrumbCustom breads={['UI', '通知提醒框']} />
|
||||||
|
<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: ConfigProps['placement']) => {
|
||||||
|
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;
|
||||||
95
src/components/ui/Spins.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* 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 '../widget/BreadcrumbCustom';
|
||||||
|
import NProgress from 'nprogress';
|
||||||
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
|
class Spins extends React.Component {
|
||||||
|
state = { loading: false };
|
||||||
|
toggle = (value: boolean) => {
|
||||||
|
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 breads={['UI', '加载中']} />
|
||||||
|
<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;
|
||||||
218
src/components/ui/Tabs.tsx
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/25.
|
||||||
|
*/
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Row, Col, Card, Tabs, Radio, Button } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/BreadcrumbCustom';
|
||||||
|
import { RadioChangeEvent } from 'antd/lib/radio';
|
||||||
|
import { TabsPosition } from 'antd/lib/tabs';
|
||||||
|
import { AndroidOutlined, AppleOutlined } from '@ant-design/icons';
|
||||||
|
const TabPane = Tabs.TabPane;
|
||||||
|
|
||||||
|
type TabsCustomState = {
|
||||||
|
activeKey: string;
|
||||||
|
panes: any;
|
||||||
|
mode: TabsPosition;
|
||||||
|
};
|
||||||
|
class TabsCustom extends Component<any, TabsCustomState> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
newTabIndex: number = 0;
|
||||||
|
callback = (key: string) => {
|
||||||
|
console.log(key);
|
||||||
|
};
|
||||||
|
handleModeChange = (e: RadioChangeEvent) => {
|
||||||
|
const mode = e.target.value;
|
||||||
|
this.setState({ mode });
|
||||||
|
};
|
||||||
|
onChange = (activeKey: string) => {
|
||||||
|
this.setState({ activeKey });
|
||||||
|
};
|
||||||
|
onEdit = (targetKey: string | React.MouseEvent<HTMLElement>, action: 'add' | 'remove'): any => {
|
||||||
|
this[action](targetKey as string);
|
||||||
|
};
|
||||||
|
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: string) => {
|
||||||
|
let activeKey = this.state.activeKey;
|
||||||
|
let lastIndex = 0;
|
||||||
|
this.state.panes.forEach((pane: any, i: number) => {
|
||||||
|
if (pane.key === targetKey) {
|
||||||
|
lastIndex = i - 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const panes = this.state.panes.filter((pane: any) => 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 breads={['UI', '标签页']} />
|
||||||
|
<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>
|
||||||
|
<AppleOutlined />
|
||||||
|
Tab 1
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
Tab 1
|
||||||
|
</TabPane>
|
||||||
|
<TabPane
|
||||||
|
tab={
|
||||||
|
<span>
|
||||||
|
<AndroidOutlined />
|
||||||
|
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: any) => (
|
||||||
|
<TabPane tab={pane.title} key={pane.key}>
|
||||||
|
{pane.content}
|
||||||
|
</TabPane>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabsCustom;
|
||||||
195
src/components/ui/Wysiwyg.tsx
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
/**
|
||||||
|
* Created by hao.cheng on 2017/4/26.
|
||||||
|
*/
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Row, Col, Card } from 'antd';
|
||||||
|
import BreadcrumbCustom from '../widget/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';
|
||||||
|
import { EditorState } from 'draft-js';
|
||||||
|
|
||||||
|
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: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
type WysiwygState = {
|
||||||
|
editorContent: any;
|
||||||
|
contentState: any;
|
||||||
|
editorState: EditorState | undefined;
|
||||||
|
};
|
||||||
|
class Wysiwyg extends Component {
|
||||||
|
state: WysiwygState = {
|
||||||
|
editorContent: undefined,
|
||||||
|
contentState: rawContentState,
|
||||||
|
editorState: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
onEditorChange = (editorContent: any) => {
|
||||||
|
this.setState({
|
||||||
|
editorContent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
clearContent = () => {
|
||||||
|
this.setState({
|
||||||
|
contentState: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onContentStateChange = (contentState: any) => {
|
||||||
|
console.log('contentState', contentState);
|
||||||
|
};
|
||||||
|
|
||||||
|
onEditorStateChange = (editorState: any) => {
|
||||||
|
this.setState({
|
||||||
|
editorState,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
imageUploadCallBack = (file: any) =>
|
||||||
|
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 breads={['UI', '富文本']} />
|
||||||
|
<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
src/components/ui/banners/AutoPlay.tsx
Normal file
@@ -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;
|
||||||