react实现路由跳转拦截功能(导航守卫)

  • 背景
  • 方法1:通过Prompt组件实现react路由跳转拦截功能
    • Prompt组件介绍
    • Prompt组件示例
    • 自定义Prompt组件的提示弹窗
  • 方法2:通过history.block实现react路由跳转拦截功能
    • history.block介绍
    • history.block基本示例
    • history.block使用示例(自定义弹窗)
  • 文章参考

背景

最近接到一个需求,当用户将要离开指定页面的时候,需要拦截页面的跳转,并弹出提示框,提醒用户去做某一项操作(比如数据的保存和提交),只有当用户完成操作之后,或者关闭提示窗,才能离开此页面进行下一页面的跳转。
这样的需求,通常做法是:监听路由的跳转操作,阻塞跳转,实现拦截,并在用户处理完需要的操作之后(如数据保存、提交、关闭弹窗),才放开跳转权限,允许用户跳转下一页面。

方法1:通过Prompt组件实现react路由跳转拦截功能

如果前端项目是使用vue来编写,我们可以很快处理这一问题,因为vue自带的导航守卫Api如beforeRouteLeave就可以实现该功能。

但是react并没有提供像vue一样的导航守卫Api,因此我们需要另辟蹊径。react-router-dom提供了Prompt组件,通过在需要进行路由跳转拦截的页面的任意地方加上Prompt组件,我们都能实现路由跳转拦截。

Prompt组件介绍

<Prompt when={true} message={(location) => { return '信息还没保存,确定离开吗?' }} />

Prompt接受两个属性:

  1. when(非必传),数据类型为boolean值。
    when=true时,阻塞路由跳转(如果message为字符串或者message的方法返回字符串,还会弹出提示弹窗,弹窗信息为message的字符串值或者message方法返回的字符串);
    when=false,不阻塞路由跳转,也不弹出提示弹窗(当when=false时,即使message为字符串或者message的方法返回字符串,也不会弹出提示窗)。
    注意:只有当when=true的时候,才会执行message的方法。

  2. message(必传),数据类型可以是字符串或方法
    message可以是字符串或方法,如果message为方法,那么该方法会接收一个location参数,参数包含将要跳转的下一个路由的路径等信息(如果message为方法,那么该方法主要用于处理路由拦截后的操作)。
    前提是当when=true时,message为字符串,则会阻塞路由并弹出提示弹窗,弹窗内容为message字符串。
    前提是当when=true时,message为方法,当方法返回true就顺利跳转;返回false则阻塞路由跳转(不会弹出提示弹窗);当方法返回字符串就阻塞路由跳转,并弹出提示弹窗,弹窗提示内容为方法返回的字符串。

  3. Prompt对路由拦截的作用只会作用于其所挂载的当前路由,当跳转到另一个路由(或者Prompt组件被销毁的时候),该Prompt组件将不会再对路由跳转有拦截作用,除非重新挂载和初始化Prompt组件

Prompt组件示例

接下来我们看看在react中如何使用Prompt组件实现路由拦截:
页面上任意位置挂载Prompt 组件,点击“路由跳转”按钮,触发路由跳转事件

import React, { Component } from 'react';
import { Button } from 'antd';
import { withRouter, Prompt } from 'react-router-dom'; // 从react-router-dom中引出Prompt组件class index extends Component {state = {}// 跳转路由handleRouterSwitch = () => {this.props.history.push('/outside')}render() {return (<div><Button onClick={this.handleRouterSwitch}>跳转路由</Button>{/* 页面的任何地方加上Prompt组件都生效 */}<Prompt when={true} message="信息还没保存,确定离开吗?" /></div>)}
}export default withRouter(index)

接下来我们看看Prompt组件的when和message属性的不同属性值,在触发路由跳转时,会发生什么:

① 只赋值when属性,不赋值message属性,页面会报错,提示message属性应必传

<Prompt when={true} />


② when=true,message为字符串,当触发路由跳转事件时,路由跳转会被拦截,并弹出提示窗,提示信息为message的值,点击提示窗确定按钮,路由被释放,跳转到指定页面;点击取消按钮,路由跳转被取消,同时关闭提示窗

<Prompt when={true} message="信息还没保存,确定离开吗?" />


③ when=false,message为字符串,当触发路由跳转事件时,路由跳转不会被拦截,路由会跳转到指定页面。这是因为只有当when=true的时候,才会执行message的方法。

