React项目笔记-尚硅谷谷粒后台(上)

目录

1.创建脚手架

2.引入antd

2.1自定义主题(如有不懂可以参照antd官网,可在印记中文中查看)

2.1.1下载包@craco/craco和craco-less

2.1.2在根目录下创建craco.config.js文件

2.1.3修改package.json默认配置项

2.1.4引入antd样式

3.引入路由

3.1Switch

3.2Redirect

3.3.BrowserRouter

4.login页面

 4.1外层容器

4.2登录页面头部部分

4.3登录页面内容部分

4.3.1在login页面render中需额外做的事

4.4登录页面的onclick函数

4.4.1封装ajax函数

4.4.2在封装的ajax函数基础上进一步封装

4.4.3onFinsh中我们需要做的事

4.4.4memoryUtils

4.4.5storageUtils

5.admin页面

6.admin左侧left-nav

6.1.admin左侧left-nav头部left-nav-header

6.2.admin页面左侧left-nav的Menu

6.3.admin页面左侧left-nav的Menu菜单动态生成

6.3.1Menu菜单动态配置项。

6.3.2动态生成Menu菜单的getMenuNodes函数

7admin页面右侧的header部分。

7.1admin页面右侧header的header-top部分

7.2admin页面右侧header的header-top部分中的退出按钮效果

7.3 admin页面右侧header的header-bottom部分

7.4admin页面右侧header的header-bottom部分中实时更新时间的效果

 7.5admin页面右侧header的header-bottom部分中获取天气相关信息

7.5.1定义jsonp请求函数

7.5.2发送jsonp请求。

7.6动态显示标题

8.admin页面右侧content内容区的home组件


1.创建脚手架

使用create-react-app命令创建脚手架

如:用cmd进入目标文件夹下,在目标文件夹下使用create-react-app project(project为项目名称)


2.引入antd

yarn add antd  下载antd组件库

yarn add @ant-design/icons 下载antd图表字体库(已经丛antd张红独立出去了)

注意:新版本的antd已经可以实现按需加载了,所以我们不用像3xx版本中一样去配置按需加载了

2.1自定义主题(如有不懂可以参照antd官网,可在印记中文中查看)

注意:我们可以将antd默认的蓝色改成我们需要的颜色)

2.1.1下载包@craco/craco和craco-less

命令:yarn add @craco/craco craco-less

用了这个之后我们的脚手架就可以支持less了,我们的脚手架默认是不支持less的

2.1.2在根目录下创建craco.config.js文件

该文件下加入如下代码:

const CracoLessPlugin = require('craco-less');

module.exports = {
  plugins: [
    {
      plugin: CracoLessPlugin,
      options: {
        lessLoaderOptions: {
          lessOptions: {
            modifyVars: { '@primary-color': '#1DA57A' },
            javascriptEnabled: true,
          },
        },
      },
    },
  ],
};

2.1.3修改package.json默认配置项

/* package.json */
"scripts": {
-   "start": "react-scripts start",
-   "build": "react-scripts build",
-   "test": "react-scripts test",
+   "start": "craco start",
+   "build": "craco build",
+   "test": "craco test",
}

将-号的默认配置项删除,改成+号的配置项内容。

2.1.4引入antd样式

import 'antd/dist/antd.less';这样引入之后,我们的antd组件的样式才会生效

注意:前面没有写./的时候会默认到我们的node_modules中去找


3.引入路由

(注意:项目版本比较旧,react-router-dom选择5.0.0版本,不然后面会有很多报错不好解决)

yarn add react-router-dom@5.0.0

首先考虑为什么admin和login组件为什么要用路由组件来做。因为我们只是一个spa页面,所以注定了我们这个地方必须使用路由组件,那么我们什么时候会在admin页面,什么时候会在login页面中呢?我们可以将用户的信息存在本地的缓存中,然后每次在程序的入口文件就读取用户信息,然后render()渲染的时候判断,本地缓存中是否有用户,如果没有则显示login页面,如果有则显示admin页面,所以在这个地方我们一定要用路由组件。

