目录
总结
1 项目创建
1.2 命令创建
create-react-app admin
1.3 模块分类
1.3.1 模块功能
- assets 存放静态文件的文件目录,比如图片
- Components 存放组件,比如布局组件,登录组件
- Pages 存放页面,开发中的每一个页面
- Routers 存放页面跳转的路由
1.4 使用antd UI
1.4.1 安装antd
使用框架 cnpm i antd -S
使用图标 cnpm install --save @ant-design/icons
1.4.2 配置antd
在App.css中添加 @import '~antd/dist/antd.css';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hPhRgzmD-1620631198088)(C:\Users\皮卡丘\AppData\Roaming\Typora\typora-user-images\image-20210118113743379.png)]
1.5 使用less
1.5.1 安装less
cnpm i sass node-sass -S
1.5.2 配置使用
直接创建less文件,编写less样式就可以使用
1.6 使用路由
1.6.1 安装router
cnpm i react-router-dom react-router -S
1.6.2 使用路由
直接引入路由即可使用
1.7 使用redux
1.7.1 安装redux
cnpm i redux react-redux react-hot-loader -S
1.7.2 使用redux
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jo1dEktl-1620631198091)(C:\Users\皮卡丘\AppData\Roaming\Typora\typora-user-images\image-20210118144228286.png)]
-
Actions 封装的行为
import {ADD_COUNT,SUB_COUNT } from '../Types' export const addCount = value=> { return{ type:ADD_COUNT, value } } export const subCount = value => { return{ type:SUB_COUNT, value } }
-
reducer 出来行为的中间件
import {ADD_COUNT,SUB_COUNT} from '../Types' const defaultProp = { count:0 } export default (state=defaultProp,action)=>{ switch (action.type) { case ADD_COUNT: const newState = JSON.parse(JSON.stringify(state)) newState.count += action.value return newState case SUB_COUNT: const subState = JSON.parse(JSON.stringify(state)) subState.count -= action.value return subState default: return state } }
-
Types 全局常量
export const ADD_COUNT = 'ADD_COUNT'
export const SUB_COUNT = 'SUB_COUNT'
- App.js
import React,{Component} from 'react'
import {Button} from 'antd'
import {connect} from 'react-redux'
import './App.css'
import {addCount,subCount} from './Store/Actions'
class APP extends Component{
constructor() {
super();
}
render() {
return (
<div>
<Button type='primary' onClick={this.props.subCount}>-</Button>
<span style={{margin:'0 10px',
fontSize:20}}>{this.props.count}</span>
<Button type='primary' onClick={this.props.addCount}>+</Button>
</div>
)
}
}
const mapProps = state =>{
return{
count:state.count
}
}
const mapActions = dispatch =>{
return{
addCount(){
dispatch(addCount(4))
},
subCount(){
dispatch(subCount(2))
}
}
}
export default connect(mapProps,mapActions)(APP)
1.8 使用请求
1.8.1 安装redux-thunk
cnpm i redux-thunk axios -S
1.8.2 配置使用
-
Actions中 编写请求
export const getList = value => { return{ type:GET_LIST, value } } export const getTodos = number => dispatch => { axios .get('http://jsonplaceholder.typicode.com/todos') .then(todos => { // 把actions 发送给 reducer console.log(todos) dispatch(getList(todos)) }) }
-
reducer 处理行为
case GET_LIST: const newList = JSON.parse(JSON.stringify(state)) newList.todos = action.value.data return newList
-
store/index.js
import { createStore, applyMiddleware, compose } from 'redux' import reducer from './reducer' import thunk from 'redux-thunk'; const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const enhancer = composeEnhancers( applyMiddleware(thunk), ); const store = createStore( reducer,//构建初始的数据 enhancer ) export default store
-
App.js 中使用
import React,{Component} from 'react'
import {Button} from 'antd'
import {connect} from 'react-redux'
import './App.css'
import {addCount,subCount,getTodos} from './Store/Actions'
class APP extends Component{
constructor() {
super();
}
componentDidMount() {
this.props.getTodo()
}
render() {
return (
<div>
<Button type='primary' onClick={this.props.subCount}>-</Button>
<span style={{margin:'0 10px',
fontSize:20}}>{this.props.count}</span>
<Button type='primary' onClick={this.props.addCount}>+</Button>
</div>
)
}
}
const mapProps = state =>{
return{
count:state.count
}
}
const mapActions = dispatch =>{
return{
addCount(){
dispatch(addCount(4))
},
subCount(){
dispatch(subCount(2))
},
getTodo(){
dispatch(getTodos())
}
}
}
export default connect(mapProps,mapActions)(APP)
2 项目开发
- 模拟数据’json-server’
- 查 get
- 增 post
- 删 delete
- 改 patch
2.1 登录页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jozzqpbk-1620631198093)(C:\Users\皮卡丘\AppData\Roaming\Typora\typora-user-images\image-20210118175110731.png)]
- login.jsx
import React,{Component} from 'react'
import './index.scss'
import logo from '../../assets/logo.svg'
import {Card, Form, Input, Button, Checkbox, message} from "antd";
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import {logins} from '../../Store/Actions'
import {connect} from 'react-redux'
class Login extends Component{
onFinish = async (values) => {
let data =this.props.loginRes
let temp = {}
data.forEach(el=>{
if(el.name===values.username){
temp=el
}
})
if(temp.name===values.username && temp.password===values.password){
message.success('登录成功')
window.location.pathname='/home'
}else {
message.error('用户名或者密码不正确')
}
}
componentDidMount() {
this.props.login()
}
render() {
return(
<div className={'login'}>
<Card className={'loginBox'}>
<img src={logo}/>
<Form
name="normal_login"
className="login-form"
initialValues={{
remember: true,
username:'admin',
password:'123456'
}}
onFinish={this.onFinish}
>
<Form.Item
name="username"
rules={[
{
required: true,
message: '请输入账号名称!',
},
]}
>
<Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="账号" />
</Form.Item>
<Form.Item
name="password"
rules={[
{
required: true,
message: '请输入账号密码!',
},
]}
>
<Input
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="密码"
/>
</Form.Item>
<Form.Item>
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>自动登录</Checkbox>
</Form.Item>
</Form.Item>
<Form.Item>
<Button style={{width:'100%',borderRadius:'6px'}} type="primary" htmlType="submit" className="login-form-button">
登录
</Button>
</Form.Item>
</Form>
</Card>
</div>
)
}
}
const mapProps = state =>{
return({
count:state.count,
loginRes:state.loginRes
})
}
const mapActions = dispatch =>{
return{
login(){
dispatch(logins())
}
}
}
export default connect(mapProps,mapActions)(Login)
- app.js
import React,{Component} from 'react'
import './App.css'
import {BrowserRouter as Router,Route} from 'react-router-dom'
import {Login,Home} from './Components'
class APP extends Component{
render() {
return (
<Router>
<div className={'app'}>
<Route path={'/'} component={Login} exact/>
<Route path={'/login'} component={Login} />
<Route path={'/home'} component={Home} />
</div>
</Router>
)
}
}
export default APP
2.2 首页开发
-
路由编写
import React from 'react' import {All,Device,Interface,Operation,Setting,Profiles} from '../Pages' import { DashboardOutlined, RocketOutlined, SnippetsOutlined, FileTextOutlined, WifiOutlined, SettingOutlined } from '@ant-design/icons'; export const RouterList=[ { id:1, name:'概览', path:'/index', component:All, icon:<DashboardOutlined /> }, { id:2, name:'设备', path:'/device', component:Device, icon:<RocketOutlined /> }, { id:3, name:'设备Profiles', path:'/profiles', component:Profiles, icon:<SnippetsOutlined /> }, { id:4, name:'操作日志', path:'/operation', component:Operation, icon:<FileTextOutlined /> }, { id:5, name:'数据接口', path:'/data', component:Interface, icon:<WifiOutlined /> }, { id:6, name:'设置', path:'/setting', component:Setting, icon:<SettingOutlined /> }, ]
-
首页开发
import React,{Component} from "react"; import { Layout, Menu } from 'antd'; import { MenuUnfoldOutlined, MenuFoldOutlined, } from '@ant-design/icons'; import './index.scss' import {RouterList} from '../../Routers' import {HashRouter as Router, Route, Link, Redirect} from 'react-router-dom' const { Header, Sider, Content } = Layout; export default class Home extends Component{ constructor() { super(); this.state={ collapsed: false } } toggle = () => { this.setState({ collapsed: !this.state.collapsed, }); }; render() { return( <Router> <div className={'layout'}> <Layout> <Sider trigger={null} collapsible collapsed={this.state.collapsed}> <div className="logo" /> <Menu theme="dark" mode="inline" defaultSelectedKeys={['1']} style={{height:'90vh'}}> { RouterList.map((el,index)=>{ return( <Menu.Item key={el.id} icon={el.icon}> <Link to={el.path}>{el.name}</Link> </Menu.Item> ) }) } </Menu> </Sider> <Layout className="site-layout"> <Header className="site-layout-background" style={{ padding: 0,color:'#fff',backgroundColor:'#001529',height:'68px' }}> {React.createElement(this.state.collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, { className: 'trigger', onClick: this.toggle, })} </Header> <Content className="site-layout-background" style={{ margin: '24px 16px', padding: 24, minHeight: '80vh', }} > {/*<Redirect to={'/'} />*/} { RouterList.map((el,index)=>{ return( <Route path={el.path} component={el.component} key={el.id}/> ) }) } </Content> </Layout> </Layout> </div> </Router> ) } }
2.3 模块开发
1 数据接口
2 首页
-
使用图表
cnpm install echarts --save
-
按时间查询
cnpm install moment -S
2.4 表格数据导出
- 下载依赖
cnpm i react-html-table-to-excel -S
<div>
<Table columns={this.state.columns} dataSource={this.state.data} pagination/>
<table ref="table" id='table-to-xls' style={{display:"none"}}>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
{
this.state.dataNone.map((el,index)=>{
return (
<tr>
<td>{index+1}</td>
<td>{el}</td>
</tr>
)
})
}
</table>
</div>
- 导出
export=()=>{
const tableCon = ReactDOM.findDOMNode(this.refs['table'])
const table = tableCon.querySelector('table')
// table.setAttribute('id', 'table-to-xls')
console.log(table)
}
3 跨域错误
-
const proxy = require('http-proxy-middleware') module.exports = function(app) { app.use( proxy.createProxyMiddleware('/api', { //`api`是需要转发的请求 target: 'http://172.30.195.14:8639', // 这里是接口服务器地址 changeOrigin: true, pathRewrite: {'^/api':'/'} }) ) }
-
cnpm i cross-env http-proxy-middleware -S
-
"start": "cross-env PORT=8639 react-scripts start",