<Prompt when={false} message="信息还没保存,确定离开吗?" />

④ when=true,message为方法,且方法返回值为字符串,当触发路由跳转事件时,路由跳转会被拦截,并弹出提示窗,提示信息为message的方法返回的字符串,点击提示窗确定按钮,路由被释放,跳转到指定页面;点击取消按钮,路由跳转被取消,同时关闭提示窗

<Prompt when={true} message={(location) => {return '信息还没保存,确定离开吗?'}} />


⑤ when=false,message为方法,且方法返回值为字符串,同时message方法内还包含如console.log(‘跳转’)语句。当触发路由跳转事件时,路由跳转不会被拦截,路由会跳转到指定页面。且message方法未被执行。这是因为只有当when=true的时候,才会执行message的方法。

<Prompt when={false} message={(location) => {console.log(location, '跳转');return '信息还没保存,确定离开吗?';
}} />

⑥ when=true,message为方法,且方法返回值为true,同时message方法内还包含如console.log(‘跳转’)语句。当触发路由跳转事件时,message方法会被执行,路由跳转不会被拦截,路由会跳转到指定页面。

<Prompt when={true} message={(location) => {console.log(location, '跳转');return true;
}} />


⑦ when=true,message为方法,且方法返回值为false,同时message方法内还包含如console.log(‘跳转’)语句。当触发路由跳转事件时,message方法会被执行,路由跳转会被拦截,但不会弹出提示框。

<Prompt when={true} message={(location) => {console.log(location, '跳转');return false;
}} />


⑧ 接下来我们演示一下项目中通过Prompt组件拦截用户跳转路由。
需求:

  1. 用户跳转页面的时候,判断用户是否保存了用户数据,如果没有,则阻塞跳转,弹出确认提示。
  2. 用户跳转页面的时候,判断用户是否是跳转了不让跳转的特殊页面,如果没有,则阻塞跳转,弹出确认提示。
    做法:
    Prompt的when属性值变量放于state中,message赋值为方法
    注意:
    当when的属性值,初始化必须为true,否则阻塞不了路由跳转。因为setState是异步操作
import React, { Component } from 'react';
import { Button } from 'antd';
import { withRouter, Prompt } from 'react-router-dom'; // 从react-router-dom中引出Prompt组件class index extends Component {state = {isUserInfoSaved: false,isHoldUpRouter: true, // when的属性值,初始化必须为true,否则阻塞不了路由跳转。因为setState是异步操作}// 跳转路由handleRouterSwitch = () => {this.props.history.push('/settings/audit_rules')}// 保存用户信息saveUserInfo = () => {this.setState({isUserInfoSaved: true})}// 处理路由拦截handleRouterHoldUp = (location) => {const { isUserInfoSaved } = this.state;if (!isUserInfoSaved) { // 信息未保存,阻塞路由跳转,并弹出提示弹窗return '信息还没保存,确定离开吗?'} else if (location.pathname.indexOf('errorUrl') > -1) { // 阻塞跳转到特定的页面,并弹出提示弹窗return '禁止跳转到指定的errorUrl页面,确定继续跳转吗'} else {return true; // 符合跳转的条件,释放路由,路由正常跳转}}render() {const { isHoldUpRouter } = this.state;return (<div><Button onClick={this.handleRouterSwitch}>跳转路由</Button><Button onClick={this.saveUserInfo}>保存信息</Button>{/* 页面的任何地方加上Prompt组件都生效 */}<Prompt when={isHoldUpRouter} message={this.handleRouterHoldUp} /></div>)}
}export default withRouter(index);

效果:
当用户点击“路由跳转”按钮,阻塞路由跳转,弹出提示窗,提示内容为message的函数返回的字符串,当用户点击“保存信息”按钮后,再次点击路由跳转按钮,路由成功跳转,不再弹出提示窗

自定义Prompt组件的提示弹窗