路由组件不一定是Link跳转的组件就是路由组件,我们也可以通过在地址栏的输入,这同样也是路由组件,所以其this.props里面也是有history,match和location的。

import React, { Component } from 'react'
import { Switch,Route,Redirect } from 'react-router-dom'
import Login from "./pages/login/login"
import Admin from "./pages/admin/admin"
import 'antd/dist/antd.less';
export default class App extends Component {
  render() {
    //   App不是路由组件,但是App里面的admin和login是路由组件
    return (
      <Switch>
          <Route path="/login" component={Login}/>
          <Route path="/" component={Admin}/>
          <Redirect to="/"/>
      </Switch>
    )
  }
}

注意:

  1. :第一个Route和第二个Route一定不要换了顺序,如果把顺序换了,在第一次登录的时候就会出现很严重的问题,也就是我们内存没值,先跳到admin,然后内存没值会定向到login,login判断内存没值,会重定向到<Redirect to="/login" />,问题就出在这/login会跟第一个 <Route path="/" component={Admin} />匹配上,然后又会跳到admin,然后如此循环,所以不停的在两个组件之间渲染,跳来跳去,都没有进各自的render,所以一直没有效果。(这里是由于模糊匹配的原因,如果路由链接里面的to属性值是我们注册路由path的子集,那么这个注册路由就会显示)
  2. :我们的admin一般在主页面的路由中,也就是地址栏中一般不显示,直接显示主页面中默认显示的home路由,所以这里我们是这么写的<Route path="/" component={Admin} />

3.1Switch

Switch的目的是匹配到一个之后就不再匹配了,不然可能符合条件的所有注册路由都会显示在页面上。

3.2Redirect

Redirect的目的是重定向,当我们上面两个路由都没有匹配到的时候,就给我定向到第一个注册路由,也就是显示Admin组件。

在render函数中的跳转一般都是用的这个属性,然后在js中一般都是用路由组件的replace或者是push方法。

import React from "react"
import ReactDOM from "react-dom"
import { BrowserRouter } from "react-router-dom"
import App from './App'
import storageUtils from "./utils/storageUtils"
import memoryUtils from "./utils/memoryUtils"
const user=storageUtils.getUser()
memoryUtils.user=user
ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>,
    document.getElementById("root")
)

3.3.BrowserRouter

相当于是路由器,我们点了路由链接之后它怎么知道跳到哪个注册路由,用的就是这个路由器,要把我们的注册路由和路由链接包裹起来,所以我们一般直接扩大范围写在App.jsx中将我们的入口文件index.js中,这样我们就可以用一个路由器管理所有的路由了,比较方便。


4.login页面

 

 4.1外层容器

login的html结构

<div className="login"></div>

注意:我们要把login页面的高度设置成百分之百,而我们的html,body,以及我么的root的高度都是0,所以我们要先设置他们的高度为百分之百,默认我么设置login的百分之百才会有效果

login的less样式

   

    width: 100%;
    height: 100%;
    background-image: url(../../assets/images/bg.jpg);
    background-repeat: no-repeat;
    background-size: 100% 100%;

4.2登录页面头部部分

header的html结构

                <header className="login-header">
                    <img src={Logo} alt={Logo} />
                    <h1>React项目:后台管理系统</h1>
                </header>

header的less样式

.login-header {
        height: 80px;
        background-color: rgba(21, 20, 13, 0.5);
        display: flex;
        align-items: center;
        img {
            width: 40px;
            height: 40px;
            margin: 0 15px 0 50px;
        }
        h1 {
            font-size: 30px;
            color: white;
        }
    }

注意:React中的img不支持src中写路径的方式,我们将图片引入进来定义一个变量接受,然后将变量卸载src里面。

4.3登录页面内容部分

