react后台管理系统(四)
🌈个人主页:前端青山
🔥系列专栏:React篇
🔖人终将被年少不可得之物困其一生
依旧青山,本期给大家带来React篇专栏内容:react后台管理系统(四)
前言
本文将详细介绍如何在React项目中集成ECharts,实现柱状图和折线图的动态渲染,并通过路由配置实现页面导航。
目录
26.实现登录
// layout/login/Index.jsx
import React, { Component } from 'react'
class App extends Component {
render() {
return (
<div>
登录
</div>
)
}
}
export default App
// App.jsx
import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import Main from './layout/main/index.jsx'
import Login from './layout/login/Index.jsx'
function App () {
return (
<Router >
<Switch>
<Route path="/login" exact component = {Login}/>
<Route path="/" component = {Main}/>
</Switch>
</Router>
)
}
export default App
完善登录布局
import React, { Component } from 'react'
import { Divider, Space, Input, Button } from 'antd'
class App extends Component {
render() {
return (
<div className="loginContainer">
<div className="loginBox">
<div className="loginLogo">
嗨购管理系统
</div>
<Divider>使用用户名登录</Divider>
<Space direction="vertical" style={{ width: '100%'}}>
<div style={{ width: '100%', height: 40, borderBottom: '1px solid #999'}}>
<Input size="large" bordered = { false } placeholder="请输入用户名"/>
</div>
<div style={{ width: '100%', height: 40, borderBottom: '1px solid #999'}}>
<Input size="large" type="password" bordered = { false } placeholder="请输入密码"/>
</div>
<Button style={{ marginTop: 50}} type="primary" block size="large">登录</Button>
</Space>
</div>
</div>
)
}
}
export default App
@import '~antd/dist/antd.css';
#root { height: 100%;}
.ant-layout { height: 100%;}
.mySider .trigger {
padding: 0 24px;
font-size: 18px;
line-height: 64px;
cursor: pointer;
transition: color 0.3s;
}
.mySider .trigger:hover {
color: #1890ff;
}
.mySider .logo {
height: 32px;
margin: 16px;
/* background: rgba(255, 255, 255, 0.3); */
color: #fff;
font-size: 18px;
line-height: 32px;
}
.site-layout .site-layout-background {
background: #fff;
}
/* 登录 */
.loginContainer {
width: 100%;
height: 100%;
background: url(https://cas.1000phone.net/cas/images/login/bg.png) center no-repeat;
background-size: cover;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.loginBox {
width: 420px;
height: 460px;
background-color: rgba(255, 255, 255, 1);
border-radius: 32px;
padding: 10px 30px;
}
.loginLogo {
width: 100%;
height: 80px;
font-size: 32px;
display: flex;
justify-content: center;
align-items: center;
}
实现登录功能
import React, { Component } from 'react'
import { Divider, Space, Input, Button, message } from 'antd'
import { login } from './../../api/users'
class App extends Component {
state = {
adminname: '',
password: ''
}
changeAdminname = (e) => this.setState({ adminname: e.target.value })
changePassword = (e) => this.setState({ password: e.target.value })
loginFn = () => {
const params = {
adminname: this.state.adminname,
password: this.state.password
}
login(params).then(res => {
if (res.data.code === '10003') {
message.warning('密码错误')
} else if (res.data.code === '10005') {
message.warning('账户不存在')
} else {
console.log(res.data.data)
// 信息存入本地
localStorage.setItem('adminToken', res.data.data.token)
localStorage.setItem('adminname', res.data.data.adminname)
localStorage.setItem('role', res.data.data.role)
localStorage.setItem('loginState', true)
// 跳转到首页
this.props.history.push('/')
}
})
}
render() {
return (
<div className="loginContainer">
<div className="loginBox">
<div className="loginLogo">
嗨购管理系统
</div>
<Divider>使用用户名登录</Divider>
<Space direction="vertical" style={{ width: '100%'}}>
<div style={{ width: '100%', height: 40, borderBottom: '1px solid #999'}}>
<Input value = { this.state.adminname } onChange = { this.changeAdminname }
size="large" bordered = { false } placeholder="请输入用户名"/>
</div>
<div style={{ width: '100%', height: 40, borderBottom: '1px solid #999'}}>
<Input value = { this.state.password } onChange = { this.changePassword }
size="large" type="password" bordered = { false } placeholder="请输入密码"/>
</div>
<Button onClick={ this.loginFn } style={{ marginTop: 50}} type="primary" block size="large">登录</Button>
</Space>
</div>
</div>
)
}
}
export default App
27.封装获取左侧菜单数据
// router/getMenu.js
// import menus from './menus'
// const checkedKeys = ['0-0', '0-1-0', '0-2-1', '0-3-1']
// ['0-0', '0-1', '0-1-0', '0-2', '0-2-1', '0-3']
export function getMenu (menus, checkedKeys) {
console.log(checkedKeys)
// 1.想办法获取到 ['0-0', '0-1', '0-1-0', '0-2', '0-2-1', '0-3']
// 先获取到所有的一级菜单, 合并原来的数组,再去重
const arr = []
checkedKeys.forEach(item => {
arr.push(item.slice(0, 3))
}) // ['0-0', '0-1', '0-2', '0-3']
// 合并数组,并且去重
const lastArr = Array.from(new Set([ ...arr, ...checkedKeys]))
// ['0-0', '0-1', '0-1-0', '0-2', '0-2-1', '0-3']
console.log(lastArr)
// 2.遍历lastArr,组合数据
const result = []
lastArr.forEach(item => {
// 2.1获取真实对应的索引值
const index1 = item.split('-')[1]
const index2 = item.split('-')[2]
let obj = {}
// 2.2 如果index2存在,一定有二级菜单,如果不存在, 判断index1对应的数据存在不存在
if (index2 >= 0) {
// 2.4 先构建一级菜单的数据
obj = {
title: menus[index1].title,
path: menus[index1].path,
key: menus[index1].key,
icon: menus[index1].icon,
redirect: menus[index1].redirect,
children: []
}
// 2.5
const cindex = result.findIndex(item => item.key === obj.key)
if (cindex === -1) { // 添加二级菜单
obj.children.push({
title: menus[index1].children[index2].title,
path: menus[index1].children[index2].path,
key: menus[index1].children[index2].key,
icon: menus[index1].children[index2].icon,
component: menus[index1].children[index2].component
})
result.push(obj)
} else { // 给既有的一级菜单添加二级菜单
result[cindex].children.push({
title: menus[index1].children[index2].title,
path: menus[index1].children[index2].path,
key: menus[index1].children[index2].key,
icon: menus[index1].children[index2].icon,
component: menus[index1].children[index2].component
})
}
} else {
// 2.3 先构建一级菜单的数据
obj = {
title: menus[index1].title,
path: menus[index1].path,
key: menus[index1].key,
icon: menus[index1].icon,
component: menus[index1].component,
children: []
}
// 判断数据中有没有obj对应的数据,如果没有,则插入,如果有,不做操作
!result.some(item => item.key === obj.key) && result.push(obj)
}
})
// console.log(result)
return result
}
28.动态渲染左侧的菜单栏
// api/users.js
import request from './../utils/request'
export function getAdminList (params) {
return request.get('/admin/list', { params })
}
export function addAdmin (params) {
return request.post('/admin/add', params)
}
export function updateAdmin (params) {
return request.post('/admin/update', params)
}
export function deleteAdmin (params) {
return request.post('/admin/delete', params)
}
export function login (params) {
return request.post('/admin/login', params)
}
export function getAdminDetail (params) {
return request.get('/admin/detail', {params})
}
// layout/main/SideMenu.jsx
import React, { useState, useEffect } from 'react'
import { Menu } from 'antd';
import menus from '../../router/menus'
import { useHistory, useLocation } from 'react-router-dom'
import { getMenu } from './../../router/getMenu'
import { getAdminDetail } from './../../api/users'
const { SubMenu } = Menu;
// submenu keys of first level
const keyArr = []
menus.forEach(item => { // 只需要一级菜单项即可
keyArr.push(item.path)
})
// const rootSubmenuKeys = ['sub1', 'sub2', 'sub4'];
const rootSubmenuKeys = keyArr
const SideMenu = () => {
// 获取地址栏中的地址
const location = useLocation()
const { pathname } = location // /banner/list
console.log(location)
// 用来 编程式跳转
const history = useHistory()
// 设置默认打开哪一项 ---- 参数是 string[] ['/banner']
const [openKeys, setOpenKeys] = React.useState(['/' + pathname.split('/')[1] ]);
const onOpenChange = keys => {
const latestOpenKey = keys.find(key => openKeys.indexOf(key) === -1);
if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
setOpenKeys(keys);
} else {
setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
}
};
const renderMenus = (menus) => {
return menus.map(item => {
// 判断当前的选项有没有子菜单
// ——————————————————*********开启********——————————————
if (item.children && item.children.length > 0) { // 有子级菜单
return (
<SubMenu key={ item.path } icon={ item.icon } title={item.title}>
{/* 递归调用自身,渲染多级菜单 */}
{ renderMenus(item.children) }
</SubMenu>
)
} else {
// path具有唯一性,key也具有唯一性
// 为了后续操作方便,此时建议将path作为遍历的key值
return <Menu.Item key={ item.path } icon = { item.icon }>
{ item.title }
</Menu.Item>
}
})
}
const changeUrl = ({ key }) => {
// console.log(key) // 为什么在渲染菜单栏时 把 item.path 置为 key属性
// console.log(item)
// 编程式跳转 props.history.push
history.push(key)
}
{/*——————————————————*********开启********——————————————*/}
const [myMenu, setMyMenu] = useState([])
useEffect(() => {
const adminname = localStorage.getItem('adminname')
getAdminDetail({
adminname
}).then(res => {
console.log(res.data.data)
if (adminname === 'admin') {
setMyMenu(menus)
} else {
console.log(res.data.data[0].checkedKeys)
const testMenus = getMenu(menus, res.data.data[0].checkedKeys)
setMyMenu(testMenus)
}
})
}, [])
{/*——————————————————*********结束********——————————————*/}
return (
<Menu onClick = { changeUrl } theme="dark"
mode="inline"
openKeys={openKeys}
selectedKeys = { [pathname] }
onOpenChange={onOpenChange} >
{
{/*——————————————————*******myMenu**********——————————————*/}
renderMenus(myMenu)
}
{/* <Menu.Item key="1">Option 1</Menu.Item>
<SubMenu key="sub1" icon={<MailOutlined />} title="Navigation One">
<Menu.Item key="1">Option 1</Menu.Item>
<Menu.Item key="2">Option 2</Menu.Item>
<Menu.Item key="3">Option 3</Menu.Item>
<Menu.Item key="4">Option 4</Menu.Item>
</SubMenu> */}
</Menu>
);
};
export default SideMenu
考虑到在添加管理员时,如果不勾选权限管理项,代码会出错误,就在添加管理员时做判断
// views/users/AdminList.jsx const addAdminFn = () => { const obj = { adminname, password, role, checkedKeys } console.log(obj) if (checkedKeys.length === 0) { // *************************** message.error('请先选择权限管理项') return } addAdmin(obj).then((res) => { if (res.data.code === '10004') { notification.warning({ duration: 2, message: '消息提醒', description: '该管理员已存在', onClick: () => { }, }); } else { // 抽屉消失,表单回归初始状态 setVisible(false) setAdminname('') setPasssword('') setRole('1') setCheckedKeys([]) // 重新渲染一次页面 getAdminList().then(res => { setAdminList(res.data.data) }) } }) }发现左侧菜单渲染时顺序会有变化,给menus.js 每一个一级菜单添加 index 索引值
export function getMenu (menus, checkedKeys) { // console.log(result) result.sort((a, b) => a.index - b.index) // ************************** return result }
29退出
import React from 'react'
import { Layout, Button } from 'antd';
import logo from '../../logo.svg'
import { withRouter } from 'react-router-dom'
import {
MenuUnfoldOutlined,
MenuFoldOutlined
} from '@ant-design/icons';
import SideMenu from './SideMenu';
import RouterView from './RouterView';
const { Header, Sider, Content } = Layout;
class App extends React.Component {
}
export default withRouter(App)
// ************************
30.配置路由 的权限
// layout/main/RouterView.jsx
import React, { Suspense, useState, useEffect } from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
import { Spin } from 'antd';
import menus from './../../router/menus'
import NotFound from './../../views/error/NotFound/Index'
import { getMenu } from './../../router/getMenu'
import { getAdminDetail } from './../../api/users'
function RouterView() {
const renderRoute = (menus) => {
return menus.map(item => {
// ********************************************
if (item.children && item.children.length > 0) {
// 递归调用
return renderRoute(item.children)
} else {
return <Route exact path = { item.path } key = { item.path } component = { item.component } />
}
})
}
const renderRedirect = (menus) => {
return menus.map(item => {
if (item.redirect) {
return <Redirect exact key={ item.key } from = { item.path } to = { item.redirect } />
} else {
return null
}
})
}
// ************************************
const [myMenu, setMyMenu] = useState([])
useEffect(() => {
const adminname = localStorage.getItem('adminname')
getAdminDetail({
adminname
}).then(res => {
console.log(res.data.data)
if (adminname === 'admin') {
setMyMenu(menus)
} else {
// console.log(res.data.data[0].checkedKeys)
const testMenus = getMenu(menus, res.data.data[0].checkedKeys)
console.log(testMenus)
setMyMenu(testMenus)
}
})
}, [])
return (
<Suspense fallback = { <Spin /> }>
<Switch>
{/* <Route path="" component={} /> */}
{
// ***************
renderRoute(myMenu)
}
{
// ***************
renderRedirect(myMenu)
}
<Redirect exact from = "/" to="/home" />
<Route component = { NotFound } />
</Switch>
</Suspense>
)
}
export default RouterView
31.未登录直接访问登录页面
import React from 'react'
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom'
import Main from './layout/main/index.jsx'
import Login from './layout/login/Index.jsx'
function App () {
return (
<Router >
<Switch>
<Route path="/login" exact component = {Login}/>
{/* <Route path="/" component = {Main}/> */}
<Route path = "/" render = { () => {
return localStorage.getItem('loginState') === 'true' ? <Main /> :
<Redirect to="/login" />
}} />
</Switch>
</Router>
)
}
export default App
32.使用token验证
可以通过axios的拦截器统一处理 token信息
import axios from 'axios'
// 判断用户当前运行的环境 开发环境 生产环境
// 访问不同的环境 ,请求不同的地址
// 开发环境 npm run start ---- development
// 生产环境 npm run build ---- production
// process.env.NODE_ENV
const isDev = process.env.NODE_ENV === 'development'
// http://121.89.205.189/admin/banner/add
// 请求是请求 /banner/add
const request = axios.create({ // 自定义axios
// baseURL: 'http://121.89.205.189/admin'
// baseURL: isDev ? 'http://localhost:3001/admin' : 'http://121.89.205.189/admin'
baseURL: isDev ? 'http://121.89.205.189/admin' : 'http://121.89.205.189/admin'
})
// axios 拦截器
// 添加请求拦截器
request.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
config.headers.common.token = localStorage.getItem('adminToken')
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
request.interceptors.response.use(function (response) {
// 对响应数据做点什么
if (response.data.code === '10119') {
window.location.href = "/login"
}
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default request
33.使用redux管理状态
cnpm i redux react-redux redux-thunk -S
// store/modules/user.js
const reducer = (state = {
adminname: localStorage.getItem('adminname') || '',
adminToken: localStorage.getItem('adminToken') || '',
role: localStorage.getItem('role') || 1,
loginState: localStorage.getItem('loginState') === 'true' || false
}, { type, payload }) => {
switch (type) {
case 'CHANGE_ADMIN_NAME':
return { ...state, adminname: payload }
case 'CHANGE_ADMIN_TOKEN':
return { ...state, adminToken: payload }
case 'CHANGE_ROLE':
return { ...state, role: payload }
case 'CHANGE_LOGIN_STATE':
return { ...state, loginState: payload }
default:
return state
}
}
export default reducer
// store/modules/pro.js
const reducer = (state = {
prolist: [],
bannerlist: []
}, { type, payload }) => {
return state
}
export default reducer
// store/index.js
import { createStore, combineReducers } from 'redux'
import user from './modules/user'
import pro from './modules/pro'
const reducer = combineReducers({
user,
pro
})
const store = createStore(reducer)
export default store
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App.jsx';
import { ConfigProvider } from 'antd';
// 由于 antd 组件的默认文案是英文,所以需要修改为中文
import zhCN from 'antd/lib/locale/zh_CN';
import { Provider } from 'react-redux'
import store from './store'
// import { getMenu } from './router/getMenu'
ReactDOM.render(
<ConfigProvider locale = { zhCN }>
<Provider store = { store }>
<App />
</Provider>
</ConfigProvider>,
document.getElementById('root')
);
// getMenu()
多个组件需要使用同一个状态
// App.jsx
import React from 'react'
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom'
import Main from './layout/main/index.jsx'
import Login from './layout/login/Index.jsx'
import { connect } from 'react-redux' // _______******______
function App (props) {
return (
<Router >
<Switch>
<Route path="/login" exact component = {Login}/>
{/* <Route path="/" component = {Main}/> */}
<Route path = "/" render = { () => {
// 导航守卫 - 路由守卫 // _______******______
return props.loginState ? <Main /> :
<Redirect to="/login" />
}} />
</Switch>
</Router>
)
}
// _______******______
const mapStateToProps = (state) => {
return {
loginState: state.user.loginState
}
}
// _______******______
export default connect(mapStateToProps)(App)
来自不同组件的行为需要变更同一个状态
import React, { Component } from 'react'
import { Divider, Space, Input, Button, message } from 'antd'
import { login } from './../../api/users'
import { connect } from 'react-redux'
class App extends Component {
state = {
adminname: '',
password: ''
}
changeAdminname = (e) => this.setState({ adminname: e.target.value })
changePassword = (e) => this.setState({ password: e.target.value })
loginFn = () => {
const params = {
adminname: this.state.adminname,
password: this.state.password
}
this.props.loginFn(params).then(() => {
this.props.history.push('/')
})
/*login(params).then(res => {
if (res.data.code === '10003') {
message.warning('密码错误')
} else if (res.data.code === '10005') {
message.warning('账户不存在')
} else {
console.log(res.data.data)
// 信息存入本地
localStorage.setItem('adminToken', res.data.data.token)
localStorage.setItem('adminname', res.data.data.adminname)
localStorage.setItem('role', res.data.data.role)
localStorage.setItem('loginState', true)
// 跳转到首页
this.props.history.push('/')
}
})*/
}
render() {
return (
<div className="loginContainer">
<div className="loginBox">
<div className="loginLogo">
嗨购管理系统
</div>
<Divider>使用用户名登录</Divider>
<Space direction="vertical" style={{ width: '100%'}}>
<div style={{ width: '100%', height: 40, borderBottom: '1px solid #999'}}>
<Input value = { this.state.adminname } onChange = { this.changeAdminname }
size="large" bordered = { false } placeholder="请输入用户名"/>
</div>
<div style={{ width: '100%', height: 40, borderBottom: '1px solid #999'}}>
<Input value = { this.state.password } onChange = { this.changePassword }
size="large" type="password" bordered = { false } placeholder="请输入密码"/>
</div>
<Button onClick={ this.loginFn } style={{ marginTop: 50}} type="primary" block size="large">登录</Button>
</Space>
</div>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
loginFn (params) {
// 解决完成组件需要后续的业务逻辑
return new Promise((resolve, reject) => {
login(params).then(res => {
if (res.data.code === '10003') {
message.warning('密码错误')
} else if (res.data.code === '10005') {
message.warning('账户不存在')
} else {
console.log(res.data.data)
// 信息存入本地
localStorage.setItem('adminToken', res.data.data.token)
localStorage.setItem('adminname', res.data.data.adminname)
localStorage.setItem('role', res.data.data.role)
localStorage.setItem('loginState', true)
// 给状态管理器存储数据
dispatch({type: 'CHANGE_ADMIN_TOKEN', payload: res.data.data.token})
dispatch({type: 'CHANGE_ADMIN_NAME', payload: res.data.data.adminname})
dispatch({type: 'CHANGE_ROLE', payload: res.data.data.role})
dispatch({type: 'CHANGE_LOGIN_STATE', payload: true})
// 跳转到首页
resolve()
}
})
})
}
}
}
export default connect(null, mapDispatchToProps)(App)
34.使用redux-thunk解决异步问题
分reducer操作不变----- 创建分reducer
创建store时使用redux-thunk中间件
import { createStore, combineReducers, applyMiddleware } from 'redux'
import user from './modules/user'
import pro from './modules/pro'
import thunk from 'redux-thunk' // ******
const reducer = combineReducers({
user,
pro
})
// ******
const store = createStore(reducer, applyMiddleware(thunk))
export default store
创建异步处理操作 store/actionCreator/user.js
import { message } from 'antd'
import { login } from './../../api/users'
const action = {
// fn (params) { // 可以传递参数
// return (dispatch) => { // dispatch 是默认参数,可以触发改变reducer
// }
// }
// fn2(dispatch) { // 不可以传递参数
// }
loginActionFn (params) {
return (dispatch) => {
// 如果含有后续的操作,需要使用promise
return new Promise(resolve => {
login(params).then(res => {
if (res.data.code === '10003') {
message.warning('密码错误')
} else if (res.data.code === '10005') {
message.warning('账户不存在')
} else {
console.log(res.data.data)
// 信息存入本地
localStorage.setItem('adminToken', res.data.data.token)
localStorage.setItem('adminname', res.data.data.adminname)
localStorage.setItem('role', res.data.data.role)
localStorage.setItem('loginState', true)
// 给状态管理器存储数据
dispatch({type: 'CHANGE_ADMIN_TOKEN', payload: res.data.data.token})
dispatch({type: 'CHANGE_ADMIN_NAME', payload: res.data.data.adminname})
dispatch({type: 'CHANGE_ROLE', payload: res.data.data.role})
dispatch({type: 'CHANGE_LOGIN_STATE', payload: true})
// 跳转到首页
resolve()
}
})
})
}
}
}
export default action
组件内触发aciton, layout/login/Index.jsx
import action from './../../store/acitonCreator/user'
class App extends Component {
}
const mapDispatchToProps = (dispatch) => {
return {
loginFn (params) {
// dispatch(action.fn2)
return dispatch(action.loginActionFn(params))
}
}
}
export default connect(null, mapDispatchToProps)(App)
35.数据可视化
echarts
antv
highCharts
d3
react + echarts
cnpm i echarts echarts-for-react -S
创建 views/data/Bar.jsx + Line.jsx
配置路由
import React from 'react'
import {
HomeOutlined,
PictureOutlined,
NotificationOutlined,
UsergroupDeleteOutlined,
SwitcherOutlined
} from '@ant-design/icons'
import Home from './../views/home/Index'
const menus = [
...,
{
index: 5,
title: '数据可视化',
path: '/data',
key: '0-5',
icon: <SwitcherOutlined />,
redirect:"/data/bar",
children: [
{
title: '柱状图',
path: '/data/bar',
key: '0-5-0',
component: React.lazy(() => import('./../views/data/Bar'))
},
{
title: '折线图',
path: '/data/line',
key: '0-5-1',
component: React.lazy(() => import('./../views/data/Line'))
}
]
}
]
export default menus
import React from 'react'
import ReactECharts from 'echarts-for-react'
function Bar() {
const getOption = () => {
return {
title: {
text: '测试',
left: 'center'
},
legend: {
data: ['1销量', '2销量'],
bottom: 'bottom'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
tooltip: {
show: true
},
series: [
{
name: '2销量',
data: [350, 230, 284, 218, 35, 147, 260],
type: 'bar'
}]
}
}
return (
<div>
柱状图
<ReactECharts option={getOption()} />
</div>
)
}
export default Bar
import React, { Component } from 'react'
import ReactECharts from 'echarts-for-react'
export default class Line extends Component {
getOption = () => {
return {
title: {
text: '测试',
left: 'center'
},
legend: {
data: ['1销量', '2销量'],
bottom: 'bottom'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
tooltip: {
show: true
},
series: [
{
name: '2销量',
data: [350, 230, 284, 218, 35, 147, 260],
type: 'line'
}]
};
}
render() {
return (
<div>
折线图
<ReactECharts option={this.getOption()} />
</div>
)
}
}
动态渲染数据
import React, { useState } from 'react'
import ReactECharts from 'echarts-for-react'
function Bar() {
const [series, setSeries] = useState([
{
name: '2销量',
data: [350, 230, 284, 218, 35, 147, 260],
type: 'bar'
}])
const getOption = () => {
return {
title: {
text: '测试',
left: 'center'
},
legend: {
data: ['1销量', '2销量'],
bottom: 'bottom'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
tooltip: {
show: true
},
series: series
}
}
return (
<div>
柱状图
<button onClick={ () => {
setSeries([{
name: '2销量',
data: [550, 430, 184, 118, 135, 547, 160],
type: 'bar'
}])
}}>改变数据</button>
<ReactECharts option={getOption()} />
</div>
)
}
export default Bar
import React, { Component } from 'react'
import ReactECharts from 'echarts-for-react'
export default class Line extends Component {
state = {
series: [
{
name: '2销量',
data: [350, 230, 284, 218, 35, 147, 260],
type: 'line'
}]
}
getOption = () => {
return {
title: {
text: '测试',
left: 'center'
},
legend: {
data: ['1销量', '2销量'],
bottom: 'bottom'
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
tooltip: {
show: true
},
series: this.state.series
};
}
render() {
return (
<div>
折线图
<button onClick = { () => {
this.setState({
series: [{
name: '2销量',
data: [550, 430, 184, 118, 135, 547, 160],
type: 'line'
}]
})
}}>改变数据</button>
<ReactECharts option={this.getOption()} />
</div>
)
}
}
总结
本文详细介绍了在React项目中使用ECharts进行数据可视化的步骤。通过创建Bar.jsx
和Line.jsx
两个组件,我们实现了柱状图和折线图的动态数据更新。同时,通过配置路由,我们实现了数据可视化页面的导航功能。这些步骤不仅帮助开发者快速上手ECharts,还为构建复杂的图表应用提供了参考。希望本文的内容对大家在实际开发中有所帮助。
原文地址:https://blog.csdn.net/2302_76329106/article/details/144018947
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!