从上述示例我们可以看到,Prompt阻塞路由跳转之后弹出的提示弹窗,样式是系统默认的,我们并不可以修改,那么我们能不能自定义提示弹窗呢?接下来我们演示一下如何自定义Prompt的提示弹窗
需求:

  1. 用户跳转页面的时候,判断用户是否保存了用户数据,如果没有,则阻塞跳转,弹出确认提示;如果已保存,则正常跳转路由。
  2. 确认提示框使用自定义提示框
  3. 点击弹窗“取消”按钮,关闭弹窗,不跳转页面;点击弹窗“确定”按钮,关闭弹窗,跳转页面;
    做法:
    Prompt的when属性值变量放于state中,message赋值为方法。其中message方法主要用于处理路由拦截后的操作,为了不让默认的系统弹窗弹出,而是弹出我们自己的自定义弹窗,因此这时候message方法只能始终return false。这样的话,我们主要操作Prompt的when属性值变化,来拦截或者释放路由的跳转。
    注意:
    当when的属性值,初始化必须为true,否则阻塞不了路由跳转,因为setState是异步操作;同时,当setState改变when的属性值之后,因为setState是异步操作,需要在setState的回调上手动跳转指定页面的路由,这样才能在处理完业务逻辑之后,立马同步跳转路由,否则由于是异步更新state,用户需要点击两次跳转按钮才能跳转路由。