内容部分的html结构

                <section className="login-content">
                    <h2>用户登录</h2>
                    <Form
                        initialValues={{ remember: true }}
                        onFinish={this.onFinish}
                    >
                        <Item
                            name="username"
                            rules={
                                [
                                    { required: true, message: '用户名必须输入' },
                                    { max: 12, message: '用户名最多12位' },
                                    { min: 4, message: '用户名至少4位' },
                                    { pattern: /^[a-zA-Z0-9_]+$/, message: '用户名必须是数字字母或下划线' }
                                ]
                            }
                        >
                            <Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Username" />
                        </Item>
                        <Item
                            name="password"
                            rules={
                                [
                                    { required: true, message: '密码必须输入' },
                                    { max: 12, message: '密码最多12位' },
                                    { min: 4, message: '密码至少4位' },
                                    { pattern: /^[a-zA-Z0-9_]+$/, message: '密码必须是数字字母或下划线' }
                                ]
                            }
                        >
                            <Input
                                prefix={<LockOutlined className="site-form-item-icon" />}
                                type="password"
                                placeholder="Password"
                            />
                        </Item>

                        <Item>
                            <Button style={{ width: "100%" }} type="primary" htmlType="submit" className="login-form-button">
                                登录
                            </Button>
                        </Item>
                    </Form>
                </section>

内容部分的less样式

.login-content {
        width: 400px;
        height: 300px;
        background-color: #fff;
        margin: 50px auto;
        padding: 20px 40px;

        h2 {
            text-align: center;
            font-size: 20px;
            font-weight: bold;
            margin-bottom: 20px;
        }

    }

4.3.1在login页面render中需额外做的事

什么时候我们要显示login,也就是我们的缓存中没有user信息的时候。所以当我们的缓存中有user信息的时候,我们就要重定向到admin页面了。(可以实现免登录的效果,也就是将我们的用户信息存在本地的localStorage中,每次程序运行的时候在如果文件中就读到我们的程序内存memoryUtils中,通过判断程序内存中有无值,就可以实现用户免登录的效果)

所以也就有了下面的代码

        const user=memoryUtils.user
        if(user._id){
            return <Redirect to="/" />
        }

同样的步骤也要在admin的render的return返回前做,当user没有的时候(人为清空localStorage,再刷新,或者点击退出的时候),我们就要退回到login页面,在后面登录页面讲。

4.4登录页面的onclick函数

    onFinish = async (value) => {
        // 收集用户用户名和密码
        const { username, password } = value
        // 发送ajax请求验证登录密码,发送成功且密码验证成功则跳转到admin页面,如果成功且密码验证失败则继续留在当前页面
        let result = await reqLogin(username, password)
        console.log(result)
        if (result.status===0) {
            message.success("登录成功")
            const user=result.data
            memoryUtils.user=user
            storageUtils.saveUser(user)
            this.props.history.replace("/")
        } else {
            message.error("登录失败")
        }
        // 发送请求失败则在发送请求那个里面进行处理
    }

4.4.1封装ajax函数

import { message } from "antd";
import axios from "axios";
export default function ajax(url,data={},type="GET"){
    return new Promise((resolve,reject)=>{
        let promise
        if(type==="GET"){
            promise= axios.get(url,{params:data})
        }else{
            promise = axios.post(url,data)
        }
        promise.then((value)=>{resolve(value.data)}).catch((reason)=>{message.error(reason.message)})
    })

}

注意:

  1. 请求异常的捕获位置,其实异常都是大同小异,所以我们没必要一个一个的捕获,所以我们可以在发请求收到结果的时候就把异常给处理了。因为使用resolve,所以外面必须得包一层promise,所以才会return一个新的promise。所以请求失败的情况我们在ajax这个封装的函数中统一处理了,所以我们就不用再处理请求失败的情况了,请求失败与请求成功不一样,请求成功也有登录成功和登录失败两种情况。
  2. 我们如果resolve(value),那么后面我们得到的每一个都需要value.data才能得到我们需要的代码,所以我们可以直接在这里resolve(value.data),这样可以减少代码的冗余。

4.4.2在封装的ajax函数基础上进一步封装

import ajax from "./ajax"

export const  reqLogin=(username,password)=> ajax("/login",{username,password},"POST")

为什么还需要再封装一次呢,我们发现我们如果同样的请求很多的话,其实就是username和password不一样,其他的其实都是一样的,所以我们可以在ajax的上面在做一封装,然后就可以达到简化代码的目的。

