├── .gitattributes ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public └── vite.svg ├── src ├── App.jsx ├── api │ ├── config.js │ └── index.js ├── assets │ ├── font │ │ ├── demo.html │ │ ├── webfont.eot │ │ ├── webfont.svg │ │ ├── webfont.ttf │ │ ├── webfont.woff │ │ └── webfont.woff2 │ └── images │ │ ├── 404.jpg │ │ ├── 500.png │ │ ├── loading.gif │ │ ├── loading1.gif │ │ ├── login.jpg │ │ └── user.jpg ├── components │ ├── CustomBreadcrumb │ │ ├── CustomBreadcrumb.jsx │ │ └── index.js │ ├── CustomMenu │ │ ├── CustomMenu.jsx │ │ └── index.js │ └── PageLoading │ │ └── Index.jsx ├── containers │ ├── AppAside.jsx │ ├── AppFooter.jsx │ ├── AppHeader.jsx │ ├── DefaultLayout.jsx │ ├── index.js │ └── menu.js ├── main.jsx ├── routes │ └── index.js ├── style │ ├── App.scss │ ├── base.scss │ ├── layout.scss │ └── view-style │ │ ├── animation.scss │ │ ├── button.scss │ │ ├── dropdown.scss │ │ ├── editor.scss │ │ ├── form.scss │ │ ├── icon.scss │ │ ├── index.scss │ │ ├── login.scss │ │ ├── progress.scss │ │ ├── step.scss │ │ └── table.scss ├── utils │ └── util.js └── views │ ├── About │ ├── About.jsx │ └── index.js │ ├── FormView │ ├── FormBaseView │ │ ├── FormBaseView.jsx │ │ └── index.js │ └── FormStepView │ │ ├── FormStepView.jsx │ │ └── index.js │ ├── Index │ ├── Index.jsx │ ├── bar.jsx │ ├── index.js │ ├── line.jsx │ ├── pictorialBar.jsx │ ├── pie.jsx │ └── scatter.jsx │ ├── Login │ ├── Login.jsx │ └── index.js │ ├── NavView │ ├── Dropdown │ │ ├── Dropdown.jsx │ │ └── index.js │ ├── Menu │ │ ├── Menu.jsx │ │ └── index.js │ └── Step │ │ ├── Step.jsx │ │ └── index.js │ ├── Others │ ├── 404 │ │ ├── 404.jsx │ │ └── index.js │ ├── 500 │ │ ├── 500.jsx │ │ └── index.js │ ├── Animation │ │ ├── Animation.jsx │ │ └── index.js │ ├── Editor │ │ ├── Editor.jsx │ │ └── index.js │ ├── Progress │ │ ├── Progress.jsx │ │ └── index.js │ └── Upload │ │ ├── Upload.jsx │ │ └── index.js │ ├── PublicView │ ├── Button │ │ ├── ButtonView.jsx │ │ ├── components │ │ │ └── IconButton.jsx │ │ └── index.js │ └── Icon │ │ ├── IconView.jsx │ │ └── index.js │ ├── ShowView │ ├── Collapse │ │ ├── Collapse.jsx │ │ └── index.js │ ├── Table │ │ ├── Table.jsx │ │ └── index.js │ ├── Tabs │ │ ├── Tabs.jsx │ │ ├── components │ │ │ ├── AddTabs.jsx │ │ │ ├── BaseTabs.jsx │ │ │ ├── LocationTabs.jsx │ │ │ └── SizeTabs.jsx │ │ └── index.js │ └── Tree │ │ ├── Index.scss │ │ ├── Tree.jsx │ │ ├── components │ │ ├── BaseTree.jsx │ │ ├── ControlledTree.jsx │ │ ├── DragTree.jsx │ │ └── SearchTree.jsx │ │ └── index.js │ └── TestView │ ├── TestView.jsx │ └── index.js └── vite.config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | vue.config.js filter=gitignore 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | **react-vite-admin** 4 | 5 | 基于 React 生态系统搭建的后台管理系统模板 6 | 7 | > [项目预览地址](https://github.react-vite-admin.zzhvv.com/#/login) 8 | 9 | > ~~[ts 版本](https://github.com/zzhStrive/react-vite-ts-admin)~~ 10 | 11 | ## 技术栈 12 | 13 | react@18.2.0 + vite@3.0.7 + antd@4.23.0 + axios@0.19.0 14 | 15 | > `animate.css@3.7.2` 页面动画展示 16 | 17 | > `echarts@4.4.0` 数据可视化 18 | 19 | > `nprogress@0.2.0` 顶部加载条 20 | 21 | > `screenfull@5.0.0` 全屏插件 22 | 23 | > 开发 node 版本 v16.15.0 24 | 25 | ### 基本功能 26 | 27 | - [x] 路由懒加载 28 | - [x] 面包屑导航 29 | - [x] 常用 UI 展示 30 | - [x] echarts 全屏展示 31 | - [x] 登陆/注销功能 32 | 33 | ## 快速启动 34 | 35 | ``` 36 | # 克隆项目 37 | git clone git@github.com:zzhStrive/react-vite-admin.git 38 | 39 | 40 | 41 | # 进入项目目录 42 | cd react-vite-admin 43 | 44 | # 安装依赖 45 | npm install 46 | 47 | # 启动服务 48 | npm run dev dev 49 | 50 | # 打包 51 | npm run build build 52 | ``` 53 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-vite-admin", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@ant-design/icons": "^4.7.0", 13 | "animate.css": "^3.7.2", 14 | "antd": "^4.23.0", 15 | "axios": "^0.19.0", 16 | "echarts": "^4.4.0", 17 | "eslint-plugin-react-hooks": "^2.3.0", 18 | "install": "^0.13.0", 19 | "npm": "^8.19.1", 20 | "nprogress": "^0.2.0", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0", 23 | "react-router-dom": "^5.1.1", 24 | "react-scripts": "^3.2.0", 25 | "react-simplemde-editor": "^5.0.2", 26 | "screenfull": "^5.0.0" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^18.7.14", 30 | "@types/react": "^18.0.17", 31 | "@types/react-dom": "^18.0.6", 32 | "@vitejs/plugin-react": "^2.0.1", 33 | "babel-plugin-import": "^1.12.2", 34 | "customize-cra": "^0.8.0", 35 | "husky": "^3.0.9", 36 | "lint-staged": "^9.4.2", 37 | "prettier": "^1.18.2", 38 | "react-app-rewired": "^2.1.4", 39 | "sass": "^1.54.5", 40 | "vite": "^3.0.7" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy } from 'react' 2 | import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom' 3 | import 'animate.css' 4 | import './style/base.scss' 5 | import './style/App.scss' 6 | import 'antd/dist/antd.css' 7 | import PageLoading from '@/components/PageLoading' 8 | 9 | 10 | // 公共模块 11 | const DefaultLayout = lazy(() => import('./containers')) 12 | 13 | // 基础页面 14 | const View404 = lazy(() => import('./views/Others/404')) 15 | const View500 = lazy(() => import('./views/Others/500')) 16 | const Login = lazy(() => import('./views/Login')) 17 | 18 | // Suspense 19 | // 快速完成时"闪烁" 加载模块只需要几毫秒的时间, fallback也会被执行 会闪烁一下 20 | 21 | const App = () => ( 22 |
23 | 24 | }> 25 | 26 | } /> 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | ) 36 | 37 | export default App 38 | -------------------------------------------------------------------------------- /src/api/config.js: -------------------------------------------------------------------------------- 1 | // 网站可能有好几个域名,所以单独提出来 2 | export const API = '' 3 | 4 | export const URLAPI = '' 5 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | // 这里取决于登录的时候将 token 存储在哪里 4 | const token = localStorage.getItem('token') 5 | 6 | const instance = axios.create({ 7 | timeout: 5000 8 | }) 9 | 10 | // 设置post请求头 11 | instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' 12 | 13 | // 添加请求拦截器 14 | instance.interceptors.request.use( 15 | config => { 16 | // 将 token 添加到请求头 17 | token && (config.headers.Authorization = token) 18 | return config 19 | }, 20 | error => { 21 | return Promise.reject(error) 22 | } 23 | ) 24 | 25 | // 添加响应拦截器 26 | instance.interceptors.response.use( 27 | response => { 28 | if (response.status === 200) { 29 | return Promise.resolve(response) 30 | } else { 31 | return Promise.reject(response) 32 | } 33 | }, 34 | error => { 35 | // 相应错误处理 36 | // 比如: token 过期, 无权限访问, 路径不存在, 服务器问题等 37 | switch (error.response.status) { 38 | case 401: 39 | break 40 | case 403: 41 | break 42 | case 404: 43 | break 44 | case 500: 45 | break 46 | default: 47 | console.log('其他错误信息') 48 | } 49 | return Promise.reject(error) 50 | } 51 | ) 52 | 53 | export default instance 54 | -------------------------------------------------------------------------------- /src/assets/font/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webfont Demo 6 | 222 | 223 | 224 | 225 |
226 |

webfont 字体预览

227 |

字体预览,字体演示