import React, { Component } from 'react';
import { Button, Modal } from 'antd';
import { withRouter, Prompt } from 'react-router-dom'; // 从react-router-dom中引出Prompt组件
import { ExclamationCircleOutlined } from '@ant-design/icons'class index extends Component {state = {isUserInfoSaved: false,isHoldUpRouter: true, // when的属性值,初始化必须为true,否则阻塞不了路由跳转。因为setState是异步操作whichPathUrlWillTo: '',isShowSavePromptModal: false,}// 跳转路由handleRouterSwitch = () => {this.props.history.push('/settings/audit_rules')}// 保存用户信息saveUserInfo = () => {this.setState({isUserInfoSaved: true,})}// 处理路由拦截handleRouterHoldUp = (location) => {console.log(location)const { isUserInfoSaved } = this.state;const pathUrl = location.pathname + location.search // // location 携带的路径,即将要跳转的路径this.setState({whichPathUrlWillTo: pathUrl, // 存储即将要跳转的pathUrl})if (!isUserInfoSaved) { // 信息未保存,阻塞路由跳转,并弹出自定义提示弹窗this.setState({isShowSavePromptModal: true,});} else {this.setState({ // 释放路由跳转权限isHoldUpRouter: false}, () => {this.props.history.push(pathUrl) // 手动跳转,如果是手动跳转,必须放在这里执行,因为setState是异步的,如果不放回调里执行手动跳转,会陷入Prompt组件的死循环this.setState({ // 内部路由变化,当跳转之后,还需要重新关闭路由跳转权限,实现下一次跳转路由的拦截(如果所在页面的组件已经完全销毁,则不需要重新关闭路由跳转权限)isHoldUpRouter: true,})});}return false;}// 处理保存信息提示弹窗的确认事件handleSaveModelOK = () => {this.setState({isShowSavePromptModal: false, // 关闭自定义提示窗isHoldUpRouter: false, // 释放路由跳转权限},() => {this.props.history.push(this.state.whichPathUrlWillTo) // 手动跳转,如果是手动跳转,必须放在这里执行,因为setState是异步的,如果不放回调里执行手动跳转,会陷入Prompt组件的死循环this.setState({ // 内部路由变化,当跳转之后,还需要重新关闭路由跳转权限,实现下一次跳转路由的拦截(如果所在页面的组件已经完全销毁,则不需要重新关闭路由跳转权限)isHoldUpRouter: true,})})}// 处理保存信息提示弹窗的取消事件handleSaveModelCancel = () => {this.setState({isShowSavePromptModal: false, // 关闭自定义提示窗})}render() {const { isHoldUpRouter, isShowSavePromptModal } = this.state;return (<div><Button onClick={this.handleRouterSwitch}>跳转路由</Button><Button onClick={this.saveUserInfo}>保存信息</Button>{/* 页面的任何地方加上Prompt组件都生效 */}<Prompt when={isHoldUpRouter} message={this.handleRouterHoldUp} /><Modal title="提示" closable={false} visible={isShowSavePromptModal} onOk={this.handleSaveModelOK} onCancel={this.handleSaveModelCancel}><div style={{fontSize: '14px'}}><ExclamationCircleOutlined /><span style={{marginLeft: '10px'}}>信息还没保存,确定离开吗?</span></div></Modal></div>)}
}export default withRouter(index);

方法2:通过history.block实现react路由跳转拦截功能

history.block介绍

我们可用withrouter把histroy注入props,用history.block阻塞路由跳转。

当history.block的回调函数返回true,则释放路由跳转;
当history.block的回调函数返回false,则阻塞路由跳转,不弹出弹窗;
当history.block的回调函数返回字符串,则阻塞路由跳转,弹出弹窗,弹窗提示信息为回调函数的返回字符串

history.block的回调函数接受location参数,location参数包含即将要跳转到指定路径的路由信息

千万要注意的是:history.block的作用对项目是全局影响的,只要history.block初始化一次,就会对所有的路由跳转做拦截,即使跳出了当前路由,在另一个路由做跳转的时候,history.block依旧会生效,并起到路由拦截作用。如果想取消history.block的路由跳转拦截作用,只有对其重新初始化,让其回调函数return true,所以通常做法是,在组件的componentWillUnmount这一生命周期对history.block重新初始化,让其回调函数return true,取消history.block的路由跳转拦截作用

history.block基本示例

接下来我们看看在react中如何使用history.block实现路由拦截:
在组件的componentDidMount生命周期中初始化history.block,其回调函数返回字符串,这会使得在该页面做路由跳转的时候会被拦截,并弹出提示窗,提示信息为其回调函数返回的字符串

在组件的componentWillUnmount这一生命周期对history.block重新初始化,让其回调函数return true,取消history.block的路由跳转拦截作用,防止其影响其他页面做路由跳转

import React, { Component } from 'react';
import { Button } from 'antd';
import { withRouter } from 'react-router-dom';class index extends Component {state = {}componentDidMount() {this.props.history.block(location => { // 当history.block的回调函数返回true,则释放路由跳转;当history.block的回调函数返回false,则阻塞路由跳转,不弹出弹窗;当history.block的回调函数返回字符串,则阻塞路由跳转,弹出弹窗,弹窗提示信息为回调函数的返回字符串(点击确定,释放路由,继续跳转到指定页面,点击取消,关闭弹窗,继续阻塞路由跳转)console.log(location) // history.block的回调函数接手location参数,location参数包含即将要跳转到指定路径的路由信息// return true;// return false;return '信息还没保存,确定离开吗?'});}componentWillUnmount() { // history.block的作用对项目是全局影响的,组件的componentWillUnmount一定要记得重新初始化history.block,让其回调函数返回true,取消history.block的路由跳转拦截作用,防止其影响其他页面做路由跳转this.props.history.block(location => { // 当history.block的回调函数返回true,则释放路由跳转;当history.block的回调函数返回false,则阻塞路由跳转,不弹出弹窗;当history.block的回调函数返回字符串,则阻塞路由跳转,弹出弹窗,弹窗提示信息为回调函数的返回字符串(点击确定,释放路由,继续跳转到指定页面,点击取消,关闭弹窗,继续阻塞路由跳转)console.log(location) // history.block的回调函数接手location参数,location参数包含即将要跳转到指定路径的路由信息return true;// return false;// return '信息还没保存,确定离开吗?'});}// 跳转路由handleRouterSwitch = () => {this.props.history.push('/outside')}render() {return (<div><Button onClick={this.handleRouterSwitch}>跳转路由</Button></div>)}
}export default withRouter(index);

点击“路由跳转”按钮,触发路由跳转事件,history.block阻塞路由跳转,并弹出提示窗,提示信息为history.block回调函数return的字符串

点击弹窗“确定”按钮,释放路由,继续跳转到目标url
点击弹窗“取消”按钮,关闭弹窗,路由不做跳转

history.block使用示例(自定义弹窗)

从上述示例我们可以看到,history.block阻塞路由跳转之后弹出的提示弹窗,样式是系统默认的,我们并不可以修改,那么我们能不能自定义提示弹窗呢?接下来我们演示一下如何自定义history.block的提示弹窗
需求:

  1. 用户跳转页面的时候,判断用户是否保存了用户数据,如果没有,则阻塞跳转,弹出确认提示;如果已保存,则正常跳转路由。
  2. 确认提示框使用自定义提示框
  3. 点击弹窗“取消”按钮,关闭弹窗,不跳转页面;点击弹窗“确定”按钮,关闭弹窗,跳转页面;

做法:
history.block只有在其回调函数返回值为字符串的时候,才会弹出系统弹窗,而返回布尔值的时候,不会弹出弹窗,我们仅仅需要history.block的路由拦截功能,不让其弹出系统弹窗,而是弹出自定义弹窗,那么我们让history.block的回调函数返回true就可以释放路由,返回false就可以拦截路由,在返回false的同时,展示自定义的弹窗。通过重新初始化history.block,操作history.block的回调函数返回值,来拦截或者释放路由的跳转。

import React, { Component } from 'react';
import { Button, Modal } from 'antd';
import { withRouter } from 'react-router-dom';
import { ExclamationCircleOutlined } from '@ant-design/icons'class index extends Component {state = {isUserInfoSaved: false,whichPathUrlWillTo: '',isShowSavePromptModal: false,}componentDidMount() {this.props.history.block(this.handleRouterHoldUp);}componentWillUnmount() { // history.block的作用对项目是全局影响的,组件的componentWillUnmount一定要记得重新初始化history.block,让其回调函数返回true,取消history.block的路由跳转拦截作用,防止其影响其他页面做路由跳转this.props.history.block(location => { // 当history.block的回调函数返回true,则释放路由跳转;当history.block的回调函数返回false,则阻塞路由跳转,不弹出弹窗;当history.block的回调函数返回字符串,则阻塞路由跳转,弹出弹窗,弹窗提示信息为回调函数的返回字符串(点击确定,释放路由,继续跳转到指定页面,点击取消,关闭弹窗,继续阻塞路由跳转)console.log(location) // history.block的回调函数接手location参数,location参数包含即将要跳转到指定路径的路由信息return true;// return false;// return '信息还没保存,确定离开吗?'});}// 跳转路由handleRouterSwitch = () => {this.props.history.push('/machine_learning/permission_settings')}// 保存用户信息saveUserInfo = () => {this.setState({isUserInfoSaved: true,})}// 处理路由拦截handleRouterHoldUp = (location) => {console.log(location)const { isUserInfoSaved } = this.state;const pathUrl = location.pathname + location.search // // location 携带的路径,即将要跳转的路径this.setState({whichPathUrlWillTo: pathUrl, // 存储即将要跳转的pathUrl})if (!isUserInfoSaved) { // 信息未保存,阻塞路由跳转,并弹出自定义提示弹窗this.setState({isShowSavePromptModal: true,});} else {this.props.history.block(location => { // 重新初始化history.block,释放路由跳转权限return true;});this.props.history.push(pathUrl) // 手动跳转}return false;}// 处理保存信息提示弹窗的确认事件handleSaveModelOK = () => {this.setState({isShowSavePromptModal: false, // 关闭自定义提示窗})this.props.history.block(location => { // 重新初始化history.block,释放路由跳转权限return true;});this.props.history.push(this.state.whichPathUrlWillTo) // 手动跳转}// 处理保存信息提示弹窗的取消事件handleSaveModelCancel = () => {this.setState({isShowSavePromptModal: false, // 关闭自定义提示窗})}render() {const { isShowSavePromptModal } = this.state;return (<div><Button onClick={this.handleRouterSwitch}>跳转路由</Button><Button onClick={this.saveUserInfo}>保存信息</Button><Modal title="提示" closable={false} visible={isShowSavePromptModal} onOk={this.handleSaveModelOK} onCancel={this.handleSaveModelCancel}><div style={{fontSize: '14px'}}><ExclamationCircleOutlined /><span style={{marginLeft: '10px'}}>信息还没保存,确定离开吗?</span></div></Modal></div>)}
}export default withRouter(index);

文章参考

https://www.cnblogs.com/tirybk/p/14688240.html
https://www.cnblogs.com/amiezhang/p/13207409.html
https://github.com/mirrorjs/mirror/issues/78
https://segmentfault.com/a/1190000019105896
https://segmentfault.com/a/1190000020241389?utm_source=tag-newest
https://segmentfault.com/a/1190000039190541

react实现路由跳转拦截功能(导航守卫)相关推荐

  1. React使用路由跳转时控制台报Cannot update during an existing state transition (such as within `render`)错误

    React使用路由跳转时控制台报Cannot update during an existing state transition (such as within render).Render met ...

  2. 初学react实现路由跳转_如何使用React构建模因制作者:初学者指南

    初学react实现路由跳转 by Avanthika Meenakshi 通过Avanthika Meenakshi 如何使用React构建模因制作者:初学者指南 (How to build a me ...

  3. 解决React中路由跳转报错:Cannot read property ‘push’ of undefined

    React中路由跳转报错: 页面在Router中配置了,但组件无法使用 this.props.history.push() 进行跳转,并且会出现报错:Cannot read property 'pus ...

  4. react实现路由跳转

    react通过路由实现页面跳转: ​ 函数式路由(withRouter)使用原生js方法实现路由功能. ​ eg:export default withRouter(Home) Home是组件名称. ...

  5. react实现路由跳转_react实现hash路由

    众所周知,目前单页面使用的路由有两种实现方式: hash 模式 history 模式 hash 模式 路由原理: 我们先来看hash模式,页面首次加载时需要在load事件中解析初始的URL,从而展示进 ...

  6. react history路由跳转

    场景: 我从一个 子集页面 直接 退出登录 , 然后 重新登录 ,会直接到 我刚刚退出的 子页面. 问题: 这个子页面有 返回上一个的按钮 ,此时点击这个按钮直接 就跳转到 登录页了!!! 场景图: ...

  7. vue axios跨域请求_axios的请求拦截和vue路由的导航守卫有什么区别

    在Vue项目中,有两种用户登录状态判断并处理的情况,分别为:导航守卫和axios拦截器. 1. 导航守卫:拦截组件 导航守卫就是我们进行某些页面的时候需要判断当前用户是否登录过,如果登陆过,则可以跳转 ...

  8. vue 设置路由导航守卫 控制路由跳转

    在实际项目中,通常有这样的需求,在用户没有登录的时候通常时不允许访问除了登录页面之外的页面,这个时候就需要使用路由导航守卫来控制跳转了,通常设置如下 在router文件夹下的index.js中添加如下 ...

  9. React进阶(五):导航守卫

    文章目录 一.前言 二.全局守卫 三.拓展阅读 一.前言 在<React进阶(四):路由介绍>博文中,介绍了React路由相关知识,在实际项目开发过程中,路由之间的跳转必定涉及权限.用户是 ...

最新文章

  1. 时间复杂度,O(1), O(n), O(logn), O(nlogn) 的区别+样例分析
  2. 【错误记录】VMware 虚拟机报错 ( 无法连接网络 | VMWare 中打开已经连接好的虚拟机 | 选择 “ 在图形功能不兼容情况下, 车行是恢复虚拟机 “ 选项 )
  3. android 实现微信分享多张图片的功能
  4. php7连接mongodb,批量添加数据
  5. mysql函数(五.流程控制函数)
  6. C++的流输入和输出操作
  7. 服务器内文件如何修改后缀名,修改服务器配置 让asp.net文件后缀名随心所欲
  8. 分数加减乘除混合运算带答案_分数分数加减乘除混合运算练习题及答案_0.doc
  9. 设计师要的各式各样的设计软件插件都整理好了!
  10. c语言中lu是什么数据类型,C语言编程入门之--第四章C语言基本数据类型
  11. 天行健,君子以自强不息 ;地势坤,君子以厚德载物
  12. SQLTableSource
  13. 海盗王GM工具箱 - 物品编辑器
  14. 微信小程序语言c#,微信小程序推出最新脚本语言WXS,你需要知道的全在这里了...
  15. 自动化测试库、框架和工具之间的区别
  16. QComboBox样式设置——Qt
  17. lj2400恢复出厂_联想LJ2400打印机有几种,清零方法是什么,只搜到一种清零方式,我的机器还不是这一种。...
  18. 如何写好科研论文(MOOC)2021春季期末答案
  19. 三大集合:List、Map、Set的区别与联系
  20. [Excel]COUNTIF()函数使用实例以及扩展用法——根据区域是否包含某个字符进行操作

热门文章

  1. 山东大学科技文献期末复习(个人速成向)
  2. NNDL 作业4:第四章课后题
  3. 证件照背景颜色怎么用手机换
  4. jboot websocket的使用
  5. 计量经济学之格兰杰因果关系检验(Granger causality test)
  6. 苹果ipad找不到服务器怎么办,找不到网络怎么办 ipad无法加入无线网络解决方法【详解】...
  7. 爬虫基础:HTTP基本原理
  8. 速写人物的脸型怎么画?如何画好人物脸型?
  9. 杰奇运行在php7,帝国CMS7.5使用PHP7.x环境登录后台报错的解决方法!
  10. Mbit/s vs MB/s vs MiB/s