4.4.3onFinsh中我们需要做的事

首先发请求,然后根据返回的结果的状态码判断是否是登录成功,如果成功就保存值到内存中,并保存值到localStorage中,然后再通过路由组件的对象history上的方法replace跳转到我们的admin页面,为什么用replace而不是push呢,因为我们是不需要回来的,所以用replace合适。

如果是登录失败也就是说请求成功,但是后台返回我们的密码是错误的,这个时候我们就用我们的message组件提示一下错误就好了。

4.4.4memoryUtils

export default {
    user:{}
}

用来在程序运行是的内存中保存用户对象,初始值是一个空对象。

4.4.5storageUtils

import store from "store"
export default {
    saveUser(user) {
        // localStorage.setItem("user_key", JSON.stringify(user))
        // 原生封装有时候不是很兼容,在这里我们可以使用store这样一个库
        return store.set("user_key",user)
    },
    getUser() {
        // 没有值的话就是返回的null,最后给我返回一个空对象,然后我点什么属性的时候不会报错
        // return JSON.parse(localStorage.getItem("user_key") || "{}")
        return store.get("user_key")||{}
    },
    removeUser() {
        // localStorage.removeItem("user_key")
        return store.remove("user_key")
    }
}

用来在本地保存用户的用户对象,可以实现维持登录和自动登录的效果,封装了一些方法,可以实现对用户对象的增删查。

注意:为什么我们在这里取user_key的时候要去后面或一个{}呢,因为我们如果取不到值的话,就是说如果我们的localStorage中还没有这个use_key这个值的话,它其实返回的是一个null,那么我们后面用这个null再取里面的_id去判断localStorage中是否有用户存在的时候就会出现报错的情况,如果我们用一个{}的时候,那么就可以很好的解决这个问题了。


5.admin页面

layout组件

 

                <Layout style={{height:"100%"}}>
                    <Sider>
                        <LeftNav/>
                    </Sider>
                    <Layout>
                        {/* 如果把antd的header删除了用的就是我们自己的header */}
                        <Header className="header">Header</Header>
                        <Content style={{backgroundColor:"#fff"}}>
                            <Switch>
                                <Route path="/home" component={Home}/>
                                <Route path="/category" component={Category}/>
                                <Route path="/product" component={Product}/>
                                <Route path="/role" component={Role}/>
                                <Route path="/user" component={User}/>
                                <Route path="/charts/pie" component={Pie}/>
                                <Route path="/charts/line" component={Line}/>
                                <Route path="/charts/bar" component={Bar}/>
                                <Redirect to="/home"/>
                            </Switch>
                        </Content>
                        <Footer style={{textAlign:"center",color:"#ccc"}}>推荐使用谷歌浏览器,可以获得更佳页面操作体验</Footer>
                    </Layout>
                </Layout>

整个的布局分为

  • left-nav左侧导航栏部分
  • 右侧header头部部分
  • 右侧中间content主要内容展示部分
  • 右侧footer底部部分

admin页面具体实现效果


6.admin左侧left-nav

<div className="left-nav"></div>

6.1.admin左侧left-nav头部left-nav-header

html结构

        <Link to="/home" className="left-nav-header">
          <img src={Logo} alt={Logo} />
          <h1>硅谷后台</h1>
        </Link>

为什么我们在这里要用Link路由标签,因为我们点admin的主页图像以及这个标题的时候会根据路由跳到我们的这个home主页,所以我们在这里把原先的div标签换成了Link标签。那么Link标签和NavLink标签有什么区别呢?主要在于NavLink标签路由链接的时候会出现高亮的效果。

less样式

.left-nav{
    .left-nav-header{
        height: 80px;
        display: flex;
        align-items: center;
        background-color: #002140;
        img{
            margin: 0 15px ;
            width: 40px;
            height: 40px;
        }
        h1{
            color: white;
            font-size: 20px;
            margin-bottom: 0;
        }
    }
}

6.2.admin页面左侧left-nav的Menu

