详情请点阅读全文
一、布局及排版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import React,{Component} from 'react' import './header.less' export default class Header extends Component { render(){ return ( <div className='header' > <div className='header-top' > <span>欢迎,admin</span> <a href='javascript:'>退出</ a> </div> <div className='header-bottom'> <div className='header-bottom-left'> <span>首页</ span> </div> <div className='header-bottom-right'> <span>2020-2-6 10:10:10</ span> <img src='http://www.weather.com.cn/m2/i/icon_weather/21x15/d14.gif' alt='天气' /> <span>雪</span> </ div> </div> </ div> ) } }
1 2 3 4 5 6 7 8 9 10 11 12 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 48 49 50 .header { height : 80px ; .header-top{ height : 40px ; line-height : 40px ; // 以上两行控制行内文字居中 text-align :right ; border-bottom : 2px solid lightseagreen ; padding-right : 30px ; span { margin-right :10px ; } } .header-bottom { height : 40px ; background-color : white; display :flex; align-items : center; padding :0 30px ; .header-bottom-left{ width : 25%; text-align :center ; font-size : 18px ; position : relative ; //以下用伪元素::after及transform来实现下指三角形 &::after { content : '' ; position : absolute; right : 50% ; top : 100% ; transform : translateX (50% ); border-top : 20px solid white; border-right : 20px solid transparent; border-bottom : 20px solid transparent; border-left : 20px solid transparent; } } .header-bottom-right { width :75% ; text-align :right; img{ margin : 0 10px ; height :4px ; width :3px ; } } } }
效果:
二、天气请求接口函数 1. 基础知识 jsonp原理 1 2 3 4 5 6 7 8 9 10 1 ). jsonp只能解决GET类型的ajax请求跨域问题2 ). jsonp请求不是ajax请求, 而是一般的get 请求3). 基本原理 浏览器端: 动态生成<script>来请求后台接口(src就是接口的url) 定义好用于接收响应数据的函数(fn), 并将函数名通过请求参数提交给后台(如: callback=fn) 服务器端: 接收到请求处理产生结果数据后, 返回一个函数调用的js代码, 并将结果数据作为实参传入函数调用 浏览器端: 收到响应自动执行函数调用的js代码, 也就执行了提前定义好的回调函数, 并得到了需要的结果数据
jsonp本质是get 请求
用来解决跨域问题
浏览器端通过script标签发请求Request URL: http://api.map.baidu.com/telematics/v3/weather?location=%E5%BE%90%E5%B7%9E&output=json&ak=3p49MVra6urFRGOT9s8UBWr2&callback=__jp0
注意最后多了一个callback=__jp0
,它指定了返回时用来处理数据的函数名
服务器返回一个函数执行语句代码类似__jp0&&__jp0({"error":0,"status":"success",...})
在network里查看请求,注意看它的名字和请求时的指定一样1 2 3 4 5 6 7 8 9 10 11 12 13 14 jsonp(url, opts, fn) url (String ) url to fetch opts (Object ), optional param (String ) name of the query string parameter to specify the callback (defaults to callback) timeout (Number ) how long after a timeout error is emitted. 0 to disable (defaults to 60000 ) prefix (String ) prefix for the global callback functions that handle jsonp responses (defaults to __jp) name (String ) name of the global callback functions that handle jsonp responses (defaults to prefix + incremented counter) fn callback The callback is called with err, data parameters. If it times out, the err will be an Error object whose message is Timeout. Returns a function that , when called , will cancel the in -progress jsonp request (fn won't be called).
1.2. promise对象函数,及jsonp的写法 【1】天气接口函数 【2】返回一个promise函数 【3】发送一个jsonp请求,{}内为空意思是使用默认配置选项 【4】异步返回成功数据给调用者 【5】异步返回失败信息给调用者1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export const reqWeather=(city ) => { const url = `http://api.map.baidu.com/telematics/v3/weather?location=${city} &output=json&ak=3p49MVra6urFRGOT9s8UBWr2` return new Promise ((resolve,reject ) => { jsonp(url,{},(err,data) => { if (!err && data.status==='success' ){ resolve({obj}) }else { reject() } }) }) }
1.3. 获取天气信息(支持jsonp) 请求URL: http://api.map.baidu.com/telematics/v3/weather
请求方式: GET
参数类型: |参数 |是否必选 |类型 |说明
|location |Y |string |城市名称
|output |Y |string |返回数据格式: json
|ak |Y |string |唯一的应用key(3p49MVra6urFRGOT9s8UBWr2)
返回示例: {
"error": 0,
"status": "success",
"date": "2019-06-02",
"results": [
{
"currentCity": "北京",
"pm25": "119",
"index": [
{
"des": "建议着长袖T恤、衬衫加单裤等服装。年老体弱者宜着针织长袖衬衫、马甲和长裤。",
"tipt": "穿衣指数",
"title": "穿衣",
"zs": "舒适"
},
{
"des": "不宜洗车,未来24小时内有雨,如果在此期间洗车,雨水和路上的泥水可能会再次弄脏您的爱车。",
"tipt": "洗车指数",
"title": "洗车",
"zs": "不宜"
},
{
"des": "各项气象条件适宜,无明显降温过程,发生感冒机率较低。",
"tipt": "感冒指数",
"title": "感冒",
"zs": "少发"
},
{
"des": "天气较好,赶快投身大自然参与户外运动,尽情感受运动的快乐吧。",
"tipt": "运动指数",
"title": "运动",
"zs": "适宜"
},
{
"des": "紫外线强度较弱,建议出门前涂擦SPF在12-15之间、PA+的防晒护肤品。",
"tipt": "紫外线强度指数",
"title": "紫外线强度",
"zs": "弱"
}
],
"weather_data": [
{
"date": "周日 06月02日 (实时:30℃)",
"dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png",
"nightPictureUrl": "http://api.map.baidu.com/images/weather/night/leizhenyu.png",
"weather": "多云转雷阵雨",
"wind": "西南风3-4级",
"temperature": "31 ~ 20℃"
},
{
"date": "周一",
"dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png",
"nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png",
"weather": "多云",
"wind": "南风微风",
"temperature": "34 ~ 20℃"
},
{
"date": "周二",
"dayPictureUrl": "http://api.map.baidu.com/images/weather/day/leizhenyu.png",
"nightPictureUrl": "http://api.map.baidu.com/images/weather/night/leizhenyu.png",
"weather": "雷阵雨",
"wind": "东风微风",
"temperature": "28 ~ 21℃"
},
{
"date": "周三",
"dayPictureUrl": "http://api.map.baidu.com/images/weather/day/duoyun.png",
"nightPictureUrl": "http://api.map.baidu.com/images/weather/night/duoyun.png",
"weather": "多云",
"wind": "北风3-4级",
"temperature": "33 ~ 19℃"
}
]
}
]
}
2.编写天气请求接口src/api/index.js 【0】借用antd返回信息组件 【1】天气请求接口函数编写 【2】从数据中解构取出图片、天气 【3】异步返回图片、天气给调用函数者 【4】返回错误信息给调用者
1 2 3 4 5 6 7 8 9 10 11 12 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 import ajax from './ajax' import jsonp from 'jsonp' import {message} from 'antd' const BASE = '' export const reqLogin=(username,password )=> ajax(BASE+'login' ,{username,password},'POST' )export const AddUser=(user )=> ajax(BASE+'/manage/user/add' ,user,'POST' )export const reqWeather=(city ) => { const url = `http://api.map.baidu.com/telematics/v3/weather?location=${city} &output=json&ak=3p49MVra6urFRGOT9s8UBWr2` return new Promise ((resolve,reject ) => { jsonp(url,{},(err,data) => { console .log('jsonp()' , err, data) if (!err && data.status==='success' ){ const {dayPictureUrl,weather}=data.results[0 ].weather_data[0 ] resolve({dayPictureUrl,weather}) }else { message.error('天气信息获取失败' ) } }) }) } reqWeather('上海' )
刷新页面即会在控制台输入【1.3 的 返回示例】类似的信息,之所以会直接运行,原因是login页面请求了api,从而触发reqWeather(‘上海’)直接运行
3.1时间格式处理工具编写 时间获取:
时间格式化工具编写 src/utils/dateUtils.js 功能:把【时间获取Date.now()】的时间处理成如下格式 2020-2-6 10:10:10
1 2 3 4 5 6 7 8 9 10 11 12 export function formateDate (time ) { if (!time) return '' let date = new Date (time) return date.getFullYear() + '-' + (date.getMonth() + 1 ) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() }
【1】时间格式化工具 【2】内存中存取用户信息工具 【3】当前时间格式化后的字符串 【4】每过一秒获取一次系统时间 定时器函数setInterval(()=>{},1000)
【5】在第一次render()之后执行一次 一般在此执行异步操作: 发ajax请求/启动定时器 【6】解构state内的数据 【7】获取memoryUtils中的用户名 【8】把变量填入标签内:用户名、当前时间、天气图标、天气
1 2 3 4 5 6 7 8 9 10 11 12 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 48 49 50 51 52 53 54 55 56 57 58 59 60 import React,{Component} from 'react' import './header.less' import {formateDate} from '../../../utils/dateUtils.js' import memoryUtils from '../../../utils/memoryUtils' export default class Header extends Component { state={ curentTime:formateDate(Date .now()), dayPictureUrl:'' , weather:'' , } getTime=() => { this .intervalId = setInterval(() => { let curentTime=formateDate(Date .now()) this .setState({curentTime}) },1000 ) } componentDidMount(){ this .getTime() } render(){ const {curentTime,dayPictureUrl,weather} = this .state const username = memoryUtils.user.username return ( <div className='header' > <div className='header-top' > <span>欢迎,{username}</span> <a href='javascript:'>退出</ a> </div> <div className='header-bottom'> <div className='header-bottom-left'> <span>首页</ span> </div> <div className='header-bottom-right'> <span>{curentTime}</ span> <img src={dayPictureUrl} alt='天气' /> <span>{weather}</span> </ div> </div> </ div> ) } }
效果:2020-2-7 20:36:58 时间会动态变化 四、天气部分 【1】引入接口函数,非默认导 【2】天气小图标地址 【3】天气文字 【4】异步获取天气 【5】渲染之后调用一次天气 【6】解构state内的数据 【7】写入页面
1 2 3 4 5 6 7 8 9 10 11 12 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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 import React,{Component} from 'react' import './header.less' import {formateDate} from '../../../utils/dateUtils.js' import memoryUtils from '../../../utils/memoryUtils' import {reqWeather} from '../../../api/index' export default class Header extends Component { state={ curentTime:formateDate(Date .now()), dayPictureUrl:'' , weather:'' , } getWeather = async () => { const {dayPictureUrl, weather} = await reqWeather('上海' ) this .setState({dayPictureUrl, weather}) } getTime=() => { this .intervalId = setInterval(() => { let curentTime=formateDate(Date .now()) this .setState({curentTime}) },1000 ) } componentDidMount(){ this .getTime() this .getWeather() } render(){ const {curentTime,dayPictureUrl,weather} = this .state const username = memoryUtils.user.username return ( <div className='header' > <div className='header-top' > <span>欢迎,{username}</span> <a href='javascript:'>退出</ a> </div> <div className='header-bottom'> <div className='header-bottom-left'> <span>首页</ span> </div> {/ *【7 】写入页面*/} <div className='header-bottom-right'> <span>{curentTime}</ span> <img src={dayPictureUrl} alt='天气' /> <span>{weather}</span> </ div> </div> </ div> ) } }
效果:
五、显示当前路径模块 【1】用于包装当前组件,使其具有路由的3属性history 【1.1】包装起来,使有路由组件的属性 【2】导入导航配置菜单 【3】根据当前网址,在menuListConfig内找到对应的title 【4】得到当前需要显示的title 【5】显示title
1 2 3 4 5 6 7 8 9 10 11 12 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 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 104 105 import React,{Component} from 'react' import './header.less' import {formateDate} from '../../../utils/dateUtils.js' import memoryUtils from '../../../utils/memoryUtils' import {reqWeather} from '../../../api/index' import {withRouter} from 'react-router-dom' import menuList from '../../../config/menuConfig.js' class Header extends Component { state={ curentTime:formateDate(Date .now()), dayPictureUrl:'' , weather:'' , } getTitle = () => { const path = this .props.location.pathname let title menuList.forEach(item => { if (item.key===path) { title = item.title } else if (item.children) { const cItem = item.children.find(cItem => path.indexOf(cItem.key)===0 ) if (cItem) { title = cItem.title } } }) return title } getWeather = async () => { const {dayPictureUrl, weather} = await reqWeather('上海' ) this .setState({dayPictureUrl, weather}) } getTime=() => { this .intervalId = setInterval(() => { let curentTime=formateDate(Date .now()) this .setState({curentTime}) },1000 ) } componentDidMount(){ this .getTime() this .getWeather() } render(){ const {curentTime,dayPictureUrl,weather} = this .state const username = memoryUtils.user.username const title = this .getTitle() return ( <div className='header' > <div className='header-top' > <span>欢迎,{username}</span> <a href='javascript:'>退出</ a> </div> <div className='header-bottom'> <div className='header-bottom-left'> {/ *【5 】显示title*/} <span>{title}</ span> </div> <div className='header-bottom-right'> <span>{curentTime}</ span> <img src={dayPictureUrl} alt='天气' /> <span>{weather}</span> </ div> </div> </ div> ) } } export default withRouter(Header)
六、退出登录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import {Modal} from 'antd' loginOut=() => { Modal.confirm({ title: '确定要退出登录吗?' , content: '是请点确定,否则点取消' , onOk() { console .log('OK' ); }, onCancel() { console .log('Cancel' ); }, }) }
【1】内存中存取用户信息工具 默认导出,不用加花括号 【2】删除localstorage中的用户登录数据 【3】引入对话框模块 【4】退出登录函数 【5】改成前头函数,因为下面要用到 【6】删除localstorage中登录信息。及内存中登录信息 【7】删除内存中user信息 【8】跳转到登录页面,用替换因为无需退回 【9】调用退出
1 2 3 4 5 6 7 8 9 10 11 12 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 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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 import React,{Component} from 'react' import './header.less' import {formateDate} from '../../../utils/dateUtils.js' import memoryUtils from '../../../utils/memoryUtils' import storageUtils from '../../../utils/storageUtils' import {reqWeather} from '../../../api/index' import {withRouter} from 'react-router-dom' import menuList from '../../../config/menuConfig.js' import {Modal} from 'antd' class Header extends Component { state={ curentTime:formateDate(Date .now()), dayPictureUrl:'' , weather:'' , } getTitle = () => { const path = this .props.location.pathname let title menuList.forEach(item => { if (item.key===path) { title = item.title } else if (item.children) { const cItem = item.children.find(cItem => path.indexOf(cItem.key)===0 ) if (cItem) { title = cItem.title } } }) return title } getWeather = async () => { const {dayPictureUrl, weather} = await reqWeather('上海' ) this .setState({dayPictureUrl, weather}) } getTime=() => { this .intervalId = setInterval(() => { let curentTime=formateDate(Date .now()) this .setState({curentTime}) },1000 ) } loginOut=() => { Modal.confirm({ title: '确定要退出登录吗?' , content: '是请点确定,否则点取消' , onOk:() => { console .log('OK' ); storageUtils.removeUser() memoryUtils.user={} this .props.history.replace('/login' ) } }) } componentDidMount(){ this .getTime(); this .getWeather(); } render(){ const {curentTime,dayPictureUrl,weather} = this .state const username = memoryUtils.user.username const title = this .getTitle() return ( <div className='header' > <div className='header-top' > <span>欢迎,{username}</span> {/ *【9 】*/} <a href='javascript:' onClick={this.loginOut}>退出</ a> </div> <div className='header-bottom'> <div className='header-bottom-left'> <span>{title}</ span> </div> <div className='header-bottom-right'> <span>{curentTime}</ span> <img src={dayPictureUrl} alt='天气' /> <span>{weather}</span> </ div> </div> </ div> ) } } export default withRouter(Header)
效果:
七、警告完善 退出后控制台会提示如下信息: 此为定时器函数未清除造成的问题
1 2 3 4 5 6 7 8 9 10 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method. in Header (created by Context.Consumer) in withRouter(Header) (at admin.jsx:42) in section (created by BasicLayout) in BasicLayout (created by Context.Consumer) in Adapter (at admin.jsx:41) in section (created by BasicLayout) in BasicLayout (created by Context.Consumer) in Adapter (at admin.jsx:37) in Admin (created by Context.Consumer)
解决:清除定时器函数 1 2 3 4 5 6 7 componentWillUnmount () { clearInterval(this .intervalId) }
完整代码 【1】当前组件卸载之前调用清除定时器,避免其造成警告信息
1 2 3 4 5 6 7 8 9 10 11 12 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 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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 import React,{Component} from 'react' import './header.less' import {formateDate} from '../../../utils/dateUtils.js' import memoryUtils from '../../../utils/memoryUtils' import storageUtils from '../../../utils/storageUtils' import {reqWeather} from '../../../api/index' import {withRouter} from 'react-router-dom' import menuList from '../../../config/menuConfig.js' import {Modal} from 'antd' class Header extends Component { state={ curentTime:formateDate(Date .now()), dayPictureUrl:'' , weather:'' , } getTitle = () => { const path = this .props.location.pathname let title menuList.forEach(item => { if (item.key===path) { title = item.title } else if (item.children) { const cItem = item.children.find(cItem => path.indexOf(cItem.key)===0 ) if (cItem) { title = cItem.title } } }) return title } getWeather = async () => { const {dayPictureUrl, weather} = await reqWeather('上海' ) this .setState({dayPictureUrl, weather}) } getTime=() => { this .intervalId = setInterval(() => { let curentTime=formateDate(Date .now()) this .setState({curentTime}) },1000 ) } loginOut=() => { Modal.confirm({ title: '确定要退出登录吗?' , content: '是请点确定,否则点取消' , onOk:() => { console .log('OK' ); storageUtils.removeUser() memoryUtils.user={} this .props.history.replace('/login' ) } }) } componentDidMount(){ this .getTime(); this .getWeather(); } componentWillUnmount () { clearInterval(this .intervalId) } render(){ const {curentTime,dayPictureUrl,weather} = this .state const username = memoryUtils.user.username const title = this .getTitle() return ( <div className='header' > <div className='header-top' > <span>欢迎,{username}</span> <a href='javascript:' onClick={this.loginOut}>退出</ a> </div> <div className='header-bottom'> <div className='header-bottom-left'> <span>{title}</ span> </div> <div className='header-bottom-right'> <span>{curentTime}</ span> <img src={dayPictureUrl} alt='天气' /> <span>{weather}</span> </ div> </div> </ div> ) } } export default withRouter(Header)
/src/component/link-button/index.jsx
1 2 3 4 5 6 import React,{component} from 'react' import './index.less' export default function LinkButton (props ) { return <button {...props } className ='link-button' > </button > }
index.less
1 2 3 4 5 6 7 .link-button{ background-color: transparent; border: none; outline: none; color: #1da57a; cursor: pointer; }
【1】引入自定按键 【2】使用自定义组件
1 2 3 4 5 6 7 8 9 10 11 12 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 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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 import React,{Component} from 'react' import './header.less' import {formateDate} from '../../../utils/dateUtils.js' import memoryUtils from '../../../utils/memoryUtils' import storageUtils from '../../../utils/storageUtils' import {reqWeather} from '../../../api/index' import {withRouter} from 'react-router-dom' import menuList from '../../../config/menuConfig.js' import {Modal} from 'antd' import LinkButton from '../../../components/link-button/index' class Header extends Component { state={ curentTime:formateDate(Date .now()), dayPictureUrl:'' , weather:'' , } getTitle = () => { const path = this .props.location.pathname let title menuList.forEach(item => { if (item.key===path) { title = item.title } else if (item.children) { const cItem = item.children.find(cItem => path.indexOf(cItem.key)===0 ) if (cItem) { title = cItem.title } } }) return title } getWeather = async () => { const {dayPictureUrl, weather} = await reqWeather('上海' ) this .setState({dayPictureUrl, weather}) } getTime=() => { this .intervalId = setInterval(() => { let curentTime=formateDate(Date .now()) this .setState({curentTime}) },1000 ) } loginOut=() => { Modal.confirm({ title: '确定要退出登录吗?' , content: '是请点确定,否则点取消' , onOk:() => { console .log('OK' ); storageUtils.removeUser() memoryUtils.user={} this .props.history.replace('/login' ) } }) } componentDidMount(){ this .getTime(); this .getWeather(); } componentWillUnmount () { clearInterval(this .intervalId) } render(){ const {curentTime,dayPictureUrl,weather} = this .state const username = memoryUtils.user.username const title = this .getTitle() return ( <div className='header' > <div className='header-top' > <span>欢迎,{username}</span> {/ *【2 】使用自定义组件*/} <LinkButton href='javascript:' onClick={this.loginOut}>退出</ LinkButton> </div> <div className='header-bottom'> <div className='header-bottom-left'> <span>{title}</ span> </div> <div className='header-bottom-right'> <span>{curentTime}</ span> <img src={dayPictureUrl} alt='天气' /> <span>{weather}</span> </ div> </div> </ div> ) } } export default withRouter(Header)
效果: