开始
注意
项目需要手动安装axios和配置antd按需引入(可参考官方文档:https://3x.ant.design/docs/react/use-with-create-react-app-cn
)。其实,不用配置也行,但每次需要import 'antd/dist/antd.css
原理
原理:
- 项目基于React实现,通过axios发送请求获取数据,并通过bootstrap和antd进行页面渲染
涉及知识点
- 掌握html,css,js三剑客基础知识以及弹性布局
- 具备一定node.js基础,知道npm启动服务器就行
- 具备一定的axios获取数据原理
- 具备React基础知识,路由以及使用antd组件库
项目
项目结构
描述
Login(登录页)
Homer(主页)
Header(主页表头卡片)
Asider(侧边栏导航区)
Messa(个人信息展示)
Photo(接口加载的照片)
image(静态文件)
源码
注意:
- 组件下每一个文件都包含在一个文件夹里,如Login/Login.jsx,Login/Login.css,下面不再声明!!!
Login
import React, { Component } from 'react'
import './Login.css'
export default class Login extends Component {
Login = () => {
const { user, passwd } = this
if (user.value !== 'Admin' && passwd.value !== '123') return alert('用户或密码错误')
localStorage.setItem('token', 'Bearer xxxx')//设置token验证
this.props.history.replace('/home')
}
render() {
return (
<div className="main">
<div className="login-box">
<div className="owl" id="owl">
<div className="hand"></div>
<div className="hand hand-r"></div>
<div className="arms">
<div className="arm"></div>
<div className="arm arm-r"></div>
</div>
</div>
<div className="input-box">
<input ref={(c) => (this.user = c)} type="text" placeholder="账号" />
<input ref={(c) => (this.passwd = c)} type="password" placeholder="密码" id="password" />
<button onClick={() => this.Login()}>登录</button>
</div>
</div>
</div>
)
}
}
* {
/* 初始化 */
margin: 0;
padding: 0;
}
.main {
/* 100%窗口高度 */
height: 100vh;
/* 弹性布局 居中 */
display: flex;
justify-content: center;
align-items: center;
/* 渐变背景 */
background: linear-gradient(200deg, #72afd3, #96fbc4);
}
.login-box {
/* 相对定位 */
position: relative;
width: 320px;
}
.input-box {
/* 弹性布局 垂直排列 */
display: flex;
flex-direction: column;
}
.input-box input {
height: 40px;
border-radius: 3px;
/* 缩进15像素 */
text-indent: 15px;
outline: none;
border: none;
margin-bottom: 15px;
}
.input-box input:focus {
outline: 1px solid lightseagreen;
}
.input-box button {
border: none;
height: 45px;
background-color: lightseagreen;
color: #fff;
border-radius: 3px;
cursor: pointer;
}
/* 接下来是猫头鹰的样式 */
.owl {
width: 211px;
height: 108px;
/* 背景图片 */
background: url('/src/image/owl-login.png') no-repeat;
/* 绝对定位 */
position: absolute;
top: -100px;
/* 水平居中 */
left: 50%;
transform: translateX(-50%);
}
.owl .hand {
width: 34px;
height: 34px;
border-radius: 40px;
background-color: #472d20;
/* 绝对定位 */
position: absolute;
left: 12px;
bottom: -8px;
/* 沿Y轴缩放0.6倍(压扁) */
transform: scaleY(0.6);
/* 动画过渡 */
transition: 0.3s ease-out;
}
.owl .hand.hand-r {
left: 170px;
}
.owl.password .hand {
transform: translateX(42px) translateY(-15px) scale(0.7);
}
.owl.password .hand.hand-r {
transform: translateX(-42px) translateY(-15px) scale(0.7);
}
.owl .arms {
position: absolute;
top: 58px;
width: 100%;
height: 41px;
overflow: hidden;
}
.owl .arms .arm {
width: 40px;
height: 65px;
position: absolute;
left: 20px;
top: 40px;
background: url('/src/image/owl-login-arm.png') no-repeat;
transform: rotate(-20deg);
transition: 0.3s ease-out;
}
.owl .arms .arm.arm-r {
transform: rotate(20deg) scaleX(-1);
left: 158px;
}
.owl.password .arms .arm {
transform: translateY(-40px) translateX(40px);
}
.owl.password .arms .arm.arm-r {
transform: translateY(-40px) translateX(-40px) scaleX(-1);
}
Home
import Header from '../Header/Header'
import React, { Component } from 'react'
import Asider from '../Asider/Asider'
import './Home.css'
export default class Home extends Component {
render() {
return (
<div className="container">
<Header />
<Asider />
</div>
)
}
}
body {
background-color: #96fbc4;
}
Header
import React, { Component } from 'react'
import { PageHeader, Descriptions } from 'antd'
import './Header.css'
export default class Header extends Component {
render() {
return (
<div className="site-page-header-ghost-wrapper">
<PageHeader ghost={false} onBack={() => window.history.back()} title="React" subTitle="I love React!">
<Descriptions size="small" column={3}>
<Descriptions.Item label="Created">李现</Descriptions.Item>
<Descriptions.Item label="Identity">
<a href="https://github.com">student</a>
</Descriptions.Item>
<Descriptions.Item label="Creation Time">2022/3/23</Descriptions.Item>
<Descriptions.Item label="Effective Time">2022/3/23</Descriptions.Item>
<Descriptions.Item label="Remarks">Gonghu Road, Xihu District, Hangzhou, Zhejiang, China</Descriptions.Item>
</Descriptions>
</PageHeader>
</div>
)
}
}
.site-page-header-ghost-wrapper {
padding: 24px;
background-color: #2496d8;
}
Asider
import React, { Component } from 'react'
import { Menu } from 'antd'
import { AppstoreOutlined, PieChartOutlined, DesktopOutlined, ContainerOutlined, MailOutlined } from '@ant-design/icons'
import { Link, Route, Redirect } from 'react-router-dom'
import Message from '../Message/Message'
import Photo from '../Photo/Photo'
import './Asider.css'
// import 'antd/dist/antd.css'
const { SubMenu } = Menu
export default class Asider extends Component {
state = {
collapsed: false
}
//退出登陆
close = () => {
localStorage.removeItem('token')//清除token
}
toggleCollapsed = () => {
this.setState({
collapsed: !this.state.collapsed
})
}
render() {
return (
<div className="mytbody">
{/* 导航区 */}
<div style={{ width: 256 }}>
<Menu defaultSelectedKeys={['1']} defaultOpenKeys={['sub1']} mode="inline" theme="dark" inlineCollapsed={this.state.collapsed}>
<Menu.Item key="1" icon={<PieChartOutlined />}>
<Link to="/home/message">个人资料介绍</Link>
</Menu.Item>
<Menu.Item key="2" icon={<DesktopOutlined />}>
<Link to="/home/photo">相册</Link>
</Menu.Item>
<Menu.Item key="3" icon={<ContainerOutlined />}>
<Link to="/" onClick={this.close}>
退出
</Link>
</Menu.Item>
<SubMenu key="sub1" icon={<MailOutlined />} title="Navigation One">
<Menu.Item key="5">Option 5</Menu.Item>
<Menu.Item key="6">Option 6</Menu.Item>
<Menu.Item key="7">Option 7</Menu.Item>
<Menu.Item key="8">Option 8</Menu.Item>
</SubMenu>
<SubMenu key="sub2" icon={<AppstoreOutlined />} title="Navigation Two">
<Menu.Item key="9">Option 9</Menu.Item>
<Menu.Item key="10">Option 10</Menu.Item>
<SubMenu key="sub3" title="Submenu">
<Menu.Item key="11">Option 11</Menu.Item>
<Menu.Item key="12">Option 12</Menu.Item>
</SubMenu>
</SubMenu>
</Menu>
</div>
{/* 展示区 */}
<Route path="/home/message" component={Message} />
<Route path="/home/photo" component={Photo} />
<Redirect to="/home/message" />
</div>
)
}
}
.mytbody {
display: flex;
}
Message
import React, { Component } from 'react'
import { Layout } from 'antd'
import './Message.css'
// import img1 from '../../image/lx.jpg'
import img2 from '../../image/lx1.jpg'
import img3 from '../../image/lx2.jpg'
import img4 from '../../image/lx3.jpg'
const { Header, Footer, Sider, Content } = Layout
export default class Message extends Component {
render() {
return (
<div>
<Layout>
<Sider>
<div className="profile">
{/* eslint-disable-next-line jsx-a11y/alt-text */}
<img src="http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcd996d7a.jpg" />
<h3>李现</h3>
<p>actor</p>
<div className="share">
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a href="https://www.baidu.com" className="fab fa-facebook-f"></a>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a href="https://www.baidu.com" className="fab fa-twitter"></a>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a href="https://www.baidu.com" className="fab fa-instagram"></a>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a href="https://www.baidu.com" className="fab fa-linkedin"></a>
</div>
<div className="buttons">
<a href="https://www.baidu.com" className="btn">
download cv
</a>
<a href="https://www.baidu.com" className="btn">
hire me
</a>
</div>
</div>
</Sider>
<Layout>
<Header className="header-title">
<h3 className="title">你不知道的李现</h3>
</Header>
<Content>
<div className="about">
<p>李现,曾用名李晛,1991年10月19日出生于湖北省咸宁市,成长于湖北省荆州市 ,中国内地影视男演员,毕业于北京电影学院表演系2010级</p>
<p>
李现出生于湖北省咸宁市,成长于湖北省荆州市,其父母在荆州市工作 [1] ,他在上初二的时候体重达到了160斤,到了高三时他的体重开始下降。李现高中就读于荆州中学,由于第一年高考不理想,第二年他便报考了表演和播音主持方向,之后顺利考上了北京电影学院表演系2010级 [23]
,而他也是当时湖北省荆州市第一位考取北京电影学院的大学生;此外,他还接受了电视台的采访,展示了吹萨克斯、弹吉他等才艺 [24] 。
</p>
</div>
<div className="display">
<ul>
<li>中文名: 李现</li>
<li>别 名: 李晛、现哥、现现</li>
<li>国 籍: 中国</li>
<li>出生地: 湖北省咸宁市</li>
</ul>
<ul>
<li>星 座: 天秤座</li>
<li>身 高: 185cm</li>
<li>职 业: 演员</li>
<li>毕业院校: 北京电影学院</li>
</ul>
</div>
</Content>
<Footer>
<div className="image">
<h3 className="title">心之所"现"</h3>
<div className="box-container">
<div className="box">
<img src={img2} alt="" />
</div>
<div className="box">
<img src={img3} alt="" />
</div>
<div className="box">
<img src={img4} alt="" />
</div>
</div>
</div>
</Footer>
</Layout>
</Layout>
</div>
)
}
}
:root {
--blue: #2980b9;
--black: #333;
--white: #fff;
--light-color: #777;
--light-bg: #eee;
--box-shadow: 0 5px 10px rgba(0, 0, 00, 0.1);
}
* {
font-family: serif;
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
border: none;
text-decoration: none;
text-transform: capitalize;
transition: all 0.2s linear;
list-style: none;
}
.btn {
margin-top: 10px;
display: inline-block;
padding: 7px 20px;
border-radius: 5px;
background: var(--blue);
color: var(--white);
font-size: 17px;
}
.profile {
flex: 1 1 400px;
height: 600px;
background: var(--white);
padding: 30px 20px;
text-align: center;
position: sticky;
border-right: 2px solid var(--black);
top: 20px;
left: 0px;
}
.profile img {
height: 150px;
width: 150px;
border-radius: 50%;
border: 2px solid var(--black);
object-fit: cover;
margin-bottom: 10px;
}
.profile h3 {
color: var(--black);
font-size: 20px;
}
.profile p {
color: var(--blue);
padding: 5px 0;
margin-bottom: 10px;
font-size: 15px;
}
.profile .share {
margin: 10px 0;
}
.profile .share a {
height: 50px;
width: 50px;
line-height: 50px;
font-size: 20px;
border-radius: 50%;
color: var(--black);
background: var(--light-bg);
}
.profile .share a:hover {
background: var(--blue);
color: var(--white);
box-shadow: var(--box-shadow);
}
.title {
margin-top: 10px;
font-weight: 400;
font-style: italic;
letter-spacing: 1em;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #000;
color: var(#fff);
}
.about p {
color: var(--light-color);
text-indent: 2em;
line-height: 30px;
}
.display {
display: flex;
justify-content: space-around;
}
.display ul {
flex: 1 1 200px;
padding: 10px 20px;
font-size: 18px;
font-weight: bold;
}
.image .title {
font-weight: 400;
font-style: italic;
letter-spacing: 1em;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #000;
color: var(--blue);
}
.image .box-container {
display: flex;
justify-content: space-around;
}
.image .box-container img {
margin-right: 5px;
width: 200px;
height: 300px;
}
.header-title {
background-color: #2496d8;
}
Photo
import React, { Component } from 'react'
import axios from 'axios'
// import { Button } from 'antd'
import './Photo.css'
export default class Photo extends Component {
state = {
imgList: [],
flag: false
}
componentDidMount() {
axios.get('http://localhost:8080/api/photo').then(
(req) => {
this.setState({
imgList: req.data.data
})
},
(err) => {
console.log(err.message)
}
)
}
componentWillUnmount() {
this.setState = () => {
return
}
}
// getList = () => {
// axios.get('http://localhost:8080/api/photo').then(
// (req) => {
// this.setState({
// imgList: req.data.data
// })
// },
// (err) => {
// console.log(err.message)
// }
// )
// }
// hideList = () => {
// this.setState({
// imgList: []
// })
// }
render() {
const { imgList } = this.state
return (
<div>
{/* <Button type="primary" danger onClick={this.getList}>
display
</Button>
<Button type="primary" danger onClick={this.hideList}>
hide
</Button> */}
<div className="row">
{imgList.map((item) => {
return (
<div className="col-sm-6 col-md-4" key={item.id}>
<div className="thumbnail">
<img src={item.avator} alt="..." />
<div className="caption">
<h3>Thumbnail label</h3>
<p>...</p>
<p>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" className="btn btn-primary" role="button">
Button
</a>{' '}
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a href="#" className="btn btn-default" role="button">
Button
</a>
</p>
</div>
</div>
</div>
)
})}
</div>
</div>
)
}
}
.test {
width: 400px;
grid-template: 400px;
}
其它文件
index.html
引入样式
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.css" rel="stylesheet" />
App.jsx
import React, { Component } from 'react'
import { Route, Redirect } from 'react-router-dom'
import './App.css'
import Home from './components/Home/Home'
import Login from './components/Login/Login'
export default class App extends Component {
render() {
return (
<div>
{/* exact精准匹配 */}
<Route exact path="/" component={Login} />
<Route path="/home" component={Home} />
<Redirect to="/" />
</div>
)
}
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
接口文件
react_demo.js
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())
app.use((req, res, next) => {
console.log('请求中...')
next()
})
app.get('/api/photo', (req, res) => {
const users = {
msg: 'ok',
status: 200,
data: [
{ id: 1, avator: 'http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcd996d7a.jpg' },
{ id: 2, avator: 'http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcda5da91.jpg' },
{ id: 3, avator: 'http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcdb1f5d2.jpg' },
{ id: 4, avator: 'http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcdbd5403.jpg' },
{ id: 5, avator: 'http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcdc9afb0.jpg' },
{ id: 6, avator: 'http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcdd55abc.jpg' },
{ id: 7, avator: 'http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcde159b8.jpg' },
{ id: 8, avator: 'http://pic1.win4000.com/wallpaper/2019-07-30/5d3ffcded4110.jpg' },
{ id: 9, avator: 'http://pic1.win4000.com/wallpaper/2019-08-02/5d44009e69276.jpg' }
]
}
res.send(users)
})
app.listen(8080, (err) => {
// console.log(err)
if (!err) {
console.log('The server is running at http://localhost:8080/api/photo')
}
})
运行项目
nodemon .\react_demo.js
启动接口npm start
启动项目
效果图
react+node.js+antd+bootstrap实现个人空间案例