Menu菜单组件(在这里我们用3xx版本的)

Menu外面包裹一层<div className='left-nav'></div>

这里我们没有这个Button也就是折叠按钮,所以我们就可以把这个Button删掉,然后相关折叠的回调函数就可以直接删掉。

<Menu
          defaultSelectedKeys={['1']}
          defaultOpenKeys={['sub1']}
          mode="inline"
          theme="dark"
        >
          {/* {this.getMenuNodes(menuList)} */}
{/* 上面这个方法为动态生成Menu菜单项的方法,有了这个方法,我们就不用写下面这一大堆的东西了 */}
          <Menu.Item key="1">
            <Link to="/home">
              <HomeOutlined />
              <span>首页</span>
            </Link>
          </Menu.Item>

          <SubMenu
            key="2"
            title={
              <span>
                <HomeOutlined />
                <span>商品</span>
              </span>
              
            }
          >
            <Menu.Item key="3">
              <Link to="/product">
                <HomeOutlined />
                <span>商品管理</span>
              </Link>
            </Menu.Item>
            <Menu.Item key="4">
              <Link to="/category">
                <HomeOutlined />
                <span>品类管理</span>
              </Link>
            </Menu.Item>
          </SubMenu>
          <Menu.Item key="5">
            <Link to="/user">
              <HomeOutlined />
              <span>用户管理</span>
            </Link>
          </Menu.Item>
          <Menu.Item key="6">
            <Link to="/role">
              <HomeOutlined />
              <span>角色管理</span>
            </Link>
          </Menu.Item>
          <SubMenu
            key="7"
            title={
              <span>
                <HomeOutlined />
                <span>图形图表</span>
              </span>
            }
          >
            <Menu.Item key="8">
              <Link to="/charts/bar">
                <HomeOutlined />
                <span>柱形图</span>
              </Link>
            </Menu.Item>
            <Menu.Item key="9">
              <Link to="/charts/line">
                <HomeOutlined />
                <span>折线图</span>
              </Link>
            </Menu.Item>
            <Menu.Item key="10">
              <Link to="/charts/pie">
                <HomeOutlined />
                <span>饼图</span>
              </Link>
            </Menu.Item>
          </SubMenu>
        </Menu>

其实就是一个Menu下面有Menu.Item项,然后如果是某一项下面有子项的时候,就在外面包一层SubMenu就好了。

注意:

  • 我们的admin右侧content的内容区是根据我们用户点哪一个链接去实现显示什么内容的,所以我们的左侧导航区中的Menu.Item项要设置成路由链接,但是我们在SubMenu上不用设置路由链接,因为我们点SubMenu不会实现路由跳转,点SubMenu下的Menu.Item才会实现跳转。
  • 我们的Link标签要将我们的图标和字都包裹住。
  • 我们的key怎么保证不一样,我们直接用路由就可以了,也就是将to的内容设置成对应的Item的key值,然后两个SubMenu的key值分别对应为products和charts就好了。
  • SubMenu的title属性只能有一个根节点,不允许有多个,就是所有节点的外面必须包一个统一的节点。

6.3.admin页面左侧left-nav的Menu菜单动态生成

首先问大家一个问题,为什么需要动态生成Menu菜单项呢,我们动态生成的话又要去定义一个数组,然后递归这个数组然后去生成对应的节点。这样做不是增加额外的工作量,节外生枝吗?

其实不是的我们动态生成Menu菜单项,是为了后面一个非常重要的功能权限管理做准备的。也就是说不同的人可以看到不同的页面,可能经理可以看到所有的界面,但是作为一个开发人员你可能只能看到home页面,charts界面,其他的界面我们是看不到的,所以如果写死了,我们大家看到的就都是一样的,那么权限管理就没有办法实现了。

6.3.1Menu菜单动态配置项。

