极客园PC-第3章 首页
react样式冲突
组件样式覆盖问题
- 在Layout组件的
index.scss
中添加样式
.navBar {
background-color: pink;
}
- 在
Home
组件中的样式也跟着发生了改变,说明组件中的样式是相互影响的。 - 原因:在配置路由时,Layout和 Home 组件都被导入到项目中,那么组件的样式也就被导入到项目中了。如果组件之间样式名称相同,那么一个组件中的样式就会在另一个组件中也生效,从而造成组件之间样式相互覆盖的问题。
- 结论:默认,只要导入了组件,不管组件有没有显示在页面中,组件的样式就会生效。
- 解决方案
- 手动处理 (起不同的类名)
CSS IN JS
: 以js的方式来处理css- css不是一门编程语言,css没有所有的局部作用域全局作用域这样的区分。。。css只有全局作用域
CSS IN JS
-
CSS IN JS:是使用 JavaScript 编写 CSS 的统称,用来解决 CSS 样式冲突、覆盖等问题
-
CSS IN JS 的具体实现有 50 多种,比如:CSS Modules、styled-components 等
-
推荐使用:CSS Modules (React脚手架已集成,可直接使用)
CSS Modules 的说明
- CSS Modules 通过对 CSS 类名重命名,保证每个类名的唯一性,从而避免样式冲突的问题
- 换句话说:所有类名都具有“局部作用域”,只在当前组件内部生效
- 在 React 脚手架中:文件名、类名、hash(随机)三部分,只需要指定类名即可 BEM
xxxx.module.css
/* 自动生成的类名,我们只需要提供 classname 即可 */
[filename]_[classname]_[hash]
// 类名
.error {} .red{}
// 生成的类名为:
.NavHeader_error__ax7yz .NavHead_red_abcdc
在项目中使用css Modules
- 创建名为
[name].module.css
的样式文件(React脚手架中的约定,与普通 CSS 作区分)
// 在 CityList 组件中创建的样式文件名称:
index.module.css
- 组件中导入该样式文件(注意语法)
// 在 CityList 组件中导入样式文件:
import styles from './index.module.css'
- 通过 styles 对象访问对象中的样式名来设置样式
<div className={styles.test}></div>
css module的注意点
- 类名最好使用驼峰命名,因为最终类名会生成
styles
的一个属性
.tabBar {}
styles.tabBar
- 如果没有使用驼峰命名,对于不合法的命名,需要使用[]语法
.tab-bar {}
styles['tab-bar']
- 如果是全局的类名,应该使用
:global(.类名)
的方式,不然会把全局类名给修改掉
:global(.icon-map) { }
这样css modules就不会修改掉类名了
css module配合sass
- css moudule也可以配合sass来使用,创建名为
[name].module.scss
.father {
.son {
font-size: 30px;
}
:global {
.son2 {
font-size: 20px;
}
}
}
- 使用css modules解决Login组件样式冲突问题
css modules的使用步骤
- 把
index.scss
改成index.module.scss
.list {
background-color: pink;
color: red;
:global {
xxxx
}
}
- 导入样式的时候进行修改
- import './index.scss'
+ import styles from './index.module.scss'
- 使用的时候需要修改
- <div className="list">文章列表组件</div>
+ <div className={styles.list}>文章列表组件</div>
项目功能 - 首页
搭建布局组件结构
- 准备基本结构
<div className="layout">
<Layout>
<Header className="header">
<div className="logo" />
</Header>
<Layout>
<Sider width={200} className="site-layout-background">
<Menu
mode="inline"
defaultSelectedKeys={['1']}
defaultOpenKeys={['sub1']}
style={{ height: '100%', borderRight: 0 }}
>
<Menu.Item key="1">option1</Menu.Item>
<Menu.Item key="2">option2</Menu.Item>
<Menu.Item key="3">option3</Menu.Item>
</Menu>
</Sider>
<Layout style={{ padding: '0 24px 24px' }}>
<Content
className="site-layout-background"
style={{
padding: 24,
margin: 0,
minHeight: 280,
}}
>
Content
</Content>
</Layout>
</Layout>
</Layout>
</div>
- 提供样式文件
index.scss
.layout {
.logo {
float: left;
width: 120px;
height: 31px;
margin: 16px 24px 16px 0;
background: rgba(255, 255, 255, 0.3);
}
.ant-row-rtl .logo {
float: right;
margin: 16px 0 16px 24px;
}
.site-layout-background {
background: #fff;
}
}
头部结构与样式
- 头部结构
<Header className="header">
<div className="logo" />
<div className="profile">
<span>react先锋</span>
<span>
<LogoutOutlined></LogoutOutlined>
{' '}退出
</span>
</div>
</Header>
- 头部样式
.layout {
.profile {
position: absolute;
right: 20px;
color: #fff;
font-weight: 700;
span + span {
margin-left: 20px;
cursor: pointer;
}
}
}
左侧菜单
- 导入图标
import {
LogoutOutlined,
HomeOutlined,
DiffOutlined,
EditOutlined,
} from '@ant-design/icons'
- 展示图标
<Menu.Item key="1" icon={<HomeOutlined />}>
数据概览
</Menu.Item>
<Menu.Item key="2" icon={<DiffOutlined />}>
内容管理
</Menu.Item>
<Menu.Item key="3" icon={<EditOutlined />}>
发布文章
</Menu.Item>
整体样式处理
- 样式
.layout {
height: 100vh;
.ant-layout {
height: 100%;
}
}
- 内容样式修改
<Layout style={{ padding: '24px' }}>
嵌套路由的配置
- 新增组件
Home/index.js
ArticleList/index.js
ArticlePublish/index.js
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return <div className="home">Home首页</div>
}
}
- 增加配置 layout/index.js组件中
import Home from '../Home'
import ArticleList from '../ArticleList'
import ArticlePublish from '../ArticlePublish'
<Route exact path="/home" component={Home}></Route>
<Route path="/home/list" component={ArticleList}></Route>
<Route path="/home/publish" component={ArticlePublish}></Route>
退出功能
- 结构
import { Layout, Menu, Popconfirm } from 'antd'
<Popconfirm
title="你确定要退出本系统么?"
okText="确定"
cancelText="取消"
onConfirm={this.onConfirm}
>
<span>
<LogoutOutlined></LogoutOutlined>
{' '}退出
</span>
</Popconfirm>
- 功能
onConfirm = () => {
// 点击了确定
localStorage.removeItem('itcast_geek_pc')
// 跳转到登录页
this.props.history.push('/login')
// 提示消息
message.success('退出成功')
}
本地存储操作的封装
- 封装token操作
utils/storage.js
const TOKEN_KEY = 'itcast-geek-token'
const setToken = (token) => {
localStorage.setItem(TOKEN_KEY, token)
}
const removeToken = () => {
localStorage.removeItem(TOKEN_KEY)
}
const getToken = () => {
return localStorage.getItem(TOKEN_KEY)
}
export { setToken, getToken, removeToken }
- 修改登录代码
submit = async (values) => {
const { mobile, code } = values
try {
const res = await login(mobile, code)
// 存储token
// localStorage.setItem('itcast_geek_pc', res.data.token)
setToken(res.data.token)
// 跳转到首页
this.props.history.push('/home')
message.success('登录成功', 1)
} catch (err) {
message.warning(err.response.data.message, 1)
}
}
- 修改退出功能
onConfirm = () => {
// 点击了确定
// localStorage.removeItem('itcast_geek_pc')
removeToken()
// 跳转到登录页
this.props.history.push('/login')
// 提示消息
message.success('退出成功')
}
登录访问控制 - 鉴权
对于极客园 PC 端项目来说,
- 有的页面不需要登录就可以访问,比如,登录页
- 有的页面需要登录后才能访问,比如,项目后台首页、内容管理等(除了登录页面,其他页面需要登录才能访问)
因此,就需要对项目进行登录访问控制,让需要登录才能访问的页面,必须在登录后才能访问。
在没有登录时,直接跳转到登录页面,让用户进行登录。
- 如何实现登录访问控制呢?
- 分析:不管哪个页面都是通过路由来访问的,因此,需要从路由角度来进行控制
- 思路:创建
AuthRoute
组件,判断是否登录,1 登录直接显示要访问的页面 2 没有登录跳转到登录页面
难点:react中没有导航守卫,需要自己封装
分析 AuthRoute 鉴权路由组件
- 场景:限制某个页面只能在登录的情况下访问。
- 说明:在 React 路由中并没有直接提供该组件,需要手动封装,来实现登录访问控制(类似于 Vue 路由的导航守卫)。
- 如何封装?参考 react-router-dom 文档中提供的鉴权示例 。
- 如何使用?使用 AuthRoute 组件代替默认的 Route 组件,来配置路由规则。
- AuthRoute 组件实际上就是对原来的 Route 组件做了一次包装,来实现了一些额外的功能。
<Route path component render>
render 方法,指定该路由要渲染的组件内容(类似于 component 属性)。- Redirect 组件:重定向组件,通过 to 属性,指定要跳转到的路由信息。
- state 属性:表示给路由附加一些额外信息,此处,用于指定登录成功后要进入的页面地址。
// 使用方式:
<AuthRoute path="/rent/add" component={Rent} />
实现自己的AuthRoute组件
- 新增hasToken方法
const hasToken = () => !!getToken()
export { setToken, getToken, removeToken, hasToken }
- 权限判断
import React from "react"
import { Route, Redirect } from "react-router-dom"
import { hasToken } from 'utils/storage'
function AuthRouter({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={(props) => {
// console.log("props", props)
if (hasToken()) {
return <Component {...props}></Component>
} else {
return (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
></Redirect>
)
}
}}
></Route>
)
}
export default AuthRouter
- 使用AuthRoute
{/* 路由规则 */}
<Switch>
<Redirect exact from="/" to="/home"></Redirect>
<AuthRoute path="/home" component={Layout}></AuthRoute>
<Route path="/login" component={Login}></Route>
</Switch>
- 登录成功处理
submit = async (values) => {
const { mobile, code } = values
console.log(this.props)
try {
const res = await login(mobile, code)
// 存储token
// localStorage.setItem('itcast_geek_pc', res.data.token)
setToken(res.data.token)
// 跳转到首页
const { state } = this.props.location
if (state) {
this.props.history.push(state.from.pathname)
} else {
this.props.history.push('/home')
}
message.success('登录成功', 1)
} catch (err) {
message.warning(err.response.data.message, 1)
}
}
route组件的作用
<Route path="/home" component={Home}></Route>
<Route path="/login" component={Login}></Route>
Route组件会根据当前地址的中地址 和 Route的path进行匹配,,,如果路径一直,那么这个对应的组件就会被渲染出来
Route没有判断用户是否登录的能力,只会根据path判断是否要渲染对应的组件。
需求:让Route组件能够有逻辑,能够判断用户是否登录,,,,需要通过Route组件的render属性
获取个人信息
- 拦截器添加token
// 请求拦截器
instance.interceptors.request.use(
function (config) {
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
function (error) {
return Promise.reject(error)
}
)
- 封装接口,获取用户信息
/**
* 获取用户信息
* @returns Promise
*/
export const getUserInfo = () => {
return request({
url: '/user/profile',
method: 'get',
})
}
- 发送请求进行登录
state = {
profile: {},
}
async componentDidMount() {
const res = await getUserProfile()
console.log(res)
this.setState({
profile: res.data,
})
}
- 渲染
<div className="profile">
<span>{this.state.profile.name}</span>
<Popconfirm
title="你确定要退出本系统么?"
okText="确定"
cancelText="取消"
onConfirm={this.onConfirm}
>
<span>
<LogoutOutlined></LogoutOutlined>
{' '}退出
</span>
</Popconfirm>
</div>
处理token过期
使用响应拦截器拦截未登录的用户
难点:react-router-dom如何在非组件中实现路由跳转
- 响应拦截器处理
// 响应拦截器
instance.interceptors.response.use(
function (response) {
return response.data
},
function (error) {
console.log(error.response)
if (error.response.status === 401) {
// token过期
removeToken()
window.location.href = '/login'
}
return Promise.reject(error)
}
)
缺点:window.localtion会导致页面刷新
- history处理
utils/history.js
import { createHashHistory } from 'history'
const history = createHashHistory()
export default history
- App.js修改
import history from 'utils/history'
<Router history={history}>
</Router>
- 修改响应拦截器
import history from './history'
// 响应拦截器
instance.interceptors.response.use(
function (response) {
return response.data
},
function (error) {
console.log(error.response)
if (error.response.status === 401) {
// token过期
removeToken()
// 跳转到登录页
history.push('/login')
message.warning('用户信息已过期')
}
return Promise.reject(error)
}
)
左侧菜单与高亮
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={[this.props.location.pathname]}
style={{ height: '100%', borderRight: 0 }}
>
<Menu.Item key="/home" icon={<HomeOutlined />}>
<Link to="/home">首页</Link>
</Menu.Item>
<Menu.Item key="/home/list" icon={<DiffOutlined />}>
<Link to="/home/list">内容管理</Link>
</Menu.Item>
<Menu.Item key="/home/publish" icon={<EditOutlined />}>
<Link to="/home/publish">发布文章</Link>
</Menu.Item>
</Menu>
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库