228 | 229 |
230 | 第一步:使用font-face声明字体 231 |
232 | @font-face {
233 |     font-family: 'webfont';
234 |     font-display: swap;
235 |     src: url('webfont.eot'); /* IE9 */
236 |     src: url('webfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
237 |     url('webfont.woff2') format('woff2'),
238 |     url('webfont.woff') format('woff'), /* chrome、firefox */
239 |     url('webfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
240 |     url('webfont.svg#webfont') format('svg'); /* iOS 4.1- */
241 | }
242 | 
243 | 第二步:定义使用 webfont 的样式 244 |
245 | .web-font {
246 |     font-family: "webfont" !important;
247 |     font-size: 16px;
248 |     font-style: normal;
249 |     -webkit-font-smoothing: antialiased;
250 |     -moz-osx-font-smoothing: grayscale;
251 | }
252 | 
253 | 第三步:为文字加上对应的样式 254 |
255 | <i class="web-font">字体预览,字体演示</i>
256 | 
257 |
258 |
259 | 260 | 261 | -------------------------------------------------------------------------------- /src/assets/font/webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/font/webfont.eot -------------------------------------------------------------------------------- /src/assets/font/webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Created by webfont 7 | 8 | 9 | 10 | 11 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/assets/font/webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/font/webfont.ttf -------------------------------------------------------------------------------- /src/assets/font/webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/font/webfont.woff -------------------------------------------------------------------------------- /src/assets/font/webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/font/webfont.woff2 -------------------------------------------------------------------------------- /src/assets/images/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/images/404.jpg -------------------------------------------------------------------------------- /src/assets/images/500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/images/500.png -------------------------------------------------------------------------------- /src/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/images/loading.gif -------------------------------------------------------------------------------- /src/assets/images/loading1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/images/loading1.gif -------------------------------------------------------------------------------- /src/assets/images/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/images/login.jpg -------------------------------------------------------------------------------- /src/assets/images/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzwanfeng/react-vite-admin/32486f7316c8ca9867af6828bfe550dc88c6916f/src/assets/images/user.jpg -------------------------------------------------------------------------------- /src/components/CustomBreadcrumb/CustomBreadcrumb.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Breadcrumb } from 'antd' 4 | import { Link } from 'react-router-dom' 5 | 6 | const CustomBreadcrumb = props => { 7 | return ( 8 | 9 | 10 | 首页 11 | 12 | {props.arr && 13 | props.arr.map(res => { 14 | if (typeof res === 'object') { 15 | return ( 16 | 17 | {res.title} 18 | 19 | ) 20 | } else { 21 | return {res} 22 | } 23 | })} 24 | 25 | ) 26 | } 27 | 28 | CustomBreadcrumb.propTypes = { 29 | arr: PropTypes.array.isRequired 30 | } 31 | 32 | function shouldRender(nextProps, prevProps) { 33 | if (nextProps.arr.join() === prevProps.arr.join()) { 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | export default React.memo(CustomBreadcrumb, shouldRender) 40 | -------------------------------------------------------------------------------- /src/components/CustomBreadcrumb/index.js: -------------------------------------------------------------------------------- 1 | import CustomBreadcrumb from './CustomBreadcrumb.jsx' 2 | 3 | export default CustomBreadcrumb 4 | -------------------------------------------------------------------------------- /src/components/CustomMenu/CustomMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Menu } from 'antd' 4 | import { Link, withRouter } from 'react-router-dom' 5 | import { iconToElement } from '@/utils/util' 6 | 7 | 8 | // 处理 pathname 9 | const getOpenKeys = string => { 10 | let newStr = '', 11 | newArr = [], 12 | arr = string.split('/').map(i => '/' + i) 13 | for (let i = 1; i < arr.length - 1; i++) { 14 | newStr += arr[i] 15 | newArr.push(newStr) 16 | } 17 | return newArr 18 | } 19 | 20 | const CustomMenu = props => { 21 | const [state, setstate] = useState({ 22 | openKeys: [], 23 | selectedKeys: [] 24 | }) 25 | 26 | let { openKeys, selectedKeys } = state 27 | 28 | // 页面刷新的时候可以定位到 menu 显示 29 | useEffect(() => { 30 | let { pathname } = props.location 31 | setstate(prevState => { 32 | return { 33 | ...prevState, 34 | selectedKeys: [pathname], 35 | openKeys: getOpenKeys(pathname) 36 | } 37 | }) 38 | }, [props]) 39 | 40 | // 只展开一个 SubMenu 41 | const onOpenChange = openKeys => { 42 | setstate(prevState => { 43 | if (openKeys.length === 0 || openKeys.length === 1) { 44 | return { ...prevState, openKeys } 45 | } 46 | const latestOpenKey = openKeys[openKeys.length - 1] 47 | 48 | // 这里与定义的路由规则有关 49 | if (latestOpenKey.includes(openKeys[0])) { 50 | return { ...prevState, openKeys } 51 | } else { 52 | return { ...prevState, openKeys: [latestOpenKey] } 53 | } 54 | }) 55 | } 56 | 57 | // 处理下路由 58 | const menuFormatting = (value) => { 59 | const newObject = { 60 | label: value.title, 61 | key: value.key, 62 | icon: '', 63 | children: menuChilderFormatting(value.subs), 64 | } 65 | 66 | if (value.icon && value.icon !== '') { 67 | newObject.icon = iconToElement(value.icon) 68 | } 69 | return newObject 70 | } 71 | 72 | // 处理路由-子路由 73 | const menuChilderFormatting = (value) => { 74 | const newArr = value && value.map(item => { 75 | return menuFormatting(item) 76 | }) 77 | return newArr 78 | } 79 | 80 | // 选中菜单 81 | const handleSelect = ({ item, key, keyPath, selectedKeys, domEvent }) => { 82 | props.history.push(key); 83 | } 84 | 85 | return ( 86 | setstate(prevState => ({ ...prevState, selectedKeys: [key] }))} 92 | onOpenChange={onOpenChange} 93 | onSelect={handleSelect} 94 | items={props.menu && props.menu.map(item => { 95 | return menuFormatting(item) 96 | })} 97 | > 98 | 99 | ) 100 | } 101 | 102 | CustomMenu.propTypes = { 103 | menu: PropTypes.array.isRequired 104 | } 105 | 106 | export default withRouter(CustomMenu) 107 | -------------------------------------------------------------------------------- /src/components/CustomMenu/index.js: -------------------------------------------------------------------------------- 1 | import CustomMenu from './CustomMenu.jsx' 2 | 3 | export default CustomMenu 4 | -------------------------------------------------------------------------------- /src/components/PageLoading/Index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import loading1 from '@/assets/images/loading1.gif' 3 | 4 | 5 | const PageLoading = () => { 6 | return ( 7 |
8 | 9 |
10 | ) 11 | } 12 | 13 | 14 | 15 | 16 | export default PageLoading 17 | -------------------------------------------------------------------------------- /src/containers/AppAside.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Layout } from 'antd' 4 | import CustomMenu from '@/components/CustomMenu' 5 | import { iconToElement } from '@/utils/util' 6 | 7 | const { Sider } = Layout 8 | 9 | 10 | const AppAside = props => { 11 | let { menuToggle, menu } = props 12 | return ( 13 | 14 |
15 | 18 | {iconToElement('GithubOutlined', { fontSize: '3rem' })} 19 | 20 |
21 | 22 |
23 | ) 24 | } 25 | 26 | AppAside.propTypes = { 27 | menuToggle: PropTypes.bool, 28 | menu: PropTypes.array.isRequired 29 | } 30 | 31 | export default AppAside 32 | -------------------------------------------------------------------------------- /src/containers/AppFooter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Layout } from 'antd' 3 | 4 | const { Footer } = Layout 5 | 6 | export default () => 7 | -------------------------------------------------------------------------------- /src/containers/AppHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Menu, Dropdown, Layout, Avatar, Badge } from 'antd' 4 | import { iconToElement } from '@/utils/util' 5 | 6 | const { Header } = Layout 7 | 8 | const AppHeader = props => { 9 | let { menuClick, avatar, menuToggle, loginOut } = props 10 | 11 | const items = [ 12 | { 13 | label: '用户设置', 14 | key: 'item-1', 15 | type: 'group', 16 | children: [ 17 | { 18 | label: '个人设置', 19 | key: 'submenu-item-1' 20 | }, 21 | { 22 | label: '系统设置', 23 | key: 'submenu-item-2' 24 | } 25 | ], 26 | }, 27 | { 28 | label: '退出登录', 29 | key: 'item-2', 30 | }, 31 | ]; 32 | 33 | const menuOnClick = ({ key }) => { 34 | if (key === 'item-2') { 35 | loginOut() 36 | } 37 | }; 38 | 39 | const menu = ( 40 | 44 | ) 45 | 46 | return ( 47 |
48 |
49 | 53 | {menuToggle ? iconToElement('MenuUnfoldOutlined', { fontSize: '2rem' }) : iconToElement('MenuFoldOutlined', { fontSize: '2rem' })} 54 | 55 |
56 | 57 |
58 |
59 | 60 | 61 | {iconToElement('GithubOutlined', { fontSize: '2rem' }, { twoToneColor: "#eb2f96" })} 62 | 63 | 64 |
65 | 66 |
67 | {/* Badge 徽标数 严格模式下目前会报错 */} 68 | {/* */} 69 | 70 | {iconToElement('BellOutlined', { fontSize: '2rem' })} 71 | 72 | {/* */} 73 |
74 | 75 |
76 | 80 |
e.preventDefault()} 82 | className='ant-dropdown-link' 83 | > 84 | 85 |
86 |
87 |
88 |
89 |
90 | ) 91 | } 92 | 93 | AppHeader.propTypes = { 94 | menuClick: PropTypes.func, 95 | avatar: PropTypes.string, 96 | menuToggle: PropTypes.bool, 97 | loginOut: PropTypes.func 98 | } 99 | 100 | export default AppHeader 101 | -------------------------------------------------------------------------------- /src/containers/DefaultLayout.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useReducer } from 'react' 2 | import { Route, Switch, Redirect, withRouter } from 'react-router-dom' 3 | import { Layout, BackTop, message } from 'antd' 4 | import routes from '@/routes' 5 | import echarts from 'echarts/lib/echarts' 6 | import avatar from '@/assets/images/user.jpg' 7 | import menus from './menu' 8 | import '@/style/layout.scss' 9 | 10 | import AppHeader from './AppHeader.jsx' 11 | import AppAside from './AppAside.jsx' 12 | import AppFooter from './AppFooter.jsx' 13 | 14 | const { Content } = Layout 15 | 16 | const MENU_TOGGLE = 'menuToggle' 17 | 18 | const reducer = (state, action) => { 19 | switch (action.type) { 20 | case MENU_TOGGLE: 21 | return { ...state, menuToggle: !state.menuToggle } 22 | default: 23 | return state 24 | } 25 | } 26 | 27 | const getMenu = menu => { 28 | let newMenu, 29 | auth = JSON.parse(localStorage.getItem('user')).auth 30 | if (!auth) { 31 | return menu 32 | } else { 33 | newMenu = menu.filter(res => res.auth && res.auth.indexOf(auth) !== -1) 34 | return newMenu 35 | } 36 | } 37 | 38 | const DefaultLayout = props => { 39 | const [menu] = useState(prevState => { 40 | if (!localStorage.getItem('user')) { 41 | props.history.push('/login') 42 | return [] 43 | } else { 44 | return getMenu(menus) 45 | } 46 | }) 47 | 48 | const [state, dispatch] = useReducer(reducer, { menuToggle: false }) 49 | 50 | let { auth } = JSON.parse(localStorage.getItem('user')) ? JSON.parse(localStorage.getItem('user')) : '' 51 | 52 | const menuClick = () => { 53 | dispatch({ type: 'menuToggle' }) 54 | } 55 | 56 | const loginOut = () => { 57 | localStorage.clear() 58 | props.history.push('/login') 59 | message.success('登出成功!') 60 | } 61 | 62 | useEffect(() => { 63 | let { pathname } = props.location 64 | let timer 65 | 66 | // 菜单收缩展开时 echarts 图表的自适应 67 | if (pathname === '/' || pathname === '/index') { 68 | timer = setTimeout(() => { 69 | echarts.init(document.getElementById('bar')).resize() 70 | echarts.init(document.getElementById('line')).resize() 71 | echarts.init(document.getElementById('pie')).resize() 72 | echarts.init(document.getElementById('pictorialBar')).resize() 73 | echarts.init(document.getElementById('scatter')).resize() 74 | }, 500) 75 | } else { 76 | timer = null 77 | } 78 | return () => { 79 | timer && clearTimeout(timer) 80 | } 81 | }) 82 | 83 | return ( 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | {routes.map(item => { 92 | return ( 93 | 98 | !auth ? ( 99 | 100 | ) : item.auth && item.auth.indexOf(auth) !== -1 ? ( 101 | 102 | ) : ( 103 | // 这里也可以跳转到 403 页面 104 | 105 | ) 106 | }> 107 | ) 108 | })} 109 | 110 | 111 | 112 | 113 | 114 | 115 | ) 116 | } 117 | 118 | export default withRouter(DefaultLayout) 119 | -------------------------------------------------------------------------------- /src/containers/index.js: -------------------------------------------------------------------------------- 1 | import DefaultLayout from './DefaultLayout.jsx' 2 | 3 | export default DefaultLayout 4 | -------------------------------------------------------------------------------- /src/containers/menu.js: -------------------------------------------------------------------------------- 1 | const menu = [ 2 | { 3 | key: '/index', 4 | title: '首页', 5 | icon: 'HomeOutlined', 6 | auth: [1] 7 | }, 8 | { 9 | title: '通用', 10 | key: '/public', 11 | icon: 'AppstoreOutlined', 12 | auth: [1], 13 | subs: [ 14 | { 15 | title: '按钮', 16 | key: '/public/button', 17 | icon: '' 18 | }, 19 | { 20 | title: '图标', 21 | key: '/public/icon', 22 | icon: '' 23 | }] 24 | }, 25 | { 26 | title: '导航', 27 | key: '/nav', 28 | icon: 'BulbOutlined', 29 | subs: [ 30 | { 31 | title: '下拉菜单', 32 | key: '/nav/dropdown', 33 | icon: '' 34 | }, 35 | { 36 | title: '导航菜单', 37 | key: '/nav/menu', 38 | icon: '' 39 | }, 40 | { 41 | title: '步骤条', 42 | key: '/nav/steps', 43 | icon: '' 44 | } 45 | ] 46 | }, 47 | { 48 | title: '表单', 49 | key: '/form', 50 | icon: 'FormOutlined', 51 | subs: [ 52 | { 53 | title: '基础表单', 54 | key: '/form/base-form', 55 | icon: '' 56 | }, 57 | { 58 | title: '步骤表单', 59 | key: '/form/step-form', 60 | icon: '' 61 | } 62 | ] 63 | }, 64 | { 65 | title: '展示', 66 | key: '/show', 67 | icon: 'PieChartOutlined', 68 | subs: [ 69 | { 70 | title: '表格', 71 | key: '/show/table', 72 | icon: '' 73 | }, 74 | { 75 | title: '折叠面板', 76 | key: '/show/collapse', 77 | icon: '' 78 | }, 79 | { 80 | title: '树形控件', 81 | key: '/show/tree', 82 | icon: '' 83 | }, 84 | { 85 | title: '标签页', 86 | key: '/show/tabs', 87 | icon: '' 88 | } 89 | ] 90 | }, 91 | { 92 | title: '其它', 93 | key: '/others', 94 | icon: 'PaperClipOutlined', 95 | auth: [1], 96 | subs: [ 97 | { 98 | title: '进度条', 99 | key: '/others/progress', 100 | icon: '' 101 | }, 102 | { 103 | title: '动画', 104 | key: '/others/animation', 105 | icon: '' 106 | }, 107 | { 108 | title: '上传', 109 | key: '/others/upload', 110 | icon: '' 111 | }, 112 | { 113 | title: '富文本', 114 | key: '/others/editor', 115 | icon: '' 116 | }, 117 | { 118 | title: '404', 119 | key: '/404', 120 | icon: '' 121 | }, 122 | { 123 | title: '500', 124 | key: '/500', 125 | icon: '' 126 | } 127 | ] 128 | }, 129 | { 130 | title: '多级导航', 131 | key: '/one', 132 | icon: 'BarsOutlined', 133 | subs: [ 134 | { 135 | title: '二级', 136 | key: '/one/two', 137 | icon: '', 138 | subs: [{ 139 | title: '三级', 140 | key: '/one/two/three', 141 | icon: '' 142 | }] 143 | } 144 | ] 145 | }, 146 | { 147 | title: '关于', 148 | key: '/about', 149 | icon: 'UserOutlined', 150 | auth: [1] 151 | } 152 | ] 153 | 154 | export default menu 155 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | 2 | // Vite 'global is not defined' 3 | // 问题是因为vite 没有globalwindow像 webpack 那样定义一个字段。有些库依赖它,因为 webpack 比 vite 更老 4 | // 只需在一开始,在任何库导入之前插入: 5 | window.global ||= window; 6 | 7 | import React from 'react' 8 | import ReactDOM from "react-dom/client"; 9 | import App from './App' 10 | 11 | 12 | // Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot 13 | 14 | // react-dom:ReactDOM.render已弃用。使用它会警告并在 React 17 模式下运行您的应用程序。 15 | // react-dom:ReactDOM.hydrate已弃用。使用它会警告并在 React 17 模式下运行您的应用程序。 16 | // react-dom:ReactDOM.unmountComponentAtNode已弃用。 17 | // react-dom:ReactDOM.renderSubtreeIntoContainer已弃用。 18 | // react-dom/server:ReactDOMServer.renderToNodeStream已弃用。 19 | // 要解决它,您可以恢复到以前版本的 React 或更新您的 index.js 文件以符合 React 18 语法 20 | 21 | const root = ReactDOM.createRoot(document.getElementById("root")); 22 | root.render( 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React, { lazy } from 'react' 2 | 3 | 4 | const Index = lazy(() => import('@/views/Index')) 5 | 6 | // 通用 7 | const ButtonView = lazy(() => import('@/views/PublicView/Button')) 8 | const IconView = lazy(() => import('@/views/PublicView/Icon')) 9 | 10 | // 导航 11 | const DropdownView = lazy(() => import('@/views/NavView/Dropdown')) 12 | const MenuView = lazy(() => import('@/views/NavView/Menu')) 13 | const StepView = lazy(() => import('@/views/NavView/Step')) 14 | 15 | // 表单 16 | const FormBaseView = lazy(() => import('@/views/FormView/FormBaseView')) 17 | const FormStepView = lazy(() => import('@/views/FormView/FormStepView')) 18 | 19 | // 展示 20 | const TableView = lazy(() => import('@/views/ShowView/Table')) 21 | const CollapseView = lazy(() => import('@/views/ShowView/Collapse')) 22 | const TreeView = lazy(() => import('@/views/ShowView/Tree')) 23 | const TabsView = lazy(() => import('@/views/ShowView/Tabs')) 24 | 25 | // 其它 26 | const ProgressView = lazy(() => import('@/views/Others/Progress')) 27 | const AnimationView = lazy(() => import('@/views/Others/Animation')) 28 | const EditorView = lazy(() => import('@/views/Others/Editor')) 29 | const UploadView = lazy(() => import('@/views/Others/Upload')) 30 | 31 | const Three = lazy(() => import('@/views/TestView')) 32 | const About = lazy(() => import('@/views/About')) 33 | 34 | const routes = [ 35 | { path: '/index', exact: true, name: 'Index', component: Index, auth: [1] }, 36 | { path: '/public/button', exact: false, name: '按钮', component: ButtonView, auth: [1] }, 37 | { path: '/public/icon', exact: false, name: '图标', component: IconView, auth: [1] }, 38 | { path: '/nav/dropdown', exact: false, name: '下拉菜单', component: DropdownView }, 39 | { path: '/nav/menu', exact: false, name: '下拉菜单', component: MenuView }, 40 | { path: '/nav/steps', exact: false, name: '步骤条', component: StepView }, 41 | { path: '/form/base-form', exact: false, name: '表单', component: FormBaseView }, 42 | { path: '/form/step-form', exact: false, name: '表单', component: FormStepView }, 43 | { path: '/show/table', exact: false, name: '表格', component: TableView }, 44 | { path: '/show/collapse', exact: false, name: '折叠面板', component: CollapseView }, 45 | { path: '/show/tree', exact: false, name: '树形控件', component: TreeView }, 46 | { path: '/show/tabs', exact: false, name: '标签页', component: TabsView }, 47 | { path: '/others/progress', exact: false, name: '进度条', component: ProgressView, auth: [1] }, 48 | { path: '/others/animation', exact: false, name: '动画', component: AnimationView, auth: [1] }, 49 | { path: '/others/editor', exact: false, name: '富文本', component: EditorView, auth: [1] }, 50 | { path: '/others/upload', exact: false, name: '上传', component: UploadView, auth: [1] }, 51 | { path: '/one/two/three', exact: false, name: '三级', component: Three }, 52 | { path: '/about', exact: false, name: '关于', component: About, auth: [1] } 53 | ] 54 | 55 | export default routes 56 | -------------------------------------------------------------------------------- /src/style/App.scss: -------------------------------------------------------------------------------- 1 | // 仅支持 chrome 滚动条样式 2 | 3 | /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ 4 | ::-webkit-scrollbar { 5 | width: 5px; 6 | background-color: #fff; 7 | } 8 | 9 | /*定义滚动条轨道 内阴影+圆角*/ 10 | ::-webkit-scrollbar-track { 11 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 12 | } 13 | 14 | /*定义滑块 内阴影+圆角*/ 15 | ::-webkit-scrollbar-thumb { 16 | border-radius: 10px; 17 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 18 | background-color: #ccc; 19 | } 20 | 21 | .base-style { 22 | background-color: #fff; 23 | margin-bottom: 1rem; 24 | padding: 1.5rem; 25 | border-radius: 5px; 26 | box-shadow: 0px 2px 13px 0px rgba(228, 228, 228, 0.6); 27 | } 28 | 29 | @font-face { 30 | font-family: 'webfont'; 31 | font-display: swap; 32 | src: url('../assets/font/webfont.eot'); /* IE9 */ 33 | src: url('../assets/font/webfont.eot') format('embedded-opentype'), /* IE6-IE8 */ 34 | url('../assets/font/webfont.woff2') format('woff2'), 35 | url('../assets/font/webfont.woff') format('woff'), /* chrome、firefox */ 36 | url('../assets/font/webfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ 37 | url('../assets/font/webfont.svg') format('svg'); /* iOS 4.1- */ 38 | } 39 | 40 | html, 41 | body { 42 | font-family: 'webfont' !important; 43 | } 44 | -------------------------------------------------------------------------------- /src/style/base.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /*! 4 | * @名称:base.css 5 | * @功能:1、重设浏览器默认样式 6 | * 2、设置通用原子类 7 | */ 8 | /* 防止用户自定义背景颜色对网页的影响,添加让用户可以自定义字体 */ 9 | html { 10 | background: white; 11 | color: black; 12 | font-size: 10px; 13 | } 14 | 15 | /* 内外边距通常让各个浏览器样式的表现位置不同 */ 16 | body, 17 | div, 18 | dl, 19 | dt, 20 | dd, 21 | ul, 22 | ol, 23 | li, 24 | h1, 25 | h2, 26 | h3, 27 | h4, 28 | h5, 29 | h6, 30 | pre, 31 | code, 32 | form, 33 | fieldset, 34 | legend, 35 | input, 36 | textarea, 37 | p, 38 | blockquote, 39 | th, 40 | td, 41 | hr, 42 | button, 43 | article, 44 | aside, 45 | details, 46 | figcaption, 47 | figure, 48 | footer, 49 | header, 50 | hgroup, 51 | menu, 52 | nav, 53 | section { 54 | margin: 0; 55 | padding: 0; 56 | } 57 | 58 | /* 要注意表单元素并不继承父级 font 的问题 */ 59 | body, 60 | button, 61 | input, 62 | select, 63 | textarea { 64 | font: 14px \5b8b\4f53, arial, sans-serif; 65 | } 66 | 67 | input, 68 | select, 69 | textarea { 70 | font-size: 100%; 71 | } 72 | 73 | /* 去掉 table cell 的边距并让其边重合 */ 74 | table { 75 | border-collapse: collapse; 76 | border-spacing: 0; 77 | } 78 | 79 | /* ie bug:th 不继承 text-align */ 80 | th { 81 | text-align: inherit; 82 | } 83 | 84 | /* 去除默认边框 */ 85 | fieldset, 86 | img { 87 | border: none; 88 | } 89 | 90 | /* ie6 7 8(q) bug 显示为行内表现 */ 91 | iframe { 92 | display: block; 93 | } 94 | 95 | /* 去掉 firefox 下此元素的边框 */ 96 | abbr, 97 | acronym { 98 | border: none; 99 | font-variant: normal; 100 | } 101 | 102 | /* 一致的 del 样式 */ 103 | del { 104 | text-decoration: line-through; 105 | } 106 | 107 | address, 108 | caption, 109 | cite, 110 | code, 111 | dfn, 112 | em, 113 | th, 114 | var { 115 | font-style: normal; 116 | font-weight: 500; 117 | } 118 | 119 | /* 去掉列表前的标识,li 会继承 */ 120 | ol, 121 | ul { 122 | list-style: none; 123 | } 124 | 125 | /* 对齐是排版最重要的因素,别让什么都居中 */ 126 | caption, 127 | th { 128 | text-align: left; 129 | } 130 | 131 | /* 来自yahoo,让标题都自定义,适应多个系统应用 */ 132 | h1, 133 | h2, 134 | h3, 135 | h4, 136 | h5, 137 | h6 { 138 | font-size: 100%; 139 | font-weight: 500; 140 | } 141 | 142 | h1 { 143 | font-size: 2.6rem; 144 | } 145 | 146 | h2 { 147 | font-size: 2.4rem; 148 | } 149 | 150 | h3 { 151 | font-size: 2rem; 152 | } 153 | 154 | q:before, 155 | q:after { 156 | content: ''; 157 | } 158 | 159 | /* 统一上标和下标 */ 160 | sub, 161 | sup { 162 | font-size: 75%; 163 | line-height: 0; 164 | position: relative; 165 | vertical-align: baseline; 166 | } 167 | 168 | sup { 169 | top: -0.5em; 170 | } 171 | 172 | sub { 173 | bottom: -0.25em; 174 | } 175 | 176 | // /* 让链接在 hover 状态下显示下划线 */ 177 | // a:hover { 178 | // text-decoration: underline; 179 | // } 180 | 181 | /* 默认不显示下划线,保持页面简洁 */ 182 | ins, 183 | a { 184 | text-decoration: none; 185 | } 186 | 187 | /* 去除 ie6 & ie7 焦点点状线 */ 188 | a:focus, 189 | *:focus { 190 | outline: none; 191 | } 192 | 193 | /* 清除浮动 */ 194 | .clearfix:before, 195 | .clearfix:after { 196 | content: ''; 197 | display: block; 198 | } 199 | 200 | .clearfix:after { 201 | clear: both; 202 | overflow: hidden; 203 | } 204 | 205 | .clearfix { 206 | zoom: 1; 207 | /* for ie6 & ie7 */ 208 | } 209 | 210 | .clear { 211 | clear: both; 212 | display: block; 213 | font-size: 0; 214 | height: 0; 215 | line-height: 0; 216 | overflow: hidden; 217 | } 218 | 219 | /* 设置显示和隐藏,通常用来与 js 配合 */ 220 | .hide { 221 | display: none; 222 | } 223 | 224 | .block { 225 | display: block; 226 | } 227 | 228 | /* 设置浮动,减少浮动带来的 bug */ 229 | .fl, 230 | .fr { 231 | display: inline; 232 | } 233 | 234 | .fl { 235 | float: left; 236 | } 237 | 238 | .fr { 239 | float: right; 240 | } 241 | 242 | .mr15 { 243 | margin-right: 15px; 244 | } 245 | 246 | 247 | a:focus, 248 | a:active { 249 | outline: none; 250 | } 251 | 252 | a, 253 | a:focus, 254 | a:hover { 255 | cursor: pointer; 256 | color: inherit; 257 | text-decoration: none; 258 | } 259 | 260 | div:focus { 261 | outline: none; 262 | } 263 | 264 | .text_center { 265 | text-align: center; 266 | } 267 | 268 | .text_right { 269 | text-align: right; 270 | } 271 | 272 | .text_left { 273 | text-align: left; 274 | } 275 | 276 | .flex-1 { 277 | flex: 1; 278 | } 279 | 280 | .r-flex { 281 | display: flex !important; 282 | 283 | &.column { 284 | flex-direction: column; 285 | } 286 | 287 | &.wrap { 288 | flex-wrap: wrap; 289 | } 290 | 291 | &.jsb { 292 | justify-content: space-between; 293 | } 294 | 295 | &.jsc { 296 | justify-content: center; 297 | } 298 | 299 | &.aic { 300 | align-items: center; 301 | } 302 | 303 | .content { 304 | flex: 1; 305 | overflow: auto; 306 | -webkit-overflow-scrolling: touch; 307 | } 308 | } 309 | 310 | .text-overflow { 311 | overflow: hidden; 312 | white-space: nowrap; 313 | text-overflow: ellipsis; 314 | } 315 | -------------------------------------------------------------------------------- /src/style/layout.scss: -------------------------------------------------------------------------------- 1 | .app { 2 | font-size: 1.6rem; 3 | @media screen and (max-width: 960px) { 4 | font-size: 1.4rem; 5 | } 6 | .aside { 7 | position: fixed; 8 | left: 0; 9 | height: 100vh; 10 | overflow-y: auto; 11 | .logo { 12 | height: 3rem; 13 | line-height: 3rem; 14 | margin: 1rem; 15 | box-sizing: border-box; 16 | border-radius: 1rem; 17 | padding: 0 1rem; 18 | text-align: center; 19 | img { 20 | height: 100%; 21 | } 22 | } 23 | } 24 | 25 | .header { 26 | background-color: #fff; 27 | padding: 0 1.5rem; 28 | display: flex; 29 | justify-content: space-between; 30 | .right { 31 | display: flex; 32 | align-items: center; 33 | } 34 | } 35 | 36 | .content { 37 | padding: 10px 18px; 38 | width: auto !important; 39 | } 40 | 41 | .footer { 42 | text-align: center; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/style/view-style/animation.scss: -------------------------------------------------------------------------------- 1 | .ant-btn { 2 | margin-right: 8px; 3 | margin-bottom: 12px; 4 | } 5 | -------------------------------------------------------------------------------- /src/style/view-style/button.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | .ant-btn { 3 | margin-right: 8px; 4 | margin-bottom: 12px; 5 | } 6 | 7 | .ant-btn-group { 8 | margin-right: 8px; 9 | margin-bottom: 12px; 10 | } 11 | 12 | .ant-btn-group .ant-btn { 13 | margin: -1px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/style/view-style/dropdown.scss: -------------------------------------------------------------------------------- 1 | .ant-btn { 2 | margin-right: 8px; 3 | margin-bottom: 12px; 4 | } 5 | 6 | #components-dropdown-demo-dropdown-button .ant-dropdown-button { 7 | margin: 0 8px 8px 0; 8 | } 9 | 10 | #components-dropdown-demo-dropdown-button .ant-dropdown-button .ant-btn { 11 | margin: -1px; 12 | } 13 | -------------------------------------------------------------------------------- /src/style/view-style/editor.scss: -------------------------------------------------------------------------------- 1 | .editor { 2 | padding: 2rem; 3 | border: 1px solid #f3eded; 4 | border-radius: 5px; 5 | } 6 | -------------------------------------------------------------------------------- /src/style/view-style/form.scss: -------------------------------------------------------------------------------- 1 | #components-form-demo-normal-login .login-form { 2 | max-width: 300px; 3 | } 4 | 5 | #components-form-demo-normal-login .login-form-forgot { 6 | float: right; 7 | } 8 | 9 | #components-form-demo-normal-login .login-form-button { 10 | width: 100%; 11 | } 12 | 13 | .show-data { 14 | .ant-form-item { 15 | margin: 0; 16 | } 17 | .ant-btn { 18 | margin-right: 8px; 19 | margin-bottom: 12px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/style/view-style/icon.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | .anticons-list::after { 3 | content: ''; 4 | display: block; 5 | clear: both; 6 | } 7 | 8 | .anticons-list { 9 | list-style: none; 10 | margin: 0; 11 | padding: 0; 12 | 13 | li { 14 | position: relative; 15 | float: left; 16 | width: 10%; 17 | height: 60px; 18 | line-height: 60px; 19 | overflow: hidden; 20 | color: #555; 21 | text-align: center; 22 | list-style: none; 23 | background-color: #fff; 24 | border-radius: 4px; 25 | font-size: 28px; 26 | cursor: pointer; 27 | } 28 | 29 | li:hover { 30 | background-color: #ccc; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/style/view-style/index.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | padding: 1.6rem 0; 3 | .index-header { 4 | color: #fff; 5 | span { 6 | font-weight: 600; 7 | font-size: 2rem; 8 | } 9 | .base-style { 10 | height: 9rem; 11 | display: flex; 12 | align-items: center; 13 | 14 | >span:nth-child(1){ 15 | margin-right: 1rem; 16 | } 17 | 18 | >div:nth-child(2){ 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | } 25 | 26 | .wechat { 27 | background-color: #70ca63; 28 | } 29 | 30 | .qq { 31 | background-color: #967adc; 32 | } 33 | 34 | .dingding { 35 | background-color: #5d5dfd; 36 | } 37 | 38 | .weibo { 39 | background-color: #fb452d; 40 | } 41 | 42 | .icon-style { 43 | color: #fbfbf4b3; 44 | font-size: 6rem; 45 | padding-right: 1rem; 46 | } 47 | } 48 | .bar-header { 49 | display: flex; 50 | justify-content: space-between; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/style/view-style/login.scss: -------------------------------------------------------------------------------- 1 | .login { 2 | background: url("../../assets/images/login.jpg") no-repeat; 3 | background-size: cover; 4 | 5 | .model { 6 | height: 100vh; 7 | overflow: hidden; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | 12 | .login-form { 13 | padding: 2rem; 14 | box-shadow: 0px 2px 13px 0px rgba(228, 228, 228, 0.6); 15 | border-radius: 5px; 16 | background-color: #fff; 17 | width: 35rem; 18 | 19 | .login-form-button { 20 | width: 100%; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/style/view-style/progress.scss: -------------------------------------------------------------------------------- 1 | .ant-progress-circle .ant-progress-inner { 2 | margin-right: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /src/style/view-style/step.scss: -------------------------------------------------------------------------------- 1 | .steps-content { 2 | margin-top: 16px; 3 | border: 1px dashed #e9e9e9; 4 | border-radius: 6px; 5 | background-color: #fafafa; 6 | min-height: 200px; 7 | text-align: center; 8 | padding-top: 80px; 9 | } 10 | 11 | .steps-action { 12 | margin-top: 24px; 13 | } 14 | -------------------------------------------------------------------------------- /src/style/view-style/table.scss: -------------------------------------------------------------------------------- 1 | .table{ 2 | position: relative; 3 | 4 | .toc-affix { 5 | position: absolute; 6 | top: 80px; 7 | right: 20px; 8 | z-index: 99; 9 | } 10 | 11 | .ant-affix{ 12 | right: 20px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/util.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as Icon from "@ant-design/icons" 3 | 4 | 5 | // 编写生成ReactNode的方法 6 | // rotate = 0, spin = false, className = '', 7 | export const iconToElement = (name, style, other = {}) => { 8 | const { twoToneColor, rotate, component } = other 9 | return React.createElement(Icon && Icon[name], { 10 | twoToneColor, 11 | rotate, 12 | component, 13 | style 14 | }) 15 | 16 | } -------------------------------------------------------------------------------- /src/views/About/About.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Layout, Divider, Descriptions } from 'antd' 3 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 4 | import ProjectConfigJson from '../../../package.json' 5 | 6 | 7 | const { dependencies, devDependencies } = ProjectConfigJson 8 | const DependenciesArr = [] 9 | const DevDependenciesArr = [] 10 | 11 | Object.keys(dependencies).forEach(key => { 12 | const dependenciesObj = { name: '', version: '' } 13 | dependenciesObj.name = key 14 | dependenciesObj.version = dependencies[key] 15 | DependenciesArr.push(dependenciesObj) 16 | }) 17 | 18 | Object.keys(devDependencies).forEach(key => { 19 | const dependenciesObj = { name: '', version: '' } 20 | dependenciesObj.name = key 21 | dependenciesObj.version = devDependencies[key] 22 | DevDependenciesArr.push(dependenciesObj) 23 | }) 24 | 25 | 26 | const DependenciesView = () => { 27 | return ( 28 | 33 | { 34 | DependenciesArr && DependenciesArr.map((item, index) => { 35 | return ( 36 | {item.version} 37 | ) 38 | }) 39 | } 40 | 41 | ) 42 | } 43 | 44 | const DevDependenciesView = () => { 45 | return ( 46 | 51 | { 52 | DevDependenciesArr && DevDependenciesArr.map((item, index) => { 53 | return ( 54 | {item.version} 55 | ) 56 | }) 57 | } 58 | 59 | ) 60 | } 61 | 62 | 63 | const AboutView = () => { 64 | 65 | return ( 66 | 67 |
68 | 69 |
70 | 71 |
72 |

关于作者

73 | 74 |

这个人很懒,什么都没有留下……

75 |
76 | 77 |
78 | 79 |
80 | 81 |
82 | 83 |
84 |
85 | ) 86 | } 87 | 88 | export default AboutView 89 | -------------------------------------------------------------------------------- /src/views/About/index.js: -------------------------------------------------------------------------------- 1 | import AboutView from './About.jsx' 2 | 3 | export default AboutView 4 | -------------------------------------------------------------------------------- /src/views/FormView/FormBaseView/FormBaseView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 3 | import { 4 | Alert, 5 | Layout, 6 | Row, 7 | Col, 8 | Divider, 9 | Form, 10 | Button, 11 | Input, 12 | InputNumber, 13 | Checkbox, 14 | Tooltip, 15 | Cascader, 16 | Select, 17 | DatePicker, 18 | Radio, 19 | Rate, 20 | Switch, 21 | Slider, 22 | AutoComplete, 23 | message 24 | } from 'antd' 25 | import '@/style/view-style/form.scss' 26 | import { 27 | UserOutlined, 28 | UnlockOutlined, 29 | ConsoleSqlOutlined 30 | } from '@ant-design/icons'; 31 | 32 | const { Option } = Select 33 | const AutoCompleteOption = AutoComplete.Option 34 | 35 | // 住址 36 | const residences = [ 37 | { 38 | value: 'sichuan', 39 | label: '四川', 40 | children: [ 41 | { 42 | value: 'chengdu', 43 | label: '成都', 44 | children: [ 45 | { 46 | value: 'gaoxin', 47 | label: '高新区' 48 | } 49 | ] 50 | } 51 | ] 52 | }, 53 | { 54 | value: 'gansu', 55 | label: '甘肃', 56 | children: [ 57 | { 58 | value: 'lanzhou', 59 | label: '兰州', 60 | children: [ 61 | { 62 | value: 'anning', 63 | label: '安宁区' 64 | } 65 | ] 66 | } 67 | ] 68 | } 69 | ] 70 | 71 | const FromView = props => { 72 | const [confirmDirty, setConfirmDirty] = useState(false) 73 | const [autoCompleteResult, setAutoCompleteResult] = useState([]) 74 | const [visible, setVisible] = useState(true) 75 | // AutoComplete 邮箱自动完成 76 | const [value, setValue] = useState(''); 77 | const [options, setOptions] = useState([]); 78 | 79 | const [form] = Form.useForm(); 80 | const switchValue = Form.useWatch('switch', form) 81 | const agreementValue = Form.useWatch('agreement', form) 82 | 83 | const handleSubmit = e => { 84 | e.preventDefault() 85 | props.form.validateFieldsAndScroll((err, fieldsValue) => { 86 | if (err) return 87 | const values = { 88 | ...fieldsValue, 89 | 'date-picker': fieldsValue['date-picker'] ? fieldsValue['date-picker'].format('YYYY-MM-DD') : '' 90 | } 91 | console.log('这就是你填好的数据' + values) 92 | message.info('你很棒哦,这么快就填好了!') 93 | }) 94 | } 95 | 96 | 97 | // AutoComplete 邮箱自动完成 98 | const mockVal = (str) => { 99 | let autoCompleteResult 100 | autoCompleteResult = ['@google.com', '@163.com', '@qq.com'].map(domain => { 101 | return { value: `${str}${domain}` } 102 | }) 103 | return autoCompleteResult 104 | } 105 | 106 | const onSearch = (searchText) => { 107 | setOptions( 108 | !searchText ? [] : mockVal(searchText), 109 | ); 110 | }; 111 | 112 | const onSelect = (data) => { 113 | console.log('onSelect', data); 114 | }; 115 | 116 | const onChange = (data) => { 117 | setValue(data); 118 | }; 119 | 120 | 121 | // 整体布局 122 | const formItemLayout = { 123 | labelCol: { 124 | xs: { span: 16 }, 125 | sm: { span: 6 } 126 | }, 127 | wrapperCol: { 128 | xs: { span: 16 }, 129 | sm: { span: 10 } 130 | } 131 | } 132 | 133 | // item针对布局 134 | const tailFormItemLayout = { 135 | wrapperCol: { 136 | xs: { 137 | span: 16, 138 | offset: 0 139 | }, 140 | sm: { 141 | span: 10, 142 | offset: 6 143 | } 144 | } 145 | } 146 | 147 | // 联系电话 148 | const prefixSelector = ( 149 | 153 | 161 | 162 | ); 163 | 164 | return ( 165 | 166 |
167 | 168 |
169 |
170 |

何时使用

171 | 172 |

用于创建一个实体或收集信息。

173 |

需要对输入的数据类型进行校验时。

174 |
175 | 176 | 177 | 178 |
179 |
180 | {visible ? ( 181 | setVisible(false)} 187 | /> 188 | ) : null} 189 |
190 | 基础功能 191 |
196 | 197 | 198 | } 200 | placeholder='用户名' 201 | /> 202 | 203 | 204 | 205 | 206 | 207 | 208 | 不详 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | A 217 | 218 | 219 | 220 | B 221 | 222 | 223 | 224 | C 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 250 | 258 | 259 | 260 | 261 | 262 | 272 | 273 | 274 | 275 | ({ 285 | validator (_, value) { 286 | if (!value || getFieldValue('password') === value) { 287 | return Promise.resolve(); 288 | } 289 | 290 | return Promise.reject(new Error('两次输入密码不一致!')); 291 | }, 292 | }), 293 | ]}> 294 | 296 | setConfirmDirty(() => (confirmDirty ? confirmDirty : !!e.target.value)) 297 | } 298 | placeholder='请确认密码' 299 | /> 300 | 301 | 302 | 308 | 309 | 310 | 311 | 316 | 317 | 318 | 319 | 325 | 326 | 327 | 328 | 334 | 335 | 336 | 337 | 342 | 344 | 345 | 346 | 351 | 352 | 阅读并理解 此协议 353 | 354 | 355 | 356 | 357 | 364 | 365 |
366 |
367 | 368 |
369 |
370 | ) 371 | } 372 | 373 | 374 | export default FromView 375 | -------------------------------------------------------------------------------- /src/views/FormView/FormBaseView/index.js: -------------------------------------------------------------------------------- 1 | import FormBaseView from './FormBaseView.jsx' 2 | 3 | export default FormBaseView 4 | -------------------------------------------------------------------------------- /src/views/FormView/FormStepView/FormStepView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 3 | import { Layout, Divider, Row, Col, Steps, Button, Form, Input, Select, Alert, Result } from 'antd' 4 | import '@/style/view-style/form.scss' 5 | 6 | const { Step } = Steps 7 | const { Option } = Select 8 | 9 | const formItemLayout = { 10 | labelCol: { 11 | span: 8 12 | }, 13 | wrapperCol: { 14 | span: 8 15 | } 16 | } 17 | 18 | const tailFormItemLayout = { 19 | wrapperCol: { 20 | offset: 8 21 | } 22 | } 23 | 24 | const Step1From = props => { 25 | const [form] = Form.useForm(); 26 | 27 | const handleSelectChange = value => { 28 | form.setFieldsValue({ 29 | Email: `${value === 'kenan' ? 'kenan@google.com' : 'maoli@google.com'}` 30 | }) 31 | } 32 | 33 | const step1Submit = values => { 34 | props.getFormData(values) 35 | props.setCurrent(1) 36 | } 37 | 38 | // 联系方式 39 | const selectBefore = ( 40 | 44 | 49 | 50 | ); 51 | 52 | return ( 53 |
54 |
60 | 66 | 70 | 71 | 72 | 78 | 82 | 83 | 84 | 90 | 91 | 92 | 93 | 99 | 100 | 101 | 102 | 103 | 106 | 107 |
108 |
109 | ) 110 | } 111 | 112 | const Step2From = props => { 113 | const [visible, setVisible] = useState(true) 114 | const [iconLoading, setIconLoading] = useState(false) 115 | 116 | const step2Submit = () => { 117 | setIconLoading(true) 118 | setTimeout(() => { 119 | setIconLoading(false) 120 | props.setCurrent(2) 121 | }, 2000) 122 | } 123 | 124 | const { formData } = props 125 | return ( 126 |
127 | 128 | 129 | {visible ? ( 130 | setVisible(false)} 136 | {...formItemLayout} 137 | /> 138 | ) : null} 139 | 140 | 141 | 142 |
143 | {formData.User} 144 | {formData.Email} 145 | {formData.Password} 146 | {formData.Code} 147 | 148 | 149 | 152 | 153 | 154 | 155 |
156 | ) 157 | } 158 | 159 | const Step3From = props => { 160 | return ( 161 | props.setCurrent(0)}> 167 | 再发一封 168 | , 169 | 170 | ]} 171 | /> 172 | ) 173 | } 174 | 175 | 176 | const FormStepView = props => { 177 | const [current, setCurrent] = useState(0) 178 | const [formData, setFormData] = useState(null) 179 | 180 | return ( 181 | 182 |
183 | 184 |
185 |
186 |

何时使用

187 | 188 |

当用户需要分步收集不同信息时

189 |
190 | 191 | 192 | 193 |
194 | 分步表单 195 |
196 | 197 | 198 | 199 | 200 | 201 | 202 | {current === 0 && ( 203 | setFormData(val)} setCurrent={val => setCurrent(val)} /> 204 | )} 205 | {current === 1 && setCurrent(val)} />} 206 | {current === 2 && setCurrent(val)} />} 207 |
208 |
209 | 210 |
211 |
212 | ) 213 | } 214 | 215 | export default FormStepView 216 | -------------------------------------------------------------------------------- /src/views/FormView/FormStepView/index.js: -------------------------------------------------------------------------------- 1 | import FormStepView from './FormStepView.jsx' 2 | 3 | export default FormStepView 4 | -------------------------------------------------------------------------------- /src/views/Index/Index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Layout, Row, Col, Divider } from 'antd' 3 | import screenfull from 'screenfull' 4 | import '@/style/view-style/index.scss' 5 | 6 | import BarEcharts from './bar.jsx' 7 | import PieEcharts from './pie.jsx' 8 | import LineEcharts from './line.jsx' 9 | import ScatterEcharts from './scatter.jsx' 10 | import PictorialBarEcharts from './pictorialBar.jsx' 11 | 12 | import { iconToElement } from '@/utils/util' 13 | 14 | const Index = () => { 15 | const fullToggle = () => { 16 | if (screenfull.isEnabled) { 17 | screenfull.request(document.getElementById('bar')) 18 | } 19 | } 20 | return ( 21 | 22 | 23 | 24 |
25 | {iconToElement('WechatOutlined', { fontSize: '5rem' })} 26 |
27 | 999 28 |
微信
29 |
30 |
31 | 32 | 33 | 34 |
35 | {iconToElement('QqOutlined', { fontSize: '5rem' })} 36 |
37 | 366 38 |
QQ
39 |
40 |
41 | 42 | 43 | 44 |
45 | {iconToElement('DingdingOutlined', { fontSize: '5rem' })} 46 |
47 | 666 48 |
钉钉
49 |
50 |
51 | 52 | 53 | 54 |
55 | {iconToElement('WeiboOutlined', { fontSize: '5rem' })} 56 |
57 | 689 58 |
微博
59 |
60 |
61 | 62 |
63 | 64 | 65 | 66 |
67 |
68 |
图形全屏展示
69 | 73 | {iconToElement('FullscreenOutlined', { fontSize: '2rem' })} 74 | 75 |
76 | 77 | 78 |
79 | 80 |
81 | 82 | 83 | 84 |
85 | 86 |
87 | 88 | 89 | 90 |
91 | 92 |
93 | 94 | 95 | 96 |
97 | 98 |
99 | 100 | 101 | 102 |
103 | 104 |
105 | 106 |
107 |
108 | ) 109 | } 110 | 111 | export default Index 112 | -------------------------------------------------------------------------------- /src/views/Index/bar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import echarts from 'echarts/lib/echarts' 3 | import 'echarts/lib/chart/bar' 4 | import 'echarts/lib/component/tooltip' 5 | import 'echarts/lib/component/title' 6 | import 'echarts/lib/component/legend' 7 | 8 | const Bar = () => { 9 | useEffect(() => { 10 | let myChart = echarts.init(document.getElementById('bar')) 11 | myChart.setOption({ 12 | tooltip: { 13 | trigger: 'axis', 14 | axisPointer: { 15 | // 坐标轴指示器,坐标轴触发有效 16 | type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' 17 | } 18 | }, 19 | legend: { 20 | data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎', '百度', '谷歌', '必应', '其他'] 21 | }, 22 | grid: { 23 | left: '3%', 24 | right: '4%', 25 | bottom: '3%', 26 | containLabel: true 27 | }, 28 | xAxis: [ 29 | { 30 | type: 'category', 31 | data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] 32 | } 33 | ], 34 | yAxis: [ 35 | { 36 | type: 'value' 37 | } 38 | ], 39 | series: [ 40 | { 41 | name: '直接访问', 42 | type: 'bar', 43 | data: [320, 332, 301, 334, 390, 330, 320] 44 | }, 45 | { 46 | name: '邮件营销', 47 | type: 'bar', 48 | stack: '广告', 49 | data: [120, 132, 101, 134, 90, 230, 210] 50 | }, 51 | { 52 | name: '联盟广告', 53 | type: 'bar', 54 | stack: '广告', 55 | data: [220, 182, 191, 234, 290, 330, 310] 56 | }, 57 | { 58 | name: '视频广告', 59 | type: 'bar', 60 | stack: '广告', 61 | data: [150, 232, 201, 154, 190, 330, 410] 62 | }, 63 | { 64 | name: '搜索引擎', 65 | type: 'bar', 66 | data: [862, 1018, 964, 1026, 1679, 1600, 1570], 67 | markLine: { 68 | lineStyle: { 69 | normal: { 70 | type: 'dashed' 71 | } 72 | }, 73 | data: [[{ type: 'min' }, { type: 'max' }]] 74 | } 75 | }, 76 | { 77 | name: '百度', 78 | type: 'bar', 79 | barWidth: 5, 80 | stack: '搜索引擎', 81 | data: [620, 732, 701, 734, 1090, 1130, 1120] 82 | }, 83 | { 84 | name: '谷歌', 85 | type: 'bar', 86 | stack: '搜索引擎', 87 | data: [120, 132, 101, 134, 290, 230, 220] 88 | }, 89 | { 90 | name: '必应', 91 | type: 'bar', 92 | stack: '搜索引擎', 93 | data: [60, 72, 71, 74, 190, 130, 110] 94 | }, 95 | { 96 | name: '其他', 97 | type: 'bar', 98 | stack: '搜索引擎', 99 | data: [62, 82, 91, 84, 109, 110, 120] 100 | } 101 | ] 102 | }) 103 | window.addEventListener('resize', function() { 104 | myChart.resize() 105 | }) 106 | }, []) 107 | return
108 | } 109 | 110 | export default Bar 111 | -------------------------------------------------------------------------------- /src/views/Index/index.js: -------------------------------------------------------------------------------- 1 | import Index from './Index.jsx' 2 | 3 | export default Index 4 | -------------------------------------------------------------------------------- /src/views/Index/line.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import echarts from 'echarts/lib/echarts' 3 | import 'echarts/lib/chart/line' 4 | import 'echarts/lib/component/tooltip' 5 | import 'echarts/lib/component/title' 6 | import 'echarts/lib/component/legend' 7 | 8 | const Line = () => { 9 | useEffect(() => { 10 | let myChart = echarts.init(document.getElementById('line')) 11 | myChart.setOption({ 12 | tooltip: { 13 | trigger: 'axis' 14 | }, 15 | legend: { 16 | data: ['邮件营销', '联盟广告', '视频广告'] 17 | }, 18 | grid: { 19 | left: '3%', 20 | right: '4%', 21 | bottom: '3%', 22 | containLabel: true 23 | }, 24 | toolbox: { 25 | feature: { 26 | saveAsImage: {} 27 | } 28 | }, 29 | xAxis: { 30 | type: 'category', 31 | boundaryGap: false, 32 | data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] 33 | }, 34 | yAxis: { 35 | type: 'value' 36 | }, 37 | series: [ 38 | { 39 | name: '邮件营销', 40 | type: 'line', 41 | data: [120, 132, 101, 134, 90, 230, 210] 42 | }, 43 | { 44 | name: '联盟广告', 45 | type: 'line', 46 | data: [220, 182, 191, 234, 290, 330, 310] 47 | }, 48 | { 49 | name: '视频广告', 50 | type: 'line', 51 | data: [150, 232, 201, 154, 190, 330, 410] 52 | } 53 | ] 54 | }) 55 | window.addEventListener('resize', function() { 56 | myChart.resize() 57 | }) 58 | }, []) 59 | 60 | return
61 | } 62 | 63 | export default Line 64 | -------------------------------------------------------------------------------- /src/views/Index/pictorialBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import echarts from 'echarts/lib/echarts' 3 | import 'echarts/lib/chart/pictorialBar' 4 | import 'echarts/lib/component/tooltip' 5 | import 'echarts/lib/component/title' 6 | import 'echarts/lib/component/legend' 7 | 8 | let pathSymbols = { 9 | reindeer: 10 | 'path://M-22.788,24.521c2.08-0.986,3.611-3.905,4.984-5.892 c-2.686,2.782-5.047,5.884-9.102,7.312c-0.992,0.005-0.25-2.016,0.34-2.362l1.852-0.41c0.564-0.218,0.785-0.842,0.902-1.347 c2.133-0.727,4.91-4.129,6.031-6.194c1.748-0.7,4.443-0.679,5.734-2.293c1.176-1.468,0.393-3.992,1.215-6.557 c0.24-0.754,0.574-1.581,1.008-2.293c-0.611,0.011-1.348-0.061-1.959-0.608c-1.391-1.245-0.785-2.086-1.297-3.313 c1.684,0.744,2.5,2.584,4.426,2.586C-8.46,3.012-8.255,2.901-8.04,2.824c6.031-1.952,15.182-0.165,19.498-3.937 c1.15-3.933-1.24-9.846-1.229-9.938c0.008-0.062-1.314-0.004-1.803-0.258c-1.119-0.771-6.531-3.75-0.17-3.33 c0.314-0.045,0.943,0.259,1.439,0.435c-0.289-1.694-0.92-0.144-3.311-1.946c0,0-1.1-0.855-1.764-1.98 c-0.836-1.09-2.01-2.825-2.992-4.031c-1.523-2.476,1.367,0.709,1.816,1.108c1.768,1.704,1.844,3.281,3.232,3.983 c0.195,0.203,1.453,0.164,0.926-0.468c-0.525-0.632-1.367-1.278-1.775-2.341c-0.293-0.703-1.311-2.326-1.566-2.711 c-0.256-0.384-0.959-1.718-1.67-2.351c-1.047-1.187-0.268-0.902,0.521-0.07c0.789,0.834,1.537,1.821,1.672,2.023 c0.135,0.203,1.584,2.521,1.725,2.387c0.102-0.259-0.035-0.428-0.158-0.852c-0.125-0.423-0.912-2.032-0.961-2.083 c-0.357-0.852-0.566-1.908-0.598-3.333c0.4-2.375,0.648-2.486,0.549-0.705c0.014,1.143,0.031,2.215,0.602,3.247 c0.807,1.496,1.764,4.064,1.836,4.474c0.561,3.176,2.904,1.749,2.281-0.126c-0.068-0.446-0.109-2.014-0.287-2.862 c-0.18-0.849-0.219-1.688-0.113-3.056c0.066-1.389,0.232-2.055,0.277-2.299c0.285-1.023,0.4-1.088,0.408,0.135 c-0.059,0.399-0.131,1.687-0.125,2.655c0.064,0.642-0.043,1.768,0.172,2.486c0.654,1.928-0.027,3.496,1,3.514 c1.805-0.424,2.428-1.218,2.428-2.346c-0.086-0.704-0.121-0.843-0.031-1.193c0.221-0.568,0.359-0.67,0.312-0.076 c-0.055,0.287,0.031,0.533,0.082,0.794c0.264,1.197,0.912,0.114,1.283-0.782c0.15-0.238,0.539-2.154,0.545-2.522 c-0.023-0.617,0.285-0.645,0.309,0.01c0.064,0.422-0.248,2.646-0.205,2.334c-0.338,1.24-1.105,3.402-3.379,4.712 c-0.389,0.12-1.186,1.286-3.328,2.178c0,0,1.729,0.321,3.156,0.246c1.102-0.19,3.707-0.027,4.654,0.269 c1.752,0.494,1.531-0.053,4.084,0.164c2.26-0.4,2.154,2.391-1.496,3.68c-2.549,1.405-3.107,1.475-2.293,2.984 c3.484,7.906,2.865,13.183,2.193,16.466c2.41,0.271,5.732-0.62,7.301,0.725c0.506,0.333,0.648,1.866-0.457,2.86 c-4.105,2.745-9.283,7.022-13.904,7.662c-0.977-0.194,0.156-2.025,0.803-2.247l1.898-0.03c0.596-0.101,0.936-0.669,1.152-1.139 c3.16-0.404,5.045-3.775,8.246-4.818c-4.035-0.718-9.588,3.981-12.162,1.051c-5.043,1.423-11.449,1.84-15.895,1.111 c-3.105,2.687-7.934,4.021-12.115,5.866c-3.271,3.511-5.188,8.086-9.967,10.414c-0.986,0.119-0.48-1.974,0.066-2.385l1.795-0.618 C-22.995,25.682-22.849,25.035-22.788,24.521z', 11 | plane: 12 | 'path://M1.112,32.559l2.998,1.205l-2.882,2.268l-2.215-0.012L1.112,32.559z M37.803,23.96 c0.158-0.838,0.5-1.509,0.961-1.904c-0.096-0.037-0.205-0.071-0.344-0.071c-0.777-0.005-2.068-0.009-3.047-0.009 c-0.633,0-1.217,0.066-1.754,0.18l2.199,1.804H37.803z M39.738,23.036c-0.111,0-0.377,0.325-0.537,0.924h1.076 C40.115,23.361,39.854,23.036,39.738,23.036z M39.934,39.867c-0.166,0-0.674,0.705-0.674,1.986s0.506,1.986,0.674,1.986 s0.672-0.705,0.672-1.986S40.102,39.867,39.934,39.867z M38.963,38.889c-0.098-0.038-0.209-0.07-0.348-0.073 c-0.082,0-0.174,0-0.268-0.001l-7.127,4.671c0.879,0.821,2.42,1.417,4.348,1.417c0.979,0,2.27-0.006,3.047-0.01 c0.139,0,0.25-0.034,0.348-0.072c-0.646-0.555-1.07-1.643-1.07-2.967C37.891,40.529,38.316,39.441,38.963,38.889z M32.713,23.96 l-12.37-10.116l-4.693-0.004c0,0,4,8.222,4.827,10.121H32.713z M59.311,32.374c-0.248,2.104-5.305,3.172-8.018,3.172H39.629 l-25.325,16.61L9.607,52.16c0,0,6.687-8.479,7.95-10.207c1.17-1.6,3.019-3.699,3.027-6.407h-2.138 c-5.839,0-13.816-3.789-18.472-5.583c-2.818-1.085-2.396-4.04-0.031-4.04h0.039l-3.299-11.371h3.617c0,0,4.352,5.696,5.846,7.5 c2,2.416,4.503,3.678,8.228,3.87h30.727c2.17,0,4.311,0.417,6.252,1.046c3.49,1.175,5.863,2.7,7.199,4.027 C59.145,31.584,59.352,32.025,59.311,32.374z M22.069,30.408c0-0.815-0.661-1.475-1.469-1.475c-0.812,0-1.471,0.66-1.471,1.475 s0.658,1.475,1.471,1.475C21.408,31.883,22.069,31.224,22.069,30.408z M27.06,30.408c0-0.815-0.656-1.478-1.466-1.478 c-0.812,0-1.471,0.662-1.471,1.478s0.658,1.477,1.471,1.477C26.404,31.885,27.06,31.224,27.06,30.408z M32.055,30.408 c0-0.815-0.66-1.475-1.469-1.475c-0.808,0-1.466,0.66-1.466,1.475s0.658,1.475,1.466,1.475 C31.398,31.883,32.055,31.224,32.055,30.408z M37.049,30.408c0-0.815-0.658-1.478-1.467-1.478c-0.812,0-1.469,0.662-1.469,1.478 s0.656,1.477,1.469,1.477C36.389,31.885,37.049,31.224,37.049,30.408z M42.039,30.408c0-0.815-0.656-1.478-1.465-1.478 c-0.811,0-1.469,0.662-1.469,1.478s0.658,1.477,1.469,1.477C41.383,31.885,42.039,31.224,42.039,30.408z M55.479,30.565 c-0.701-0.436-1.568-0.896-2.627-1.347c-0.613,0.289-1.551,0.476-2.73,0.476c-1.527,0-1.639,2.263,0.164,2.316 C52.389,32.074,54.627,31.373,55.479,30.565z', 13 | rocket: 14 | 'path://M-244.396,44.399c0,0,0.47-2.931-2.427-6.512c2.819-8.221,3.21-15.709,3.21-15.709s5.795,1.383,5.795,7.325C-237.818,39.679-244.396,44.399-244.396,44.399z M-260.371,40.827c0,0-3.881-12.946-3.881-18.319c0-2.416,0.262-4.566,0.669-6.517h17.684c0.411,1.952,0.675,4.104,0.675,6.519c0,5.291-3.87,18.317-3.87,18.317H-260.371z M-254.745,18.951c-1.99,0-3.603,1.676-3.603,3.744c0,2.068,1.612,3.744,3.603,3.744c1.988,0,3.602-1.676,3.602-3.744S-252.757,18.951-254.745,18.951z M-255.521,2.228v-5.098h1.402v4.969c1.603,1.213,5.941,5.069,7.901,12.5h-17.05C-261.373,7.373-257.245,3.558-255.521,2.228zM-265.07,44.399c0,0-6.577-4.721-6.577-14.896c0-5.942,5.794-7.325,5.794-7.325s0.393,7.488,3.211,15.708C-265.539,41.469-265.07,44.399-265.07,44.399z M-252.36,45.15l-1.176-1.22L-254.789,48l-1.487-4.069l-1.019,2.116l-1.488-3.826h8.067L-252.36,45.15z', 15 | train: 16 | 'path://M67.335,33.596L67.335,33.596c-0.002-1.39-1.153-3.183-3.328-4.218h-9.096v-2.07h5.371 c-4.939-2.07-11.199-4.141-14.89-4.141H19.72v12.421v5.176h38.373c4.033,0,8.457-1.035,9.142-5.176h-0.027 c0.076-0.367,0.129-0.751,0.129-1.165L67.335,33.596L67.335,33.596z M27.999,30.413h-3.105v-4.141h3.105V30.413z M35.245,30.413 h-3.104v-4.141h3.104V30.413z M42.491,30.413h-3.104v-4.141h3.104V30.413z M49.736,30.413h-3.104v-4.141h3.104V30.413z M14.544,40.764c1.143,0,2.07-0.927,2.07-2.07V35.59V25.237c0-1.145-0.928-2.07-2.07-2.07H-9.265c-1.143,0-2.068,0.926-2.068,2.07 v10.351v3.105c0,1.144,0.926,2.07,2.068,2.07H14.544L14.544,40.764z M8.333,26.272h3.105v4.141H8.333V26.272z M1.087,26.272h3.105 v4.141H1.087V26.272z M-6.159,26.272h3.105v4.141h-3.105V26.272z M-9.265,41.798h69.352v1.035H-9.265V41.798z', 17 | ship: 18 | 'path://M16.678,17.086h9.854l-2.703,5.912c5.596,2.428,11.155,5.575,16.711,8.607c3.387,1.847,6.967,3.75,10.541,5.375 v-6.16l-4.197-2.763v-5.318L33.064,12.197h-11.48L20.43,15.24h-4.533l-1.266,3.286l0.781,0.345L16.678,17.086z M49.6,31.84 l0.047,1.273L27.438,20.998l0.799-1.734L49.6,31.84z M33.031,15.1l12.889,8.82l0.027,0.769L32.551,16.1L33.031,15.1z M22.377,14.045 h9.846l-1.539,3.365l-2.287-1.498h1.371l0.721-1.352h-2.023l-0.553,1.037l-0.541-0.357h-0.34l0.359-0.684h-2.025l-0.361,0.684 h-3.473L22.377,14.045z M23.695,20.678l-0.004,0.004h0.004V20.678z M24.828,18.199h-2.031l-0.719,1.358h2.029L24.828,18.199z M40.385,34.227c-12.85-7.009-25.729-14.667-38.971-12.527c1.26,8.809,9.08,16.201,8.213,24.328 c-0.553,4.062-3.111,0.828-3.303,7.137c15.799,0,32.379,0,48.166,0l0.066-4.195l1.477-7.23 C50.842,39.812,45.393,36.961,40.385,34.227z M13.99,35.954c-1.213,0-2.195-1.353-2.195-3.035c0-1.665,0.98-3.017,2.195-3.017 c1.219,0,2.195,1.352,2.195,3.017C16.186,34.604,15.213,35.954,13.99,35.954z M23.691,20.682h-2.02l-0.588,1.351h2.023 L23.691,20.682z M19.697,18.199l-0.721,1.358h2.025l0.727-1.358H19.697z', 19 | car: 20 | 'path://M49.592,40.883c-0.053,0.354-0.139,0.697-0.268,0.963c-0.232,0.475-0.455,0.519-1.334,0.475 c-1.135-0.053-2.764,0-4.484,0.068c0,0.476,0.018,0.697,0.018,0.697c0.111,1.299,0.697,1.342,0.931,1.342h3.7 c0.326,0,0.628,0,0.861-0.154c0.301-0.196,0.43-0.772,0.543-1.78c0.017-0.146,0.025-0.336,0.033-0.56v-0.01 c0-0.068,0.008-0.154,0.008-0.25V41.58l0,0C49.6,41.348,49.6,41.09,49.592,40.883L49.592,40.883z M6.057,40.883 c0.053,0.354,0.137,0.697,0.268,0.963c0.23,0.475,0.455,0.519,1.334,0.475c1.137-0.053,2.762,0,4.484,0.068 c0,0.476-0.018,0.697-0.018,0.697c-0.111,1.299-0.697,1.342-0.93,1.342h-3.7c-0.328,0-0.602,0-0.861-0.154 c-0.309-0.18-0.43-0.772-0.541-1.78c-0.018-0.146-0.027-0.336-0.035-0.56v-0.01c0-0.068-0.008-0.154-0.008-0.25V41.58l0,0 C6.057,41.348,6.057,41.09,6.057,40.883L6.057,40.883z M49.867,32.766c0-2.642-0.344-5.224-0.482-5.507 c-0.104-0.207-0.766-0.749-2.271-1.773c-1.522-1.042-1.487-0.887-1.766-1.566c0.25-0.078,0.492-0.224,0.639-0.241 c0.326-0.034,0.345,0.274,1.023,0.274c0.68,0,2.152-0.18,2.453-0.48c0.301-0.303,0.396-0.405,0.396-0.672 c0-0.268-0.156-0.818-0.447-1.146c-0.293-0.327-1.541-0.49-2.273-0.585c-0.729-0.095-0.834,0-1.022,0.121 c-0.304,0.189-0.32,1.919-0.32,1.919l-0.713,0.018c-0.465-1.146-1.11-3.452-2.117-5.269c-1.103-1.979-2.256-2.599-2.737-2.754 c-0.474-0.146-0.904-0.249-4.131-0.576c-3.298-0.344-5.922-0.388-8.262-0.388c-2.342,0-4.967,0.052-8.264,0.388 c-3.229,0.336-3.66,0.43-4.133,0.576s-1.633,0.775-2.736,2.754c-1.006,1.816-1.652,4.123-2.117,5.269L9.87,23.109 c0,0-0.008-1.729-0.318-1.919c-0.189-0.121-0.293-0.225-1.023-0.121c-0.732,0.104-1.98,0.258-2.273,0.585 c-0.293,0.327-0.447,0.878-0.447,1.146c0,0.267,0.094,0.379,0.396,0.672c0.301,0.301,1.773,0.48,2.453,0.48 c0.68,0,0.697-0.309,1.023-0.274c0.146,0.018,0.396,0.163,0.637,0.241c-0.283,0.68-0.24,0.524-1.764,1.566 c-1.506,1.033-2.178,1.566-2.271,1.773c-0.139,0.283-0.482,2.865-0.482,5.508s0.189,5.02,0.189,5.86c0,0.354,0,0.976,0.076,1.565 c0.053,0.354,0.129,0.697,0.268,0.966c0.232,0.473,0.447,0.516,1.334,0.473c1.137-0.051,2.779,0,4.477,0.07 c1.135,0.043,2.297,0.086,3.33,0.11c2.582,0.051,1.826-0.379,2.928-0.36c1.102,0.016,5.447,0.196,9.424,0.196 c3.976,0,8.332-0.182,9.423-0.196c1.102-0.019,0.346,0.411,2.926,0.36c1.033-0.018,2.195-0.067,3.332-0.11 c1.695-0.062,3.348-0.121,4.477-0.07c0.886,0.043,1.103,0,1.332-0.473c0.132-0.269,0.218-0.611,0.269-0.966 c0.086-0.592,0.078-1.213,0.078-1.565C49.678,37.793,49.867,35.408,49.867,32.766L49.867,32.766z M13.219,19.735 c0.412-0.964,1.652-2.9,2.256-3.244c0.145-0.087,1.426-0.491,4.637-0.706c2.953-0.198,6.217-0.276,7.73-0.276 c1.513,0,4.777,0.078,7.729,0.276c3.201,0.215,4.502,0.611,4.639,0.706c0.775,0.533,1.842,2.28,2.256,3.244 c0.412,0.965,0.965,2.858,0.861,3.116c-0.104,0.258,0.104,0.388-1.291,0.275c-1.387-0.103-10.088-0.216-14.185-0.216 c-4.088,0-12.789,0.113-14.184,0.216c-1.395,0.104-1.188-0.018-1.291-0.275C12.254,22.593,12.805,20.708,13.219,19.735 L13.219,19.735z M16.385,30.511c-0.619,0.155-0.988,0.491-1.764,0.482c-0.775,0-2.867-0.353-3.314-0.371 c-0.447-0.017-0.842,0.302-1.076,0.362c-0.23,0.06-0.688-0.104-1.377-0.318c-0.688-0.216-1.092-0.155-1.316-1.094 c-0.232-0.93,0-2.264,0-2.264c1.488-0.068,2.928,0.069,5.621,0.826c2.693,0.758,4.191,2.213,4.191,2.213 S17.004,30.357,16.385,30.511L16.385,30.511z M36.629,37.293c-1.23,0.164-6.386,0.207-8.794,0.207c-2.412,0-7.566-0.051-8.799-0.207 c-1.256-0.164-2.891-1.67-1.764-2.865c1.523-1.627,1.24-1.576,4.701-2.023C24.967,32.018,27.239,32,27.834,32 c0.584,0,2.865,0.025,5.859,0.404c3.461,0.447,3.178,0.396,4.699,2.022C39.521,35.623,37.887,37.129,36.629,37.293L36.629,37.293z M48.129,29.582c-0.232,0.93-0.629,0.878-1.318,1.093c-0.688,0.216-1.145,0.371-1.377,0.319c-0.231-0.053-0.627-0.371-1.074-0.361 c-0.448,0.018-2.539,0.37-3.313,0.37c-0.772,0-1.146-0.328-1.764-0.481c-0.621-0.154-0.966-0.154-0.966-0.154 s1.49-1.464,4.191-2.213c2.693-0.758,4.131-0.895,5.621-0.826C48.129,27.309,48.361,28.643,48.129,29.582L48.129,29.582z', 21 | run: 22 | 'path://M13.676,32.955c0.919-0.031,1.843-0.008,2.767-0.008v0.007c0.827,0,1.659,0.041,2.486-0.019 c0.417-0.028,1.118,0.325,1.14-0.545c0.014-0.637-0.156-1.279-0.873-1.367c-1.919-0.241-3.858-0.233-5.774,0.019 c-0.465,0.062-0.998,0.442-0.832,1.069C12.715,32.602,13.045,32.977,13.676,32.955z M14.108,29.013 c1.47-0.007,2.96-0.122,4.414,0.035c1.792,0.192,3.1-0.412,4.273-2.105c-3.044,0-5.882,0.014-8.719-0.01 c-0.768-0.005-1.495,0.118-1.461,1C12.642,28.731,13.329,29.014,14.108,29.013z M23.678,36.593c-0.666-0.69-1.258-1.497-2.483-1.448 c-2.341,0.095-4.689,0.051-7.035,0.012c-0.834-0.014-1.599,0.177-1.569,1.066c0.031,0.854,0.812,1.062,1.636,1.043 c1.425-0.033,2.852-0.01,4.278-0.01v-0.01c1.562,0,3.126,0.008,4.691-0.005C23.614,37.239,24.233,37.174,23.678,36.593z M32.234,42.292h-0.002c-1.075,0.793-2.589,0.345-3.821,1.048c-0.359,0.193-0.663,0.465-0.899,0.799 c-1.068,1.623-2.052,3.301-3.117,4.928c-0.625,0.961-0.386,1.805,0.409,2.395c0.844,0.628,1.874,0.617,2.548-0.299 c1.912-2.573,3.761-5.197,5.621-7.814C33.484,42.619,33.032,42.387,32.234,42.292z M43.527,28.401 c-0.688-1.575-2.012-0.831-3.121-0.895c-1.047-0.058-2.119,1.128-3.002,0.345c-0.768-0.677-1.213-1.804-1.562-2.813 c-0.45-1.305-1.495-2.225-2.329-3.583c2.953,1.139,4.729,0.077,5.592-1.322c0.99-1.61,0.718-3.725-0.627-4.967 c-1.362-1.255-3.414-1.445-4.927-0.452c-1.933,1.268-2.206,2.893-0.899,6.11c-2.098-0.659-3.835-1.654-5.682-2.383 c-0.735-0.291-1.437-1.017-2.293-0.666c-2.263,0.927-4.522,1.885-6.723,2.95c-1.357,0.658-1.649,1.593-1.076,2.638 c0.462,0.851,1.643,1.126,2.806,0.617c0.993-0.433,1.994-0.857,2.951-1.374c1.599-0.86,3.044-0.873,4.604,0.214 c1.017,0.707,0.873,1.137,0.123,1.849c-1.701,1.615-3.516,3.12-4.933,5.006c-1.042,1.388-0.993,2.817,0.255,4.011 c1.538,1.471,3.148,2.869,4.708,4.315c0.485,0.444,0.907,0.896-0.227,1.104c-1.523,0.285-3.021,0.694-4.538,1.006 c-1.109,0.225-2.02,1.259-1.83,2.16c0.223,1.07,1.548,1.756,2.687,1.487c3.003-0.712,6.008-1.413,9.032-2.044 c1.549-0.324,2.273-1.869,1.344-3.115c-0.868-1.156-1.801-2.267-2.639-3.445c-1.964-2.762-1.95-2.771,0.528-5.189 c1.394-1.357,1.379-1.351,2.437,0.417c0.461,0.769,0.854,1.703,1.99,1.613c2.238-0.181,4.407-0.755,6.564-1.331 C43.557,30.447,43.88,29.206,43.527,28.401z', 23 | walk: 24 | 'path://M29.902,23.275c1.86,0,3.368-1.506,3.368-3.365c0-1.859-1.508-3.365-3.368-3.365 c-1.857,0-3.365,1.506-3.365,3.365C26.537,21.769,28.045,23.275,29.902,23.275z M36.867,30.74c-1.666-0.467-3.799-1.6-4.732-4.199 c-0.932-2.6-3.131-2.998-4.797-2.998s-7.098,3.894-7.098,3.894c-1.133,1.001-2.1,6.502-0.967,6.769 c1.133,0.269,1.266-1.533,1.934-3.599c0.666-2.065,3.797-3.466,3.797-3.466s0.201,2.467-0.398,3.866 c-0.599,1.399-1.133,2.866-1.467,6.198s-1.6,3.665-3.799,6.266c-2.199,2.598-0.6,3.797,0.398,3.664 c1.002-0.133,5.865-5.598,6.398-6.998c0.533-1.397,0.668-3.732,0.668-3.732s0,0,2.199,1.867c2.199,1.865,2.332,4.6,2.998,7.73 s2.332,0.934,2.332-0.467c0-1.401,0.269-5.465-1-7.064c-1.265-1.6-3.73-3.465-3.73-5.265s1.199-3.732,1.199-3.732 c0.332,1.667,3.335,3.065,5.599,3.399C38.668,33.206,38.533,31.207,36.867,30.74z' 25 | } 26 | 27 | const PictorialBar = () => { 28 | useEffect(() => { 29 | let myChart = echarts.init(document.getElementById('pictorialBar')) 30 | myChart.setOption({ 31 | tooltip: { 32 | trigger: 'axis', 33 | axisPointer: { 34 | type: 'none' 35 | }, 36 | formatter: function(params) { 37 | return params[0].name + ': ' + params[0].value 38 | } 39 | }, 40 | xAxis: { 41 | data: ['驯鹿', '火箭', '飞机', '高铁', '轮船', '汽车', '跑步', '步行'], 42 | axisTick: { show: false }, 43 | axisLine: { show: false }, 44 | axisLabel: { 45 | textStyle: { 46 | color: '#e54035' 47 | } 48 | } 49 | }, 50 | yAxis: { 51 | splitLine: { show: false }, 52 | axisTick: { show: false }, 53 | axisLine: { show: false }, 54 | axisLabel: { show: false } 55 | }, 56 | color: ['#e54035'], 57 | series: [ 58 | { 59 | name: 'hill', 60 | type: 'pictorialBar', 61 | barCategoryGap: '-130%', 62 | // symbol: 'path://M0,10 L10,10 L5,0 L0,10 z', 63 | symbol: 'path://M0,10 L10,10 C5.5,10 5.5,5 5,0 C4.5,5 4.5,10 0,10 z', 64 | itemStyle: { 65 | normal: { 66 | opacity: 0.5 67 | }, 68 | emphasis: { 69 | opacity: 1 70 | } 71 | }, 72 | data: [123, 60, 25, 18, 12, 9, 2, 1], 73 | z: 10 74 | }, 75 | { 76 | name: 'glyph', 77 | type: 'pictorialBar', 78 | barGap: '-100%', 79 | symbolPosition: 'end', 80 | symbolSize: 50, 81 | symbolOffset: [0, '-120%'], 82 | data: [ 83 | { 84 | value: 123, 85 | symbol: pathSymbols.reindeer, 86 | symbolSize: [60, 60] 87 | }, 88 | { 89 | value: 60, 90 | symbol: pathSymbols.rocket, 91 | symbolSize: [50, 60] 92 | }, 93 | { 94 | value: 25, 95 | symbol: pathSymbols.plane, 96 | symbolSize: [65, 35] 97 | }, 98 | { 99 | value: 18, 100 | symbol: pathSymbols.train, 101 | symbolSize: [50, 30] 102 | }, 103 | { 104 | value: 12, 105 | symbol: pathSymbols.ship, 106 | symbolSize: [50, 35] 107 | }, 108 | { 109 | value: 9, 110 | symbol: pathSymbols.car, 111 | symbolSize: [40, 30] 112 | }, 113 | { 114 | value: 2, 115 | symbol: pathSymbols.run, 116 | symbolSize: [40, 50] 117 | }, 118 | { 119 | value: 1, 120 | symbol: pathSymbols.walk, 121 | symbolSize: [40, 50] 122 | } 123 | ] 124 | } 125 | ] 126 | }) 127 | window.addEventListener('resize', function() { 128 | myChart.resize() 129 | }) 130 | }, []) 131 | 132 | return
133 | } 134 | 135 | export default PictorialBar 136 | -------------------------------------------------------------------------------- /src/views/Index/pie.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import echarts from 'echarts/lib/echarts' 3 | import 'echarts/lib/chart/pie' 4 | import 'echarts/lib/component/tooltip' 5 | import 'echarts/lib/component/title' 6 | import 'echarts/lib/component/legend' 7 | 8 | const Pie = () => { 9 | useEffect(() => { 10 | let myChart = echarts.init(document.getElementById('pie')) 11 | myChart.setOption({ 12 | tooltip: { 13 | trigger: 'item', 14 | formatter: '{a}
{b} : {c} ({d}%)' 15 | }, 16 | legend: { 17 | orient: 'vertical', 18 | left: 'left', 19 | data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎'] 20 | }, 21 | series: [ 22 | { 23 | name: '访问来源', 24 | type: 'pie', 25 | radius: '55%', 26 | center: ['50%', '60%'], 27 | data: [ 28 | { value: 335, name: '直接访问' }, 29 | { value: 310, name: '邮件营销' }, 30 | { value: 234, name: '联盟广告' }, 31 | { value: 135, name: '视频广告' }, 32 | { value: 1548, name: '搜索引擎' } 33 | ], 34 | itemStyle: { 35 | emphasis: { 36 | shadowBlur: 10, 37 | shadowOffsetX: 0, 38 | shadowColor: 'rgba(0, 0, 0, 0.5)' 39 | } 40 | } 41 | } 42 | ] 43 | }) 44 | window.addEventListener('resize', function() { 45 | myChart.resize() 46 | }) 47 | }, []) 48 | 49 | return
50 | } 51 | 52 | export default Pie 53 | -------------------------------------------------------------------------------- /src/views/Index/scatter.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import echarts from 'echarts/lib/echarts' 3 | import 'echarts/lib/chart/scatter' 4 | import 'echarts/lib/component/tooltip' 5 | import 'echarts/lib/component/title' 6 | import 'echarts/lib/component/legend' 7 | 8 | let data = [ 9 | [ 10 | [28604, 77, 17096869, 'Australia', 1990], 11 | [31163, 77.4, 27662440, 'Canada', 1990], 12 | [1516, 68, 1154605773, 'China', 1990], 13 | [13670, 74.7, 10582082, 'Cuba', 1990], 14 | [28599, 75, 4986705, 'Finland', 1990], 15 | [29476, 77.1, 56943299, 'France', 1990], 16 | [31476, 75.4, 78958237, 'Germany', 1990], 17 | [28666, 78.1, 254830, 'Iceland', 1990], 18 | [1777, 57.7, 870601776, 'India', 1990], 19 | [29550, 79.1, 122249285, 'Japan', 1990], 20 | [2076, 67.9, 20194354, 'North Korea', 1990], 21 | [12087, 72, 42972254, 'South Korea', 1990], 22 | [24021, 75.4, 3397534, 'New Zealand', 1990], 23 | [43296, 76.8, 4240375, 'Norway', 1990], 24 | [10088, 70.8, 38195258, 'Poland', 1990], 25 | [19349, 69.6, 147568552, 'Russia', 1990], 26 | [10670, 67.3, 53994605, 'Turkey', 1990], 27 | [26424, 75.7, 57110117, 'United Kingdom', 1990], 28 | [37062, 75.4, 252847810, 'United States', 1990] 29 | ], 30 | [ 31 | [44056, 81.8, 23968973, 'Australia', 2015], 32 | [43294, 81.7, 35939927, 'Canada', 2015], 33 | [13334, 76.9, 1376048943, 'China', 2015], 34 | [21291, 78.5, 11389562, 'Cuba', 2015], 35 | [38923, 80.8, 5503457, 'Finland', 2015], 36 | [37599, 81.9, 64395345, 'France', 2015], 37 | [44053, 81.1, 80688545, 'Germany', 2015], 38 | [42182, 82.8, 329425, 'Iceland', 2015], 39 | [5903, 66.8, 1311050527, 'India', 2015], 40 | [36162, 83.5, 126573481, 'Japan', 2015], 41 | [1390, 71.4, 25155317, 'North Korea', 2015], 42 | [34644, 80.7, 50293439, 'South Korea', 2015], 43 | [34186, 80.6, 4528526, 'New Zealand', 2015], 44 | [64304, 81.6, 5210967, 'Norway', 2015], 45 | [24787, 77.3, 38611794, 'Poland', 2015], 46 | [23038, 73.13, 143456918, 'Russia', 2015], 47 | [19360, 76.5, 78665830, 'Turkey', 2015], 48 | [38225, 81.4, 64715810, 'United Kingdom', 2015], 49 | [53354, 79.1, 321773631, 'United States', 2015] 50 | ] 51 | ] 52 | 53 | const Scatter = () => { 54 | useEffect(() => { 55 | let myChart = echarts.init(document.getElementById('scatter')) 56 | myChart.setOption({ 57 | legend: { 58 | right: 10, 59 | data: ['1990', '2015'] 60 | }, 61 | xAxis: { 62 | splitLine: { 63 | lineStyle: { 64 | type: 'dashed' 65 | } 66 | } 67 | }, 68 | yAxis: { 69 | splitLine: { 70 | lineStyle: { 71 | type: 'dashed' 72 | } 73 | }, 74 | scale: true 75 | }, 76 | series: [ 77 | { 78 | name: '1990', 79 | data: data[0], 80 | type: 'scatter', 81 | symbolSize: function(data) { 82 | return Math.sqrt(data[2]) / 5e2 83 | }, 84 | label: { 85 | emphasis: { 86 | show: true, 87 | formatter: function(param) { 88 | return param.data[3] 89 | }, 90 | position: 'top' 91 | } 92 | }, 93 | itemStyle: { 94 | normal: { 95 | shadowBlur: 10, 96 | shadowColor: 'rgba(120, 36, 50, 0.5)', 97 | shadowOffsetY: 5, 98 | color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [ 99 | { 100 | offset: 0, 101 | color: 'rgb(251, 118, 123)' 102 | }, 103 | { 104 | offset: 1, 105 | color: 'rgb(204, 46, 72)' 106 | } 107 | ]) 108 | } 109 | } 110 | }, 111 | { 112 | name: '2015', 113 | data: data[1], 114 | type: 'scatter', 115 | symbolSize: function(data) { 116 | return Math.sqrt(data[2]) / 5e2 117 | }, 118 | label: { 119 | emphasis: { 120 | show: true, 121 | formatter: function(param) { 122 | return param.data[3] 123 | }, 124 | position: 'top' 125 | } 126 | }, 127 | itemStyle: { 128 | normal: { 129 | shadowBlur: 10, 130 | shadowColor: 'rgba(25, 100, 150, 0.5)', 131 | shadowOffsetY: 5, 132 | color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [ 133 | { 134 | offset: 0, 135 | color: 'rgb(129, 227, 238)' 136 | }, 137 | { 138 | offset: 1, 139 | color: 'rgb(25, 183, 207)' 140 | } 141 | ]) 142 | } 143 | } 144 | } 145 | ] 146 | }) 147 | window.addEventListener('resize', function() { 148 | myChart.resize() 149 | }) 150 | }, []) 151 | 152 | return
153 | } 154 | 155 | export default Scatter 156 | -------------------------------------------------------------------------------- /src/views/Login/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { Layout, Input, Form, Button, Divider, message, notification } from 'antd' 3 | import { withRouter } from 'react-router-dom' 4 | import { 5 | UserOutlined, 6 | UnlockOutlined 7 | } from '@ant-design/icons'; 8 | import '@/style/view-style/login.scss' 9 | 10 | 11 | const Login = props => { 12 | const [loading, setLoading] = useState(false) 13 | 14 | const handleSubmit = values => { 15 | // 这里可以做权限校验 模拟接口返回用户权限标识 16 | switch (values.username) { 17 | case 'admin': 18 | values.auth = 0 19 | break 20 | default: 21 | values.auth = 1 22 | } 23 | 24 | localStorage.setItem('user', JSON.stringify(values)) 25 | setLoading(true) 26 | setTimeout(() => { 27 | message.success('登录成功!') 28 | props.history.push('/') 29 | }, 2000) 30 | } 31 | 32 | useEffect(() => { 33 | notification.open({ 34 | message: '欢迎使用后台管理平台', 35 | duration: null, 36 | description: '账号 admin(管理员) 其他(游客) 密码随意' 37 | }) 38 | return () => { 39 | notification.destroy() 40 | } 41 | }, []) 42 | 43 | return ( 44 | 45 |
46 |
47 |

后台管理系统

48 | 49 |
50 | 51 | } 53 | placeholder='用户名' 54 | /> 55 | 56 | 57 | } 59 | type='password' 60 | placeholder='密码' 61 | /> 62 | 63 | 64 | 67 | 68 |
69 |
70 |
71 |
72 | ) 73 | } 74 | 75 | export default withRouter((Login)) 76 | -------------------------------------------------------------------------------- /src/views/Login/index.js: -------------------------------------------------------------------------------- 1 | import Login from './Login.jsx' 2 | 3 | export default Login 4 | -------------------------------------------------------------------------------- /src/views/NavView/Dropdown/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Layout, Divider, Menu, Dropdown, Row, Col, message, Button } from 'antd' 3 | import { iconToElement } from '@/utils/util' 4 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 5 | import '@/style/view-style/dropdown.scss' 6 | 7 | const { SubMenu } = Menu 8 | 9 | const menuOnClick = ({ key }) => { 10 | message.info(`Click on item ${key}`) 11 | } 12 | 13 | const items = [ 14 | { 15 | label: '1st menu item', 16 | key: '0', 17 | }, 18 | { 19 | label: '2st menu item', 20 | key: '1', 21 | }, 22 | { 23 | label: '3rd menu item (disabled)', 24 | key: '2', 25 | disabled: true, 26 | }, 27 | { 28 | label: 'sub menu', 29 | key: '3', 30 | children: [ 31 | { 32 | label: '4rd menu item', 33 | key: '4' 34 | }, 35 | { 36 | label: '5rd menu item', 37 | key: '5' 38 | } 39 | ], 40 | }, 41 | ]; 42 | 43 | const menu = ( 44 | 48 | ) 49 | 50 | function handleButtonClick (e) { 51 | message.info('Click on left button.') 52 | console.log('click left button', e) 53 | } 54 | 55 | const DropdownView = () => ( 56 | 57 |
58 | 59 |
60 |
61 |

何时使用

62 | 63 |

64 | 当页面上的操作命令过多时,用此组件可以收纳操作元素。点击或移入触点,会出现一个下拉菜单。可在列表中进行选择,并执行相应的命令。 65 |

66 |
67 | 68 | 69 |
70 | 71 | 75 | 76 |
77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 | 99 | 100 |
101 | 102 | 106 | 107 |
108 | 109 | 110 |
111 |
112 | 113 | Dropdown 114 | 115 | 116 | Dropdown 117 | 118 | 119 | Dropdown 120 | 121 | 122 | 126 | 127 |
128 |
129 | 130 |
131 |
132 | ) 133 | 134 | export default DropdownView 135 | -------------------------------------------------------------------------------- /src/views/NavView/Dropdown/index.js: -------------------------------------------------------------------------------- 1 | import DropdownView from './Dropdown' 2 | 3 | export default DropdownView 4 | -------------------------------------------------------------------------------- /src/views/NavView/Menu/Menu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Layout, Divider, Row, Col, Menu, Button, Switch } from 'antd' 3 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 4 | import { 5 | AppstoreOutlined, 6 | MailOutlined, 7 | SettingOutlined, 8 | ContainerOutlined, 9 | DesktopOutlined, 10 | MenuFoldOutlined, 11 | MenuUnfoldOutlined, 12 | PieChartOutlined, 13 | CalendarOutlined, 14 | LinkOutlined 15 | } from '@ant-design/icons'; 16 | 17 | const { SubMenu } = Menu 18 | 19 | const MenuView = () => { 20 | const [current, setCurrent] = useState('mail') 21 | const [collapsed, setCollapsed] = useState(false) 22 | const [openKeys, setOpenKeys] = useState(['sub1']) 23 | const [mode, setMode] = useState('inline') 24 | const [theme, setTheme] = useState('light') 25 | 26 | const rootSubmenuKeys = ['sub1', 'sub2', 'sub4'] 27 | 28 | const handleClick = e => { 29 | console.log('click ', e) 30 | setCurrent(e.key) 31 | } 32 | 33 | const onOpenChange = keys => { 34 | const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1); 35 | 36 | if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) { 37 | setOpenKeys(keys); 38 | } else { 39 | setOpenKeys(latestOpenKey ? [latestOpenKey] : []); 40 | } 41 | } 42 | 43 | const itemsTop = [ 44 | { 45 | label: 'Navigation One', 46 | key: 'mail', 47 | icon: , 48 | }, 49 | { 50 | label: 'Navigation Two', 51 | key: 'app', 52 | icon: , 53 | disabled: true, 54 | }, 55 | { 56 | label: 'Navigation Three - Submenu', 57 | key: 'SubMenu', 58 | icon: , 59 | children: [ 60 | { 61 | type: 'group', 62 | label: 'Item 1', 63 | children: [ 64 | { 65 | label: 'Option 1', 66 | key: 'setting:1', 67 | }, 68 | { 69 | label: 'Option 2', 70 | key: 'setting:2', 71 | }, 72 | ], 73 | }, 74 | { 75 | type: 'group', 76 | label: 'Item 2', 77 | children: [ 78 | { 79 | label: 'Option 3', 80 | key: 'setting:3', 81 | }, 82 | { 83 | label: 'Option 4', 84 | key: 'setting:4', 85 | }, 86 | ], 87 | }, 88 | ], 89 | }, 90 | { 91 | label: ( 92 | 93 | Navigation Four - Link 94 | 95 | ), 96 | key: 'alipay', 97 | }, 98 | ]; 99 | 100 | function getItem (label, key, icon, children, type) { 101 | return { 102 | key, 103 | icon, 104 | children, 105 | label, 106 | type, 107 | }; 108 | } 109 | 110 | // 内嵌菜单 111 | const itemsEmbedded = [ 112 | getItem('Navigation One', 'sub1', , [ 113 | getItem('Item 1', 'g1', null, [getItem('Option 1', '1'), getItem('Option 2', '2')], 'group'), 114 | getItem('Item 2', 'g2', null, [getItem('Option 3', '3'), getItem('Option 4', '4')], 'group'), 115 | ]), 116 | getItem('Navigation Two', 'sub2', , [ 117 | getItem('Option 5', '5'), 118 | getItem('Option 6', '6'), 119 | getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]), 120 | ]), 121 | getItem('Navigation Three', 'sub4', , [ 122 | getItem('Option 9', '9'), 123 | getItem('Option 10', '10'), 124 | getItem('Option 11', '11'), 125 | getItem('Option 12', '12'), 126 | ]), 127 | ]; 128 | 129 | // 可收缩菜单 130 | const itemsShrink = [ 131 | getItem('Option 1', '1', ), 132 | getItem('Option 2', '2', ), 133 | getItem('Option 3', '3', ), 134 | getItem('Navigation One', 'sub1', , [ 135 | getItem('Option 5', '5'), 136 | getItem('Option 6', '6'), 137 | getItem('Option 7', '7'), 138 | getItem('Option 8', '8'), 139 | ]), 140 | getItem('Navigation Two', 'sub2', , [ 141 | getItem('Option 9', '9'), 142 | getItem('Option 10', '10'), 143 | getItem('Submenu', 'sub3', null, [getItem('Option 11', '11'), getItem('Option 12', '12')]), 144 | ]), 145 | ]; 146 | 147 | // 只展开当前父级菜单 148 | const itemsFirstLevel = [ 149 | getItem('Navigation One', 'sub1', , [ 150 | getItem('Option 1', '1'), 151 | getItem('Option 2', '2'), 152 | getItem('Option 3', '3'), 153 | getItem('Option 4', '4'), 154 | ]), 155 | getItem('Navigation Two', 'sub2', , [ 156 | getItem('Option 5', '5'), 157 | getItem('Option 6', '6'), 158 | getItem('Submenu', 'sub3', null, [getItem('Option 7', '7'), getItem('Option 8', '8')]), 159 | ]), 160 | getItem('Navigation Three', 'sub4', , [ 161 | getItem('Option 9', '9'), 162 | getItem('Option 10', '10'), 163 | getItem('Option 11', '11'), 164 | getItem('Option 12', '12'), 165 | ]), 166 | ]; 167 | 168 | // 可切换动态菜单 169 | const itemsDynamicState = [ 170 | getItem('Navigation One', '1', ), 171 | getItem('Navigation Two', '2', ), 172 | getItem('Navigation Two', 'sub1', , [ 173 | getItem('Option 3', '3'), 174 | getItem('Option 4', '4'), 175 | getItem('Submenu', 'sub1-2', null, [getItem('Option 5', '5'), getItem('Option 6', '6')]), 176 | ]), 177 | getItem('Navigation Three', 'sub2', , [ 178 | getItem('Option 7', '7'), 179 | getItem('Option 8', '8'), 180 | getItem('Option 9', '9'), 181 | getItem('Option 10', '10'), 182 | ]), 183 | getItem( 184 | 185 | Ant Design 186 | , 187 | 'link', 188 | , 189 | ), 190 | ]; 191 | 192 | return ( 193 | 194 |
195 | 196 |
197 |
198 |

何时使用

199 | 200 |

201 | 导航菜单是一个网站的灵魂,用户依赖导航在各个页面中进行跳转。一般分为顶部导航和侧边导航,顶部导航提供全局性的类目和功能,侧边导航提供多级结构来收纳和排列网站架构。 202 |

203 |
204 | 205 | 206 |
207 | 顶部导航 208 | 209 |
210 | 211 | 212 | 213 |
214 | 内嵌菜单 215 | 225 |
226 | 227 | 228 | 229 |
230 | 只展开当前父级菜单 231 | 240 |
241 | 242 | 243 | 244 |
245 | 可收缩菜单 246 |
251 | 260 | 268 |
269 |
270 | 271 | 272 | 273 |
274 | 可切换动态菜单 275 |
276 | setMode(() => (val ? 'vertical' : 'inline'))} /> Change Mode 277 | 278 | setTheme(() => (val ? 'dark' : 'light'))} /> Change Theme 279 |
280 |
281 | 291 |
292 |
293 | 294 |
295 |
296 | ) 297 | } 298 | 299 | export default MenuView 300 | -------------------------------------------------------------------------------- /src/views/NavView/Menu/index.js: -------------------------------------------------------------------------------- 1 | import MenuView from './Menu.jsx' 2 | 3 | export default MenuView 4 | -------------------------------------------------------------------------------- /src/views/NavView/Step/Step.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Layout, Divider, Row, Col, Steps, Button, message } from 'antd' 3 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 4 | import '@/style/view-style/step.scss' 5 | import { iconToElement } from '@/utils/util' 6 | 7 | const { Step } = Steps 8 | 9 | const steps = [ 10 | { 11 | title: 'First', 12 | content: 'First-content' 13 | }, 14 | { 15 | title: 'Second', 16 | content: 'Second-content' 17 | }, 18 | { 19 | title: 'Last', 20 | content: 'Last-content' 21 | } 22 | ] 23 | 24 | const StepView = () => { 25 | const [current, setCurrent] = useState(0) 26 | 27 | return ( 28 | 29 |
30 | 31 |
32 |
33 |

何时使用

34 | 35 |

当任务复杂或者存在先后关系时,将其分解成一系列步骤,从而简化任务。

36 |
37 | 38 | 39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 |
57 | 58 |
59 | 60 | 61 | 62 |
63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | 72 |
73 | 74 | 75 | 76 | 77 | 78 |
79 | 80 | 81 | 82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 |
90 | 91 | 92 | 93 |
94 |
95 | setCurrent(current)}> 96 | {steps.map(item => ( 97 | 98 | ))} 99 | 100 |
{steps[current].content}
101 |
102 | {current < steps.length - 1 && ( 103 | 106 | )} 107 | {current === steps.length - 1 && ( 108 | 111 | )} 112 | {current > 0 && ( 113 | 116 | )} 117 |
118 |
119 |
120 | 121 | 122 | 123 |
124 | 125 | 126 | 127 | 128 | 129 |
130 | 131 |
132 |
133 | ) 134 | } 135 | 136 | export default StepView 137 | -------------------------------------------------------------------------------- /src/views/NavView/Step/index.js: -------------------------------------------------------------------------------- 1 | import StepView from './Step.jsx' 2 | 3 | export default StepView 4 | -------------------------------------------------------------------------------- /src/views/Others/404/404.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import img404 from '@/assets/images/404.jpg' 3 | 4 | const View404 = () => ( 5 |
6 | 7 |
8 | ) 9 | 10 | export default View404 11 | -------------------------------------------------------------------------------- /src/views/Others/404/index.js: -------------------------------------------------------------------------------- 1 | import View404 from './404.jsx' 2 | 3 | export default View404 4 | -------------------------------------------------------------------------------- /src/views/Others/500/500.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import img500 from '@/assets/images/500.png' 3 | 4 | const View500 = () => ( 5 |
6 | 7 |
8 | ) 9 | 10 | export default View500 11 | -------------------------------------------------------------------------------- /src/views/Others/500/index.js: -------------------------------------------------------------------------------- 1 | import View500 from './500.jsx' 2 | 3 | export default View500 4 | -------------------------------------------------------------------------------- /src/views/Others/Animation/Animation.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Layout, Divider, Row, Col, Button, Tabs } from 'antd' 3 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 4 | import '@/style/view-style/animation.scss' 5 | 6 | const { TabPane } = Tabs 7 | const typeIn = [ 8 | 'bounceInDown', 9 | 'bounceInLeft', 10 | 'bounceInRight', 11 | 'bounceInUp', 12 | 'fadeIn', 13 | 'fadeInDown', 14 | 'fadeInLeft', 15 | 'fadeInLeftBig', 16 | 'fadeInRight', 17 | 'fadeInRightBig', 18 | 'fadeInUp', 19 | 'fadeInUpBig', 20 | 'flipInX', 21 | 'flipInY', 22 | 'rotateIn' 23 | ] 24 | const typeOut = [ 25 | 'bounceOut', 26 | 'bounceOutDown', 27 | 'bounceOutLeft', 28 | 'bounceOutRight', 29 | 'bounceOutUp', 30 | 'fadeInDown', 31 | 'fadeOut', 32 | 'fadeOutDown', 33 | 'fadeOutDownBig', 34 | 'fadeOutLeft', 35 | 'fadeOutLeftBig', 36 | 'fadeOutRight', 37 | 'fadeOutRightBig', 38 | 'fadeOutUp', 39 | 'fadeOutUpBig', 40 | 'rotateOut' 41 | ] 42 | const typeOther = [ 43 | 'bounceIn', 44 | 'bounce', 45 | 'flash', 46 | 'pulse', 47 | 'rubberBand', 48 | 'shake', 49 | 'headShake', 50 | 'swing', 51 | 'tada', 52 | 'wobble', 53 | 'jello' 54 | ] 55 | 56 | 57 | const AnimationView = () => { 58 | const [fontType, setFontType] = useState('animated bounceInRight') 59 | 60 | 61 | const Access = () => { 62 | return ( 63 |
64 | { 65 | typeIn.map((v, i) => ( 66 | 74 | )) 75 | } 76 |
77 | ) 78 | } 79 | 80 | const ExitView = () => { 81 | return ( 82 |
83 | {typeOut.map((v, i) => ( 84 | 92 | ))} 93 |
94 | ) 95 | } 96 | 97 | const Other = () => { 98 | return ( 99 |
100 | {typeOther.map((v, i) => ( 101 | 109 | ))} 110 |
111 | ) 112 | } 113 | 114 | return ( 115 | 116 |
117 | 118 |
119 |
120 |

何时使用

121 | 122 |

当页面需要动态效果时。

123 |
124 | 125 | 126 | 127 | , 136 | }, 137 | { 138 | label: `退场`, 139 | key: '2', 140 | children: , 141 | }, 142 | { 143 | label: `其它`, 144 | key: '3', 145 | children: , 146 | }, 147 | ]} 148 | /> 149 | 150 | 151 | 152 |
153 | Animate.css 154 |
155 | 156 |
157 |
158 | ) 159 | } 160 | 161 | export default AnimationView 162 | -------------------------------------------------------------------------------- /src/views/Others/Animation/index.js: -------------------------------------------------------------------------------- 1 | import AnimationView from './Animation.jsx' 2 | 3 | export default AnimationView 4 | -------------------------------------------------------------------------------- /src/views/Others/Editor/Editor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 4 | import { Layout, Divider } from 'antd' 5 | import SimpleMDE from 'react-simplemde-editor'; 6 | import 'easymde/dist/easymde.min.css'; 7 | 8 | // 其它编辑器 支持node14+ 9 | // react-markdown-editor-lite 10 | // https://github.com/HarryChen0506/react-markdown-editor-lite 11 | // braft-editor 12 | // https://github.com/margox/braft-editor 13 | 14 | 15 | // 当前使用 支持node16.15.0 16 | // simplemde - markdown - editor 17 | // https://github.com/sparksuite/simplemde-markdown-editor 18 | 19 | 20 | // Register plugins if required 21 | // MdEditor.use(YOUR_PLUGINS_HERE); 22 | 23 | // Initialize a markdown parser 24 | // const mdParser = new MarkdownIt(/* Markdown-it options */); 25 | 26 | const EditorView = (props) => { 27 | const value = '这是一个富文本编辑器' 28 | const handleChange = (value) => { 29 | console.log('value', value) 30 | } 31 | 32 | return ( 33 | 34 |
35 | 36 |
37 |
38 |