import { HomeOutlined } from "@ant-design/icons"
const menuList = [
    {
        title: "首页",
        key: "/home",
        icon: HomeOutlined
    },
    {
        title: "商品",
        key: "/products",
        icon: HomeOutlined,
        children: [
            {
                title: "商品管理",
                key: "/product",
                icon: HomeOutlined,
            },
            {
                title: "品类管理",
                key: "/category",
                icon: HomeOutlined,
            },            
        ]
    },
    {
        title: "用户管理",
        key: "/user",
        icon: HomeOutlined
    },
    {
        title: "角色管理",
        key: "/role",
        icon: HomeOutlined
    },
    {
        title: "图形图表",
        key: "/charts",
        icon: HomeOutlined,
        children: [
            {
                title: "柱形图",
                key: "/charts/bar",
                icon: HomeOutlined,
            },
            {
                title: "折线图",
                key: "/charts/line",
                icon: HomeOutlined,
            },            
            {
                title: "饼图",
                key: "/charts/pie",
                icon: HomeOutlined,
            },            
        ]
    },
]
export default menuList

有四个属性,title,key,icon,children,如果有childrem属性,那么其就是一个SubMenu,然后下面才是Menu.Item。就是把这个界面的信息定义到数组里面,然后通过遍历这个数组动态生成Menu菜单。

6.3.2动态生成Menu菜单的getMenuNodes函数

  getMenuNodes = (menuList) => {
    return menuList.map((item) => {
      if (item.children) {
        return (
          <SubMenu
            key={item.key}
            title={
              <span>
                <item.icon />
                <span>{item.title}</span>
              </span>
            }
          >
            {this.getMenuNodes(item.children)}
          </SubMenu>
        )
      } else {
        return (
          < Menu.Item key={item.key} >
            <Link to={item.key}>
              <item.icon />
              <span>{item.title}</span>
            </Link>
          </Menu.Item >
        )
      }
    })
  }

其实就是用到了递归,如果没有children是一种情况,有children属性的话再去调用这哥方法就可以实现了。后期权限管理的时候每个user信息里面有一个menus数组,如果menus的项在这个配置项的key里面,那么我们就返回对应的菜单项,这样就可以实现权限管理了。


7admin页面右侧的header部分。

html结构

<div className="header"></div>

less样式

    height: 80px;
    background-color: #fff;

7.1admin页面右侧header的header-top部分

html结构

                <div className="header-top">
                    <span>欢迎{username}</span>
                    <a href="#" onClick={this.logout}>退出</a>
                </div>

less样式

    .header-top {
        height: 40px;
        padding-right: 30px;
        line-height: 40px;
        text-align: right;
        border-bottom: solid 1px #1da57a;

        span {
            margin-right: 10px;
        }
    }

7.2admin页面右侧header的header-top部分中的退出按钮效果

我们可以看到我们给a标签绑定了一个logout点击事件,点击退出之后首先我们要弹出一个确认框,来判断用户是否要真的退出登录,这个对话框是通过Modal里面的confirm函数去实现的。

 注意:

  • 点击取消其实我们什么 都不用做,只是我们需要将这个确认框关闭,但是事实上,这个confirm方法点击取消后会自动关闭,所以我们不用做什么操作。
  • 点击确认的时候我们需要做什么呢?将localStorage中的user移除,将内存中的user对象移除,移除了之后我们再进行重定位到login登录页面就好了,下面为logout的代码
  •     logout=()=>{
            // 调用这个Modal的方法就会弹出对话框
            Modal.confirm({
                content:"确认退出吗?",
                onOk:()=>{
                    storageUtils.removeUser()
                    memoryUtils.user={}
                    this.props.history.replace("/login")
                }
                // 因为点击取消的时候会自动关闭,故我们可以取消onCancel方法,我们不用做啥
            })
        }

7.3 admin页面右侧header的header-bottom部分

html结构

<div className="header-bottom">
                    <div className="header-bottom-left">
                        {title}
                    </div>
                    <div className="header-bottom-right">
                        <span>{currentTime}</span>
                        <img src={Logo} alt={Logo} />
                        <span>{city}</span>
                        <span>{weather}</span>
                        <span>{`${temperature}℃`}</span>
                    </div>
                </div>

css结构