何时使用

39 | 40 |

41 | 当用户需要一些特定输入时,此页面使用的富文本编辑器是 42 | simplemde-markdown-editor 43 |

44 |
45 | 46 | 74 |
75 | ) 76 | } 77 | 78 | export default EditorView -------------------------------------------------------------------------------- /src/views/Others/Editor/index.js: -------------------------------------------------------------------------------- 1 | import EditorView from './Editor.jsx' 2 | 3 | export default EditorView 4 | -------------------------------------------------------------------------------- /src/views/Others/Progress/Progress.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 3 | import { Layout, Row, Col, Progress, Divider, Button } from 'antd' 4 | import '@/style/view-style/progress.scss' 5 | import { iconToElement } from '@/utils/util' 6 | 7 | const ButtonGroup = Button.Group 8 | 9 | const DrawerView = () => { 10 | const [percent, setPercent] = useState(0) 11 | 12 | return ( 13 | 14 |
15 | 16 |
17 |
18 |

何时使用

19 |

在操作需要较长时间才能完成时,为用户显示该操作的当前进度和状态。

20 |
21 | 22 | 23 | 24 |
25 | 基本 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | 35 |
36 | 动态进度条 37 | 38 | 39 |
49 | 50 | 51 | 52 |
53 | 圆环 54 | 55 | 56 | 57 |
58 | 59 | 60 | 61 |
62 | 自定义文字 63 | `${percent} Days`} /> 64 | 'Done'} /> 65 |
66 | 67 |
68 |
69 | ) 70 | } 71 | 72 | export default DrawerView 73 | -------------------------------------------------------------------------------- /src/views/Others/Progress/index.js: -------------------------------------------------------------------------------- 1 | import ProgressView from './Progress.jsx' 2 | 3 | export default ProgressView 4 | -------------------------------------------------------------------------------- /src/views/Others/Upload/Upload.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Layout, Row, Col, Upload, message, Button, Divider, Modal } from 'antd' 3 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 4 | import { iconToElement } from '@/utils/util' 5 | 6 | const { Dragger } = Upload 7 | 8 | const props = { 9 | name: 'file', 10 | action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76', 11 | headers: { 12 | authorization: 'authorization-text' 13 | }, 14 | onChange (info) { 15 | if (info.file.status !== 'uploading') { 16 | console.log(info.file, info.fileList) 17 | } 18 | if (info.file.status === 'done') { 19 | message.success(`${info.file.name} file uploaded successfully`) 20 | } else if (info.file.status === 'error') { 21 | message.error(`${info.file.name} file upload failed.`) 22 | } 23 | } 24 | } 25 | 26 | function getBase64 (img, callback) { 27 | const reader = new FileReader() 28 | reader.addEventListener('load', () => callback(reader.result)) 29 | reader.readAsDataURL(img) 30 | } 31 | 32 | function getBase_64 (file) { 33 | return new Promise((resolve, reject) => { 34 | const reader = new FileReader() 35 | reader.readAsDataURL(file) 36 | reader.onload = () => resolve(reader.result) 37 | reader.onerror = error => reject(error) 38 | }) 39 | } 40 | 41 | function beforeUpload (file) { 42 | const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' 43 | if (!isJpgOrPng) { 44 | message.error('You can only upload JPG/PNG file!') 45 | } 46 | const isLt2M = file.size / 1024 / 1024 < 2 47 | if (!isLt2M) { 48 | message.error('Image must smaller than 2MB!') 49 | } 50 | return isJpgOrPng && isLt2M 51 | } 52 | 53 | const UploadView = () => { 54 | const [state, setState] = useState({ 55 | previewVisible: false, 56 | previewImage: '', 57 | fileList: [ 58 | { 59 | uid: '-1', 60 | name: 'image.png', 61 | status: 'done', 62 | url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' 63 | }, 64 | { 65 | uid: '-2', 66 | name: 'image.png', 67 | status: 'done', 68 | url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' 69 | }, 70 | { 71 | uid: '-3', 72 | name: 'image.png', 73 | status: 'done', 74 | url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' 75 | }, 76 | { 77 | uid: '-4', 78 | name: 'image.png', 79 | status: 'done', 80 | url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' 81 | }, 82 | { 83 | uid: '-5', 84 | name: 'image.png', 85 | status: 'done', 86 | url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' 87 | } 88 | ] 89 | }) 90 | 91 | const [loading, setLoading] = useState(false) 92 | const [imageUrl, setimageUrl] = useState() 93 | 94 | let { previewVisible, previewImage, fileList } = state 95 | 96 | const handleChange = info => { 97 | getBase64(info.file.originFileObj, imageUrl => { 98 | setimageUrl(() => { 99 | if (info.file.status === 'done') { 100 | return imageUrl 101 | } 102 | }) 103 | setLoading(() => (info.file.status === 'uploading' ? true : false)) 104 | }) 105 | } 106 | 107 | const handleCancel = () => 108 | setState(prevState => { 109 | return { ...prevState, previewVisible: false } 110 | }) 111 | 112 | const handlePreview = async file => { 113 | if (!file.url && !file.preview) { 114 | file.preview = await getBase_64(file.originFileObj) 115 | } 116 | setState(prevState => { 117 | return { ...prevState, previewImage: file.url || file.preview, previewVisible: true } 118 | }) 119 | } 120 | const handle_Change = ({ fileList }) => 121 | setState(prevState => { 122 | return { ...prevState, fileList } 123 | }) 124 | 125 | const uploadButton = ( 126 |
127 | {iconToElement(loading ? 'LoadingOutlined' : 'PlusOutlined', { fontSize: '2rem' })} 128 |
Upload
129 |
130 | ) 131 | return ( 132 | 133 |
134 | 135 |
136 |
137 |

何时使用

138 |

上传是将信息(网页、文字、图片、视频等)通过网页或者上传工具发布到远程服务器上的过程

139 |
140 | 141 | 142 |
143 | 普通模式 144 | 145 | 149 | 150 |
151 | 152 | 153 | 154 |
155 | 照片墙 156 |
157 | 163 | {fileList.length >= 8 ? null : uploadButton} 164 | 165 | 166 | example 173 | 174 |
175 |
176 | 177 | 178 | 179 |
180 | 自定义模式 181 | 189 | {imageUrl ? avatar : uploadButton} 190 | 191 |
192 |
193 | 可拖拽上传 194 | 195 |

196 | {iconToElement('InboxOutlined', { fontSize: '2rem' })} 197 |

198 |

Click or drag file to this area to upload

199 |

200 | Support for a single or bulk upload. Strictly prohibit from uploading company data or 201 | other band files 202 |

203 |
204 |
205 | 206 |
207 |
208 | ) 209 | } 210 | 211 | export default UploadView 212 | -------------------------------------------------------------------------------- /src/views/Others/Upload/index.js: -------------------------------------------------------------------------------- 1 | import UploadView from './Upload.jsx' 2 | 3 | export default UploadView 4 | -------------------------------------------------------------------------------- /src/views/PublicView/Button/ButtonView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 3 | import { Layout, Row, Col, Button, Divider } from 'antd' 4 | import '@/style/view-style/button.scss' 5 | import { iconToElement } from '@/utils/util' 6 | import IconButton from './components/IconButton' 7 | 8 | const ButtonGroup = Button.Group 9 | 10 | const ButtonView = () => { 11 | const [loading, setloading] = useState(false) 12 | const [iconLoading, setIconLoading] = useState(false) 13 | return ( 14 | 15 |
16 | 17 |
18 |
19 |

何时使用

20 | 21 |

标记了一个(或封装一组)操作命令,响应用户点击行为,触发相应的业务逻辑。

22 |
23 |
24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 37 | 40 | 43 | 50 |
54 |
55 |

Basic

56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |

With Icon

68 | 69 | 73 | 74 | 78 | 79 | 80 | 81 |
104 | 105 | 106 | 107 |
108 | 109 |
110 | 111 |
112 | 113 | 116 | 117 | 118 | 119 | 122 |
123 |
124 | 127 | 128 | 131 | 134 | 137 |
138 | 139 |
140 |
141 |
142 | ) 143 | } 144 | 145 | export default ButtonView 146 | -------------------------------------------------------------------------------- /src/views/PublicView/Button/components/IconButton.jsx: -------------------------------------------------------------------------------- 1 | import { SearchOutlined } from '@ant-design/icons'; 2 | import { Button, Tooltip } from 'antd'; 3 | import React from 'react'; 4 | 5 | const App = () => ( 6 | <> 7 | 8 | 14 | 15 | 18 | 19 | 20 | 24 | 25 | 26 | 32 | 15 | }, 16 | { 17 | title: 'Age', 18 | dataIndex: 'age', 19 | key: 'age' 20 | }, 21 | { 22 | title: 'Address', 23 | dataIndex: 'address', 24 | key: 'address' 25 | }, 26 | { 27 | title: 'Tags', 28 | key: 'tags', 29 | dataIndex: 'tags', 30 | render: tags => ( 31 | 32 | {tags.map(tag => { 33 | let color = tag.length > 5 ? 'geekblue' : 'green' 34 | if (tag === 'loser') { 35 | color = 'volcano' 36 | } 37 | return ( 38 | 39 | {tag.toUpperCase()} 40 | 41 | ) 42 | })} 43 | 44 | ) 45 | }, 46 | { 47 | title: 'Action', 48 | key: 'action', 49 | render: (text, record) => ( 50 | 51 | 52 | 53 | 54 | 55 | ) 56 | } 57 | ] 58 | 59 | const data = [] 60 | for (let i = 0; i < 46; i++) { 61 | data.push({ 62 | key: i, 63 | name: `Edward King ${i}`, 64 | age: `${i + 1}`, 65 | address: `London, Park Lane no. ${i}`, 66 | tags: ['nice', 'developer'] 67 | }) 68 | } 69 | 70 | class Table1 extends Component { 71 | render () { 72 | return 73 | } 74 | } 75 | 76 | class Table2 extends Component { 77 | render () { 78 | return ( 79 |
80 | 81 | 82 | 83 | ( 88 | 89 | {tags.map(tag => ( 90 | 91 | {tag} 92 | 93 | ))} 94 | 95 | )} 96 | /> 97 | ( 101 | 102 | 103 | 104 | 105 | 106 | )} 107 | /> 108 |
109 | ) 110 | } 111 | } 112 | 113 | class Table3 extends Component { 114 | state = { 115 | selectedRowKeys: [] 116 | } 117 | 118 | onSelectChange = selectedRowKeys => { 119 | console.log('selectedRowKeys changed: ', selectedRowKeys) 120 | this.setState({ selectedRowKeys }) 121 | } 122 | 123 | render () { 124 | const { selectedRowKeys } = this.state 125 | const rowSelection = { 126 | selectedRowKeys, 127 | onChange: this.onSelectChange, 128 | hideDefaultSelections: true, 129 | selections: [ 130 | { 131 | key: 'all-data', 132 | text: 'Select All Data', 133 | onSelect: () => { 134 | this.setState({ 135 | selectedRowKeys: [...Array(46).keys()] // 0...45 136 | }) 137 | } 138 | }, 139 | { 140 | key: 'odd', 141 | text: 'Select Odd Row', 142 | onSelect: changableRowKeys => { 143 | let newSelectedRowKeys = [] 144 | newSelectedRowKeys = changableRowKeys.filter((key, index) => { 145 | if (index % 2 !== 0) { 146 | return false 147 | } 148 | return true 149 | }) 150 | this.setState({ selectedRowKeys: newSelectedRowKeys }) 151 | } 152 | }, 153 | { 154 | key: 'even', 155 | text: 'Select Even Row', 156 | onSelect: changableRowKeys => { 157 | let newSelectedRowKeys = [] 158 | newSelectedRowKeys = changableRowKeys.filter((key, index) => { 159 | if (index % 2 !== 0) { 160 | return true 161 | } 162 | return false 163 | }) 164 | this.setState({ selectedRowKeys: newSelectedRowKeys }) 165 | } 166 | } 167 | ] 168 | } 169 | return 170 | } 171 | } 172 | 173 | class Table4 extends Component { 174 | state = { 175 | filteredInfo: null, 176 | sortedInfo: null 177 | } 178 | handleChange = (pagination, filters, sorter) => { 179 | console.log('Various parameters', pagination, filters, sorter) 180 | this.setState({ 181 | filteredInfo: filters, 182 | sortedInfo: sorter 183 | }) 184 | } 185 | 186 | clearFilters = () => { 187 | this.setState({ filteredInfo: null }) 188 | } 189 | 190 | clearAll = () => { 191 | this.setState({ 192 | filteredInfo: null, 193 | sortedInfo: null 194 | }) 195 | } 196 | 197 | setAgeSort = () => { 198 | this.setState({ 199 | sortedInfo: { 200 | order: 'descend', 201 | columnKey: 'age' 202 | } 203 | }) 204 | } 205 | render () { 206 | let { sortedInfo, filteredInfo } = this.state 207 | sortedInfo = sortedInfo || {} 208 | filteredInfo = filteredInfo || {} 209 | const columns = [ 210 | { 211 | title: 'Name', 212 | dataIndex: 'name', 213 | key: 'name', 214 | filters: [ 215 | { text: 'Edward King 20', value: 'Edward King 20' }, 216 | { text: 'Edward King 25', value: 'Edward King 25' } 217 | ], 218 | filteredValue: filteredInfo.name || null, 219 | onFilter: (value, record) => record.name.includes(value) 220 | }, 221 | { 222 | title: 'Age', 223 | dataIndex: 'age', 224 | key: 'age', 225 | sorter: (a, b) => a.age - b.age, 226 | sortOrder: sortedInfo.columnKey === 'age' && sortedInfo.order 227 | }, 228 | { 229 | title: 'Address', 230 | dataIndex: 'address', 231 | key: 'address', 232 | filters: [ 233 | { text: 'London, Park Lane no. 24', value: 'London, Park Lane no. 24' }, 234 | { text: 'London, Park Lane no. 27', value: 'London, Park Lane no. 27' } 235 | ], 236 | filteredValue: filteredInfo.address || null, 237 | onFilter: (value, record) => record.address.includes(value) 238 | } 239 | ] 240 | return ( 241 |
242 |
243 | 244 | 245 | 246 |
247 |
248 | 249 | ) 250 | } 251 | } 252 | 253 | const handleAnchor = (e, link) => { 254 | e.preventDefault(); 255 | }; 256 | 257 | class TableView extends Component { 258 | render () { 259 | return ( 260 | 261 |
262 | 263 |
264 |
265 |

何时使用

266 | 267 |

当有大量结构化的数据需要展现时;

268 |

当需要对数据进行排序、搜索、分页、自定义操作等复杂行为时。

269 |
270 | 271 | 272 | 277 | 278 | 279 | 280 | 281 | 282 | 283 |
284 |
285 |

基础表格

286 | 287 | 288 |
289 | 290 | 291 | 292 |
293 |

JSX表单

294 | JSX表格 295 | 296 |
297 | 298 | 299 | 300 |
301 |

可选表单

302 | 可选表格 303 | 304 |
305 | 306 | 307 | 308 |
309 |

可筛选排序表单

310 | 可筛选排序表格 311 | 312 |
313 | 314 | 315 | 316 | ) 317 | } 318 | } 319 | 320 | export default TableView 321 | -------------------------------------------------------------------------------- /src/views/ShowView/Table/index.js: -------------------------------------------------------------------------------- 1 | import TableView from './Table.jsx' 2 | 3 | export default TableView 4 | -------------------------------------------------------------------------------- /src/views/ShowView/Tabs/Tabs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 3 | import { Layout, Divider, Row, Col } from 'antd' 4 | import BaseTabs from './components/BaseTabs' 5 | import SizeTabs from './components/SizeTabs' 6 | import LocationTabs from './components/LocationTabs' 7 | import AddTabs from './components/AddTabs' 8 | 9 | const TabsViews = () => { 10 | return ( 11 | 12 |
13 | 14 |
15 |
16 |

何时使用

17 | 18 |

提供平级的区域将大块内容进行收纳和展现,保持界面整洁

19 |

Ant Design 依次提供了三级选项卡,分别用于不同的场景

20 |

- 卡片式的页签,提供可关闭的样式,常用于容器顶部。

21 |

- 标准线条式页签,用于容器内部的主功能切换,这是最常用的 Tabs。

22 |

- RadioButton 可作为更次级的页签来使用。

23 |
24 | 25 | 26 |
27 |
28 | 基础 29 | 30 |
31 | 32 | 33 | 34 |
35 | 控制大小 36 | 37 |
38 | 39 | 40 | 41 |
42 | 控制显示位置 43 | 44 |
45 | 46 | 47 | 48 |
49 | 可增加删除 50 | 51 |
52 | 53 | 54 | 55 | ) 56 | } 57 | 58 | export default TabsViews 59 | -------------------------------------------------------------------------------- /src/views/ShowView/Tabs/components/AddTabs.jsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from 'antd'; 2 | import React, { useRef, useState } from 'react'; 3 | const initialItems = [ 4 | { 5 | label: 'Tab 1', 6 | children: 'Content of Tab 1', 7 | key: '1', 8 | }, 9 | { 10 | label: 'Tab 2', 11 | children: 'Content of Tab 2', 12 | key: '2', 13 | }, 14 | { 15 | label: 'Tab 3', 16 | children: 'Content of Tab 3', 17 | key: '3', 18 | closable: false, 19 | }, 20 | ]; 21 | 22 | const App = () => { 23 | const [activeKey, setActiveKey] = useState(initialItems[0].key); 24 | const [items, setItems] = useState(initialItems); 25 | const newTabIndex = useRef(0); 26 | 27 | const onChange = (newActiveKey) => { 28 | setActiveKey(newActiveKey); 29 | }; 30 | 31 | const add = () => { 32 | const newActiveKey = `newTab${newTabIndex.current++}`; 33 | const newPanes = [...items]; 34 | newPanes.push({ 35 | label: 'New Tab', 36 | children: 'Content of new Tab', 37 | key: newActiveKey, 38 | }); 39 | setItems(newPanes); 40 | setActiveKey(newActiveKey); 41 | }; 42 | 43 | const remove = (targetKey) => { 44 | let newActiveKey = activeKey; 45 | let lastIndex = -1; 46 | items.forEach((item, i) => { 47 | if (item.key === targetKey) { 48 | lastIndex = i - 1; 49 | } 50 | }); 51 | const newPanes = items.filter((item) => item.key !== targetKey); 52 | 53 | if (newPanes.length && newActiveKey === targetKey) { 54 | if (lastIndex >= 0) { 55 | newActiveKey = newPanes[lastIndex].key; 56 | } else { 57 | newActiveKey = newPanes[0].key; 58 | } 59 | } 60 | 61 | setItems(newPanes); 62 | setActiveKey(newActiveKey); 63 | }; 64 | 65 | const onEdit = (targetKey, action) => { 66 | if (action === 'add') { 67 | add(); 68 | } else { 69 | remove(targetKey); 70 | } 71 | }; 72 | 73 | return ( 74 | 81 | ); 82 | }; 83 | 84 | export default App; -------------------------------------------------------------------------------- /src/views/ShowView/Tabs/components/BaseTabs.jsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from 'antd'; 2 | import React from 'react'; 3 | 4 | const onChange = (key) => { 5 | console.log(key); 6 | }; 7 | 8 | const App = () => ( 9 | 30 | ); 31 | 32 | export default App; -------------------------------------------------------------------------------- /src/views/ShowView/Tabs/components/LocationTabs.jsx: -------------------------------------------------------------------------------- 1 | import { Radio, Space, Tabs } from 'antd'; 2 | import React, { useState } from 'react'; 3 | 4 | const App = () => { 5 | const [tabPosition, setTabPosition] = useState('left'); 6 | 7 | const changeTabPosition = (e) => { 8 | setTabPosition(e.target.value); 9 | }; 10 | 11 | return ( 12 | <> 13 | 18 | Tab position: 19 | 20 | top 21 | bottom 22 | left 23 | right 24 | 25 | 26 | { 29 | const id = String(i + 1); 30 | return { 31 | label: `Tab ${id}`, 32 | key: id, 33 | children: `Content of Tab ${id}`, 34 | }; 35 | })} 36 | /> 37 | 38 | ); 39 | }; 40 | 41 | export default App; -------------------------------------------------------------------------------- /src/views/ShowView/Tabs/components/SizeTabs.jsx: -------------------------------------------------------------------------------- 1 | import { Radio, Tabs } from 'antd'; 2 | import React, { useState } from 'react'; 3 | 4 | const App = () => { 5 | const [size, setSize] = useState('small'); 6 | 7 | const onChange = (e) => { 8 | setSize(e.target.value); 9 | }; 10 | 11 | return ( 12 |
13 | 20 | Small 21 | Middle 22 | Large 23 | 24 | { 31 | const id = String(i + 1); 32 | return { 33 | label: `Tab ${id}`, 34 | key: id, 35 | children: `Content of tab ${id}`, 36 | }; 37 | })} 38 | /> 39 | { 44 | const id = String(i + 1); 45 | return { 46 | label: `Card Tab ${id}`, 47 | key: id, 48 | children: `Content of card tab ${id}`, 49 | }; 50 | })} 51 | /> 52 |
53 | ); 54 | }; 55 | 56 | export default App; -------------------------------------------------------------------------------- /src/views/ShowView/Tabs/index.js: -------------------------------------------------------------------------------- 1 | import TabsView from './Tabs.jsx' 2 | 3 | export default TabsView 4 | -------------------------------------------------------------------------------- /src/views/ShowView/Tree/Index.scss: -------------------------------------------------------------------------------- 1 | .site-tree-search-value { 2 | color: #f50; 3 | } -------------------------------------------------------------------------------- /src/views/ShowView/Tree/Tree.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 3 | import { Layout, Divider, Row, Col } from 'antd' 4 | import { iconToElement } from '@/utils/util' 5 | import BaseTree from './components/BaseTree' 6 | import SearchTree from './components/SearchTree' 7 | import ControlledTree from './components/ControlledTree' 8 | import DragTree from './components/DragTree' 9 | 10 | 11 | const TreeView = () => { 12 | return ( 13 | 14 |
15 | 16 |
17 |
18 |

何时使用

19 | 20 |

21 | 文件夹、组织架构、生物分类、国家地区等等,世间万物的大多数结构都是树形结构。使用 树控件 22 | 可以完整展现其中的层级关系,并具有展开收起选择等交互功能。 23 |

24 |
25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ) 45 | } 46 | 47 | export default TreeView 48 | -------------------------------------------------------------------------------- /src/views/ShowView/Tree/components/BaseTree.jsx: -------------------------------------------------------------------------------- 1 | import { Tree, Divider } from 'antd'; 2 | import React from 'react'; 3 | 4 | const treeData = [ 5 | { 6 | title: '0-0', 7 | key: '0-0', 8 | children: [ 9 | { 10 | title: '0-0-0', 11 | key: '0-0-0', 12 | children: [ 13 | { title: '0-0-0-0', key: '0-0-0-0' }, 14 | { title: '0-0-0-1', key: '0-0-0-1' }, 15 | { title: '0-0-0-2', key: '0-0-0-2' } 16 | ] 17 | }, 18 | { 19 | title: '0-0-1', 20 | key: '0-0-1', 21 | children: [ 22 | { title: '0-0-1-0', key: '0-0-1-0' }, 23 | { title: '0-0-1-1', key: '0-0-1-1' }, 24 | { title: '0-0-1-2', key: '0-0-1-2' } 25 | ] 26 | }, 27 | { 28 | title: '0-0-2', 29 | key: '0-0-2' 30 | } 31 | ] 32 | }, 33 | { 34 | title: '0-1', 35 | key: '0-1', 36 | children: [ 37 | { title: '0-1-0-0', key: '0-1-0-0' }, 38 | { title: '0-1-0-1', key: '0-1-0-1' }, 39 | { title: '0-1-0-2', key: '0-1-0-2' } 40 | ] 41 | }, 42 | { 43 | title: '0-2', 44 | key: '0-2' 45 | } 46 | ] 47 | 48 | const BaseTree = () => { 49 | const onSelect = (selectedKeys, info) => { 50 | console.log('selected', selectedKeys, info); 51 | }; 52 | 53 | const onCheck = (checkedKeys, info) => { 54 | console.log('onCheck', checkedKeys, info); 55 | }; 56 | 57 | return ( 58 |
59 | 基础用法 60 | 69 |
70 | ); 71 | }; 72 | 73 | export default BaseTree; -------------------------------------------------------------------------------- /src/views/ShowView/Tree/components/ControlledTree.jsx: -------------------------------------------------------------------------------- 1 | import { Tree, Divider } from 'antd'; 2 | import React, { useState } from 'react'; 3 | 4 | const treeData = [ 5 | { 6 | title: '0-0', 7 | key: '0-0', 8 | children: [ 9 | { 10 | title: '0-0-0', 11 | key: '0-0-0', 12 | children: [ 13 | { 14 | title: '0-0-0-0', 15 | key: '0-0-0-0', 16 | }, 17 | { 18 | title: '0-0-0-1', 19 | key: '0-0-0-1', 20 | }, 21 | { 22 | title: '0-0-0-2', 23 | key: '0-0-0-2', 24 | }, 25 | ], 26 | }, 27 | { 28 | title: '0-0-1', 29 | key: '0-0-1', 30 | children: [ 31 | { 32 | title: '0-0-1-0', 33 | key: '0-0-1-0', 34 | }, 35 | { 36 | title: '0-0-1-1', 37 | key: '0-0-1-1', 38 | }, 39 | { 40 | title: '0-0-1-2', 41 | key: '0-0-1-2', 42 | }, 43 | ], 44 | }, 45 | { 46 | title: '0-0-2', 47 | key: '0-0-2', 48 | }, 49 | ], 50 | }, 51 | { 52 | title: '0-1', 53 | key: '0-1', 54 | children: [ 55 | { 56 | title: '0-1-0-0', 57 | key: '0-1-0-0', 58 | }, 59 | { 60 | title: '0-1-0-1', 61 | key: '0-1-0-1', 62 | }, 63 | { 64 | title: '0-1-0-2', 65 | key: '0-1-0-2', 66 | }, 67 | ], 68 | }, 69 | { 70 | title: '0-2', 71 | key: '0-2', 72 | }, 73 | ]; 74 | 75 | const ControlledTree = () => { 76 | const [expandedKeys, setExpandedKeys] = useState(['0-0-0', '0-0-1']); 77 | const [checkedKeys, setCheckedKeys] = useState(['0-0-0']); 78 | const [selectedKeys, setSelectedKeys] = useState([]); 79 | const [autoExpandParent, setAutoExpandParent] = useState(true); 80 | 81 | const onExpand = (expandedKeysValue) => { 82 | console.log('onExpand', expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse. 83 | // or, you can remove all expanded children keys. 84 | 85 | setExpandedKeys(expandedKeysValue); 86 | setAutoExpandParent(false); 87 | }; 88 | 89 | const onCheck = (checkedKeysValue) => { 90 | console.log('onCheck', checkedKeysValue); 91 | setCheckedKeys(checkedKeysValue); 92 | }; 93 | 94 | const onSelect = (selectedKeysValue, info) => { 95 | console.log('onSelect', info); 96 | setSelectedKeys(selectedKeysValue); 97 | }; 98 | 99 | return ( 100 |
101 | 受控控件 102 | 113 |
114 | ); 115 | }; 116 | 117 | export default ControlledTree; -------------------------------------------------------------------------------- /src/views/ShowView/Tree/components/DragTree.jsx: -------------------------------------------------------------------------------- 1 | import { Tree, Divider } from 'antd'; 2 | import React, { useState } from 'react'; 3 | 4 | const x = 3; 5 | const y = 2; 6 | const z = 1; 7 | const defaultData = []; 8 | 9 | const generateData = (_level, _preKey, _tns) => { 10 | const preKey = _preKey || '0'; 11 | const tns = _tns || defaultData; 12 | const children = []; 13 | 14 | for (let i = 0; i < x; i++) { 15 | const key = `${preKey}-${i}`; 16 | tns.push({ 17 | title: key, 18 | key, 19 | }); 20 | 21 | if (i < y) { 22 | children.push(key); 23 | } 24 | } 25 | 26 | if (_level < 0) { 27 | return tns; 28 | } 29 | 30 | const level = _level - 1; 31 | children.forEach((key, index) => { 32 | tns[index].children = []; 33 | return generateData(level, key, tns[index].children); 34 | }); 35 | }; 36 | 37 | generateData(z); 38 | 39 | const DragTree = () => { 40 | const [gData, setGData] = useState(defaultData); 41 | const [expandedKeys] = useState(['0-0', '0-0-0', '0-0-0-0']); 42 | 43 | const onDragEnter = (info) => { 44 | console.log(info); // expandedKeys 需要受控时设置 45 | // setExpandedKeys(info.expandedKeys) 46 | }; 47 | 48 | const onDrop = (info) => { 49 | console.log(info); 50 | const dropKey = info.node.key; 51 | const dragKey = info.dragNode.key; 52 | const dropPos = info.node.pos.split('-'); 53 | const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); 54 | 55 | const loop = (data, key, callback) => { 56 | for (let i = 0; i < data.length; i++) { 57 | if (data[i].key === key) { 58 | return callback(data[i], i, data); 59 | } 60 | 61 | if (data[i].children) { 62 | loop(data[i].children, key, callback); 63 | } 64 | } 65 | }; 66 | 67 | const data = [...gData]; // Find dragObject 68 | 69 | let dragObj; 70 | loop(data, dragKey, (item, index, arr) => { 71 | arr.splice(index, 1); 72 | dragObj = item; 73 | }); 74 | 75 | if (!info.dropToGap) { 76 | // Drop on the content 77 | loop(data, dropKey, (item) => { 78 | item.children = item.children || []; // where to insert 示例添加到头部,可以是随意位置 79 | 80 | item.children.unshift(dragObj); 81 | }); 82 | } else if ( 83 | (info.node.props.children || []).length > 0 && // Has children 84 | info.node.props.expanded && // Is expanded 85 | dropPosition === 1 // On the bottom gap 86 | ) { 87 | loop(data, dropKey, (item) => { 88 | item.children = item.children || []; // where to insert 示例添加到头部,可以是随意位置 89 | 90 | item.children.unshift(dragObj); // in previous version, we use item.children.push(dragObj) to insert the 91 | // item to the tail of the children 92 | }); 93 | } else { 94 | let ar = []; 95 | let i; 96 | loop(data, dropKey, (_item, index, arr) => { 97 | ar = arr; 98 | i = index; 99 | }); 100 | 101 | if (dropPosition === -1) { 102 | ar.splice(i, 0, dragObj); 103 | } else { 104 | ar.splice(i + 1, 0, dragObj); 105 | } 106 | } 107 | 108 | setGData(data); 109 | }; 110 | 111 | return ( 112 | 113 |
114 | 可拖拽 115 | 124 |
125 | ); 126 | }; 127 | 128 | export default DragTree; -------------------------------------------------------------------------------- /src/views/ShowView/Tree/components/SearchTree.jsx: -------------------------------------------------------------------------------- 1 | import { Tree, Divider, Input } from 'antd'; 2 | import React, { useMemo, useState } from 'react'; 3 | 4 | const { Search } = Input; 5 | const x = 3; 6 | const y = 2; 7 | const z = 1; 8 | const defaultData = []; 9 | 10 | const generateData = (_level, _preKey, _tns) => { 11 | const preKey = _preKey || '0'; 12 | const tns = _tns || defaultData; 13 | const children = []; 14 | 15 | for (let i = 0; i < x; i++) { 16 | const key = `${preKey}-${i}`; 17 | tns.push({ 18 | title: key, 19 | key, 20 | }); 21 | 22 | if (i < y) { 23 | children.push(key); 24 | } 25 | } 26 | 27 | if (_level < 0) { 28 | return tns; 29 | } 30 | 31 | const level = _level - 1; 32 | children.forEach((key, index) => { 33 | tns[index].children = []; 34 | return generateData(level, key, tns[index].children); 35 | }); 36 | }; 37 | 38 | generateData(z); 39 | const dataList = []; 40 | 41 | const generateList = (data) => { 42 | for (let i = 0; i < data.length; i++) { 43 | const node = data[i]; 44 | const { key } = node; 45 | dataList.push({ 46 | key, 47 | title: key, 48 | }); 49 | 50 | if (node.children) { 51 | generateList(node.children); 52 | } 53 | } 54 | }; 55 | 56 | generateList(defaultData); 57 | 58 | const getParentKey = (key, tree) => { 59 | let parentKey; 60 | 61 | for (let i = 0; i < tree.length; i++) { 62 | const node = tree[i]; 63 | 64 | if (node.children) { 65 | if (node.children.some((item) => item.key === key)) { 66 | parentKey = node.key; 67 | } else if (getParentKey(key, node.children)) { 68 | parentKey = getParentKey(key, node.children); 69 | } 70 | } 71 | } 72 | 73 | return parentKey; 74 | }; 75 | 76 | const SearchTree = () => { 77 | const [expandedKeys, setExpandedKeys] = useState([]); 78 | const [searchValue, setSearchValue] = useState(''); 79 | const [autoExpandParent, setAutoExpandParent] = useState(true); 80 | 81 | const onExpand = (newExpandedKeys) => { 82 | setExpandedKeys(newExpandedKeys); 83 | setAutoExpandParent(false); 84 | }; 85 | 86 | const onChange = (e) => { 87 | const { value } = e.target; 88 | const newExpandedKeys = dataList 89 | .map((item) => { 90 | if (item.title.indexOf(value) > -1) { 91 | return getParentKey(item.key, defaultData); 92 | } 93 | 94 | return null; 95 | }) 96 | .filter((item, i, self) => item && self.indexOf(item) === i); 97 | setExpandedKeys(newExpandedKeys); 98 | setSearchValue(value); 99 | setAutoExpandParent(true); 100 | }; 101 | 102 | const treeData = useMemo(() => { 103 | const loop = (data) => 104 | data.map((item) => { 105 | const strTitle = item.title; 106 | const index = strTitle.indexOf(searchValue); 107 | const beforeStr = strTitle.substring(0, index); 108 | const afterStr = strTitle.slice(index + searchValue.length); 109 | const title = 110 | index > -1 ? ( 111 | 112 | {beforeStr} 113 | {searchValue} 114 | {afterStr} 115 | 116 | ) : ( 117 | {strTitle} 118 | ); 119 | 120 | if (item.children) { 121 | return { 122 | title, 123 | key: item.key, 124 | children: loop(item.children), 125 | }; 126 | } 127 | 128 | return { 129 | title, 130 | key: item.key, 131 | }; 132 | }); 133 | 134 | return loop(defaultData); 135 | }, [searchValue]); 136 | 137 | return ( 138 |
139 | 可搜索 140 | 147 | 153 | 154 |
155 | ); 156 | }; 157 | 158 | export default SearchTree; -------------------------------------------------------------------------------- /src/views/ShowView/Tree/index.js: -------------------------------------------------------------------------------- 1 | import TreeView from './Tree.jsx' 2 | import './index.scss' 3 | 4 | export default TreeView 5 | -------------------------------------------------------------------------------- /src/views/TestView/TestView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Layout, Divider } from 'antd' 3 | import CustomBreadcrumb from '@/components/CustomBreadcrumb' 4 | 5 | const TestView = () => ( 6 | 7 |
8 | 9 |
10 |
11 |

多级导航

12 | 13 |

这个是多级导航

14 |
15 |
16 | ) 17 | 18 | export default TestView 19 | -------------------------------------------------------------------------------- /src/views/TestView/index.js: -------------------------------------------------------------------------------- 1 | import TestView from './TestView.jsx' 2 | 3 | export default TestView 4 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import { resolve } from 'path'; 4 | 5 | 6 | 7 | function pathResolve (dir) { 8 | return resolve(__dirname, dir); 9 | } 10 | 11 | export default ({ mode }) => { 12 | process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }; 13 | 14 | // https://vitejs.dev/config/ 15 | return defineConfig({ 16 | // 项目根目录 17 | root: process.cwd(), 18 | // 项目部署的基础路径 19 | base: '/', 20 | //环境配置 21 | mode: 'development', 22 | //全局变量替换 Record 23 | define: { 24 | // util: [path.resolve(__dirname, './src/utils/util.ts')], 25 | // aaaa: "123", 26 | }, 27 | plugins: [ 28 | react(), 29 | ], 30 | //静态资源服务的文件夹 31 | publicDir: "public", 32 | resolve: { 33 | // 别名 34 | // alias: { 35 | // '@': pathResolve('./src'), 36 | // // '@': path.resolve(__dirname, './src'), 37 | // // views: path.resolve(__dirname, './src/views'), 38 | // // components: path.resolve(__dirname, './src/components'), 39 | // // utils: path.resolve(__dirname, './src/utils'), 40 | // // less: path.resolve(__dirname, "./src/less"), 41 | // // assets: path.resolve(__dirname, "./src/assets"), 42 | // // com: path.resolve(__dirname, "./src/components"), 43 | // // store: path.resolve(__dirname, "./src/store"), 44 | // // mixins: path.resolve(__dirname, "./src/mixins") 45 | // }, 46 | alias: [ 47 | { find: /^~/, replacement: '' }, 48 | { find: '@', replacement: pathResolve('./src') } 49 | ], 50 | dedupe: [], 51 | //情景导出package.json配置中的exports 字段 52 | conditions: [], 53 | //解析package.json中的字段 54 | mainFields: ['module', 'jsnext:main', 'jsnext'], 55 | //导入时想要省略的扩展名列表 56 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'] 57 | }, 58 | // 依赖优化项 59 | optimizeDeps: { 60 | // include 默认情况下,不在 node_modules 中的,链接的包不会被预构建。使用此选项可强制预构建链接的包。 61 | include: [ 62 | '@ant-design/colors', 63 | '@ant-design/icons', 64 | ], 65 | // 设置为 true 强制使依赖预构建 66 | // force: true, 67 | }, 68 | // CSS 预处理器 69 | css: { 70 | // modules: { 71 | // localsConvention: 'camelCaseOnly', 72 | // }, 73 | preprocessorOptions: { 74 | less: { 75 | javascriptEnabled: true, 76 | // modifyVars是在全局less文件后面添加变量的配置。modifyVars对应的对象属性名会加上@追加到less文件后 77 | modifyVars: { 78 | '@primary-color': '#1890ff', 79 | }, 80 | }, 81 | }, 82 | postcss: { 83 | plugins: [ 84 | { 85 | postcssPlugin: 'internal:charset-removal',//vite打包问题(忽略字符集问题) 86 | AtRule: { 87 | charset: (atRule) => { 88 | if (atRule.name === 'charset') { 89 | atRule.remove(); 90 | } 91 | } 92 | } 93 | } 94 | ], 95 | }, 96 | }, 97 | build: { 98 | // 指定输出路径 99 | outDir: 'dist', 100 | //指定生成静态资源的存放路劲 101 | assetsDir: 'static', 102 | //将 CommonJS 模块转换为 ES6 的 Rollup 插件 103 | commonjsOptions: { 104 | requireReturnsDefault: 'namespace' 105 | }, 106 | rollupOptions: { 107 | output: { 108 | //分割打包 解决打包时Some chunks are larger警告 109 | manualChunks (id) { 110 | if (id.includes('node_modules')) { 111 | return id.toString().split('node_modules/')[1].split('/')[0].toString(); 112 | } 113 | } 114 | } 115 | }, 116 | // chunk 大小警告的限制(以 kbs 为单位) 117 | chunkSizeWarningLimit: 1000, 118 | // 启用/禁用 gzip 压缩大小报告 119 | reportCompressedSize: false, 120 | }, 121 | //静态资源处理 字符串|正则表达式 122 | assetsInclude: '', 123 | //调整控制台输出的级别 'info' | 'warn' | 'error' | 'silent' 124 | logLevel: 'info', 125 | //设为 false 可以避免 Vite 清屏而错过在终端中打印某些关键信息 126 | clearScreen: true, 127 | // 服务 128 | server: { 129 | // 服务器主机名,如果允许外部访问,可设置为"0.0.0.0" 130 | host: "localhost", 131 | port: 9527, 132 | // 设为 true ,若端口已被占用则会直接退出,而不是尝试下一个可用端口 133 | strictPort: false, 134 | // 是否自动在浏览器打开 135 | open: false, 136 | // 是否开启 https 137 | https: false, 138 | // 为开发服务器配置 CORS 139 | cors: true, 140 | proxy: { 141 | '/api': { 142 | //#gitignoreline_start 143 | target: 'xxx', 144 | //#gitignoreline_end 145 | changeOrigin: true, 146 | ws: true, 147 | }, 148 | }, 149 | }, 150 | }) 151 | } 152 | 153 | 154 | --------------------------------------------------------------------------------