.header-bottom {
        height: 40px;
        display: flex;
        align-items: center;

        .header-bottom-left {
            position: relative;
            width: 25%;
            text-align: center;
            &::after{
                right: 50%;
                transform: translateX(50%);
                top: 100%;
                content:"";
                position: absolute;
                border: 20px solid transparent;
                border-top: 20px solid white;
                // border-bottom: 20px solid red;
                // border-left: 20px solid green;
                // border-right: 20px solid pink;
            }
        }

        .header-bottom-right {
            width: 75%;
            text-align: right;
            margin-right: 30px;
            img{
                margin: 0 15px;
                width: 30px;
                height: 20px;
            }
            span{
                margin-left: 2px;
            }
        }
    }

注意:其中实现小三角的时候如果不开启positon:absolute的话,就会被内容撑开,所以即使我们没有设置宽高,也不是一个正宗的正方形,本来应该是一个正方形的。设置了定位之后就可以消除这个效果。

然后就是right50%的时候是根据正方形右边顶点来和父元素的中点平齐的,所以我们还要往右移动自己的50%,这个时候就可以用平移实现了。或者用cacl一步到位也可以。然后在向下移动就好了,向下移动和向右移动都是正的,不是负的。

7.4admin页面右侧header的header-bottom部分中实时更新时间的效果

  1. 首先获取到时间,我们通过Date.now()这个方法,但是这个方法返回的是一个时间戳,所以我们要借用格式化函数将其格式化,之后我们就可以看到我们想要看到的时间格式效果。
  2. 时间如果我们不放在state里面,那么第一次render(){}之后这个时间会变吗,肯定不会变,所以这个时间肯定要在state里面。
  3. 既然知道了时间要放在state里面,现在要实现的就是动态更新state里面的时间值了,而且是必须隔一秒更新一次,所以在这里我们要开启一个定时器,实时获取现在的时间,然后将实时获取的到的时间更新到状态里面去,这样就可以动态的更新时间了。
  4. 最关键的问题是我们这个定时器在哪开呢?我们有一个钩子函数componentDidMount(){}会在我们的第一次render后调用一次,这个钩子里面一般是做开启定时器和发送异步请求。定时器我们开了之后要关啊,不然当用户点退出按钮退出登录的时候就会报错,因为你的定时器没有清除,所以我们要在组件即将卸载之前将我们的定时器清除,这里有一个钩子叫做componentWillUnmount(){},我们可以在这个钩子函数里面清除定时器。

state:

    state = {
        currentTime: getDate(Date.now()),
        weather: "",
        temperature: "",
        city:""
    }

获取时间的函数

    getCurrentTime=()=>{
        this.intervalId=setInterval(()=>{
            this.setState({currentTime:getDate(Date.now())})
        },1000)
    }

开启定时器以及清除定时器的地方

    componentDidMount() {
        this.getWeather()
        this.getCurrentTime()
    }
    componentWillUnmount(){
        // 组件卸载之前清除计时器
        clearInterval(this.intervalId)
    }

 7.5admin页面右侧header的header-bottom部分中获取天气相关信息

在这里我们通过jsonp实现跨域请求,jsonp是一个小型的库,我们查看其用法可以在github上搜索的到。

7.5.1定义jsonp请求函数

在项目中yarn add jsonp

有三个参数

第一个是jsonp请求的url,这里用的是高德的天气接口(项目中使用的天气接口已停用),需要注册获得一个自己的key值,才可以使用其功能,第二个就是city的编码,每一个城市对应一个编码,我们可以去高德相关的官网查询,第三个是返回数据的格式,可以指定XML或者json。

详情参见:(https://lbs.amap.com/api/webservice/guide/api/weatherinfo

第二个是配置对象,配置对象与普通对象的区别就是配置对象里的属性都是写好的,我们不能改。这里其实我们用其默认的就好了不需要改里面的什么东西,第一个是执行我们回调函数的名字,不指定就是callback,第二个是延迟时间,默认是60秒,超过60秒没有响应就是报错等等

第三个就是回调函数,回调函数有两个参数,第一个是error,如果有错误里面就会有值,没有错误里面就不会有值。 

jsonp请求函数

export const reqWeather=()=>{
    return new Promise((resolve,reject)=>{
    const url="https://restapi.amap.com/v3/weather/weatherInfo?city=420100&output=json&key=7293d9dd8240c49320d5b241d88da2ee"
        jsonp(url,{},(error,data)=>{
            if(error){
                // 请求失败
                message.error("获取天气信息失败")
            }else{
                // 请求成功
                if(data.status==="1"){
                    const {weather,temperature,city}=data.lives[0]
                    resolve({weather,temperature,city})
                }else{
                    // 请求成功,返回失败(和密码错误一样的)
                    message.error("获取天气信息失败")
                }
            }
        }) 
    })

}

注意:

  • 请求失败的时候我们不用管,直接message.error(“获取天气信息失败就可以了”),不用调用reject()函数,在这里因为我们可以直接获取到返回的数据,所以可以自己直接在这里判断。
  • 在jsonp函数中返回的不是一个promise对象,而我们想所有的请求都返回一个promise对象,这样便于我们处理,所以我们就返回了一个新的promise对象。
  • 为什么我们在其他的ajax中不直接判断请求成功后解构呢,比如是登录成功,还是密码错误导致的登陆失败呢?因为每个请求函数的类型不一样,所以我们不在这里统一处理,而在外面处理,第二原因是如果要promise中的数据那就要调用then方法,然后再一递一个回调函数中处理成功与失败,这样逻辑不是很明显,所以我们在这里不直接处理 ,况且处理的标准可能不同,你怎么处理呢。

7.5.2发送jsonp请求。

    getWeather=async ()=>{
        const {weather,temperature,city}=await reqWeather()
        this.setState({weather,temperature,city})
    }

其实这个weather,city,temperature没必要放在state里面,我们在render(){}的时候能获取到三个属性就可以了,其实也可以存在组件的自身身上,其实state也是自身身上的一个属性罢了,其实都差不多。

7.6动态显示标题

首先我们这个title为什么没有放在我们的state里面,放在state里面可以吗?其实是不可以的,设计到我们在哪去调用这个getTitle()函数的问题,首先第一次render之后要调用,在以后render的过程中也要调用,所以我们不能写在其他的生命周期钩子里面,只能写在render里面,那么我们这个getTilte这个函数里面肯定要更新组件的状态吧,那么这个函数又在render中调用,所以render不是会一直调用下去嘛,就造成了render的无限递归调用,这样肯定不行,所以得出结论,render中肯定不能更新state的状态。currentTime是因为在componentDidMount中开启了定时器的原因,所以才会隔一秒一执行,可以写在state中。而天气的相关参数只要我们render执行的时候获取一次就可以了,完全可以等到下一次用户下一次进去页面render的时候再去获取请求就可以了,所以也可以写在componentDidMount中。

我们只是在render()的时候要用到这个title,后面只是用户改变了路由这个标题才会变,所以用户每一次改变路由都会调用render,所以我们在render里面获取到这个title就可以了。

    getTitle=()=>{
        const pathname=this.props.location.pathname
        let title
        menuList.forEach(item=>{
            if(item.key===pathname){
                title=item.title
            }else if(item.children){
                const cItem=item.children.find((cItem)=>cItem.key===pathname)
                if(cItem){
                    title=cItem.title
                }
            }
        })    
        return title
    }

currentTime是因为在componentDidMount中开启了定时器的原因,所以才会隔一秒一执行,可以写在state中。

而天气的相关参数只要我们render执行的时候获取一次就可以了,完全可以等到下一次用户下一次进去页面render的时候再去获取请求就可以了,所以也可以写在componentDidMount中。

所以不能写在state中的参数特征:没有明确间隔时间的更新且在不止在第一次render的时候需要。


8.admin页面右侧content内容区的home组件

html结构

      <div className='home'>
          欢迎使用硅谷后台管理系统
      </div>

less样式

.home{
    height: 100%;
    width: 100%;
    display: flex;
    font-size: 30px;
    justify-content: center;
    align-items: center;
}

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值