react+vite+antD+reduce+echarts项目完整记录
之前写前端项目,都是用的vue,从最开始的vue2到后来的vue3,断断续续写了3年,打包工具也从webpack转到了vite,全局数据管理工具从vuex转到了pinia。总体而言,vue3对比vue2,有非常明显的提升,vite比webpack打包的速度更是快了无数倍,至于pinia和vuex,因人而异,我更喜欢pinia,组合式api的写法深得我心。总而言之一句话,我是全方面拥抱了vue3的新技术栈,当然,除了TS,TS对后端比较友好,我只能算半个后端,用不用无所谓。时代在前进,技术在发展,如果永远守着一套陈旧的技术,找各种理由为自己辩解,实在是不明智的选择。
一直想学一下react,中途学过几次,因为平时工作事情太多不得不停下来。刚开始接触jsx,我是抵制的,vue把html、js和css进行了严格的区分,并摆脱了原生的dom操作,jsx却又把这些混在一起,写代码的时候让我感觉像吃了si一样难受。学完之后依然觉得很难受,但为啥我还是坚持要学react呢,这么几个原因:
- 从全球来看,react是最火的前端框架,vue只在国内火,我在看国外的一些项目的源码时,发现自己完全看不懂在写什么,甚至国内,有些开源项目也只出了react的包,比如mapv
- 换一种框架,扩展一下自己的技能树
- 熟悉原生js
花了3天速刷了一遍B站黑马前端讲师的课,并跟着完整写了一个非常简单的项目,后端接口也是用的黑马的,感谢黑马,记录一下完整的过程,为自己后面写项目提供参考,也为后来人提供参考
项目最终界面:
- 登录界面
- 首页
- 文章管理
- 创建文章
目前就这些了,以下进入正题
〇、代码仓库地址
https://gitee.com/hgandzl/react-vite
一、创建项目并配置基础环境
1. vite创建项目
黑马老师是基于CRA创建项目,应该是和webpack相关的技术,没深入了解,我是用的vite
vite创建前端项目的指令
npm create vite@latest
创建过程如下:
vscode打开创建的项目,执行npm i
后执行npm run dev
,即可打开默认的vite+react项目
2. 整理项目目录
项目src文件夹下依次创建如下文件夹
-src-apis 项目接口函数-assets 项目资源文件,比如,图片等-components 通用组件-pages 页面组件-router 路由-store 集中状态管理-utils 工具,比如,token、axios 的封装等-App.jsx 根组件-index.scss 全局样式-main.jsx 项目入口
删除无关的文件,只保留App.jsx和main.jsx,并删除相关引入
删除main.jsx中的严格节点模式
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";ReactDOM.createRoot(document.getElementById("root")).render(<App />);
删除App.jsx中无关的代码,保留基础组件
function App() {return <>app</>;
}export default App;
3. 使用scss预处理器
实现步骤
- 安装解析 sass 的包:
npm i sass -D
- 创建全局样式文件:
index.scss
index.scss
* {margin: 0;padding: 0;
}
项目入口文件引入index.scss
4. 使用Ant Design作为UI框架
实现步骤
- 安装 antd 组件库:
npm i antd
- 页面上导入并使用
5. 配置基础路由
实现步骤
-
安装路由包
npm i react-router-dom
-
准备
Layout
和Login
俩个基础组件pages目录下新建两个组件,分别是pages/Layout/index.jsx和pages/Login/index.jsx,并同步新建样式文件
pages/Layout/index.jsx
const Layout = () => {return <div>this is layout</div> } export default Layout
pages/Login/index.jsx
const Login = () => {return <div>this is login</div> } export default Login
-
配置路由
router文件夹下新建index.jsx文件,并配置如下基础路由
import { createBrowserRouter } from 'react-router-dom'import Login from '../pages/Login' import Layout from '../pages/Layout'const router = createBrowserRouter([{path: '/',element: <Layout />,},{path: '/login',element: <Login />,}, ])export default router
-
全局挂载路由
和vue项目类似,路由要全局挂载
main.jsx
import React from "react"; import ReactDOM from "react-dom/client"; import "./index.scss"; import router from "./router"; import { RouterProvider } from "react-router-dom";ReactDOM.createRoot(document.getElementById("root")).render(<RouterProvider router={router} /> );
二、编写登录页面
1. 使用antd搭建基本结构
实现步骤
-
在
Login/index.js
中创建登录页面基本结构import "./index.scss"; import { Card, Form, Input, Button } from "antd"; import logo from "../../assets/global.png";const Login = () => {return (<div className="login"><Card className="login-container"><img className="login-logo" src={logo} alt="" />{/* 登录表单 */}<Form><Form.Item><Input size="large" placeholder="请输入手机号" /></Form.Item><Form.Item><Input size="large" placeholder="请输入验证码" /></Form.Item><Form.Item><Button type="primary" htmlType="submit" size="large" block>登录</Button></Form.Item></Form></Card></div>); };export default Login;
-
在 Login 目录中创建 index.scss 文件,指定组件样式
.login {width: 100%;height: 100%;position: absolute;left: 0;top: 0;// background: center/cover url('~@/assets/login.png');.login-logo {// width: 200px;// height: 60px;display: block;margin: 0 auto 20px;}.login-container {width: 440px;height: 400px;position: absolute;left: 50%;top: 50%;transform: translate(-50%, -50%);box-shadow: 0 0 50px rgb(0 0 0 / 10%);}.login-checkbox-label {color: #1890ff;} }
启动项目,地址输入登录页面路由,显示如下
2. 实现表单校验功能
实现步骤
- 为 Form 组件添加
validateTrigger
属性,指定校验触发时机的集合 - 为 Form.Item 组件添加 name 属性,这是为了能取到表单项里面的值
- 为 Form.Item 组件添加
rules
属性,用来添加表单校验规则对象,这与elementplus的验证机制高度相似
整体实现代码
const Login = () => {return (<Form validateTrigger={['onBlur']}><Form.Itemname="mobile"rules={[{ required: true, message: '请输入手机号' },{pattern: /^1[3-9]\d{9}$/,message: '手机号码格式不对'}]}><Input size="large" placeholder="请输入手机号" /></Form.Item><Form.Itemname="code"rules={[{ required: true, message: '请输入验证码' },]}><Input size="large" placeholder="请输入验证码" maxLength={6} /></Form.Item><Form.Item><Button type="primary" htmlType="submit" size="large" block>登录</Button></Form.Item></Form>)
}
3. 获取登录form的表单数据
实现步骤
- 为 Form 组件添加
onFinish
属性,该事件会在点击登录按钮时触发。其实这个onFinish也是button中的submit绑定的,也就是说点击submit按钮时,就会触发onFinish方法 - 创建 onFinish 函数,通过函数参数 values 拿到表单值,onFinish函数传递默认参数,参数就是表单内的每一项数据
const onFinish = (formData) => {console.log(formData);};
....<Form validateTrigger={["onBlur"]} onFinish={onFinish}>....<Form.Item><Button type="primary" htmlType="submit" size="large" block>登录</Button></Form.Item></Form>....export default Login;
4. aixos二次封装
因为需要向后端发起请求,涉及token认证的地方需要设置请求拦截器,也可能需要设置响应拦截器,所以需要对axios二次封装
在此之前,我曾详细记录过如何使用react+redux完成登录页面及token的存取和登录保持,因此,整个登录不再赘述,只上关键过程和重要代码
实现步骤
- 安装 axios 到项目
- 创建 utils/http.jsx 文件
- 创建 axios 实例,配置
baseURL,请求拦截器,响应拦截器
https.jsx
import axios from "axios";const http = axios.create({baseURL: "http://geek.itheima.net/v1_0",timeout: 5000,
});// axios请求拦截器
http.interceptors.request.use((config) => {return config;},(e) => Promise.reject(e)
);// axios响应式拦截器
http.interceptors.response.use((res) => res.data,(e) => {console.log(e);return Promise.reject(e);}
);export default http;
5. 引入redux管理全局数据
react中的redux就相当于vue中的vuex,都用于管理全局数据,登录时后端返回的token数据就是全局需要的数据
实现步骤
-
安装redux相关包
npm i react-redux @reduxjs/toolkit
-
配置redux,配置redux在另一篇博客中有详细记录,不再具体说明
- 新建user模块
store/moduls/user.jsx
,填入以下代码
import { createSlice } from "@reduxjs/toolkit"; import http from "../../utils/http"; const userStore = createSlice({name: "user",// 数据状态initialState: {token: "",},// 同步修改方法reducers: {setToken(state, action) {state.userInfo = action.payload;},}, });// 解构出actionCreater const { setToken } = userStore.actions;// 获取reducer函数 const userReducer = userStore.reducer;// 异步方法封装 const fetchLogin = (loginForm) => {return async (dispatch) => {const res = await http.post("/authorizations", loginForm);dispatch(setToken(res.data.token));}; };export { fetchLogin };export default userReducer;
- 在index.jsx中注册子模块,
store/index.jsx
import { configureStore } from "@reduxjs/toolkit"; import userReducer from "./modules/user";export default configureStore({reducer: {user: userReducer} })
- 入口文件中全局注册store,
main.jsx
import ReactDOM from "react-dom/client"; import App from "./App.jsx"; import "./index.scss"; import { RouterProvider } from "react-router-dom"; import router from "./router/index.jsx"; import { Provider } from "react-redux"; import store from "./store/index.jsx";ReactDOM.createRoot(document.getElementById("root")).render(<Provider store={store}><RouterProvider router={router} /></Provider> );
- 新建user模块
6. 实现登录逻辑
实现步骤
- 收集表单信息,向后端发送登录请求
- 登录成功后跳转到首页,提示用户登录成功
主要是修改上面的Login/index.jsx中的onFinish方法
如下:
// 省略其他代码
// .......
import { useDispatch } from "react-redux";
import { fetchLogin } from "../../store/modules/user";
import { useNavigate } from "react-router-dom";// 省略其他代码
// .......const onFinish = async (formData) => {console.log(formData);await dispatch(fetchLogin(formData))navigate('/')message.success('登录成功')};
// 省略其他代码
// .......
7. 实现token持久化存储
其实就是登录时把token存到localstorage中去,react+redux完成登录页面及token的存取和登录保持–这篇博客中详细记录了,这里只上关键代码
- 首先封装token的存、取、删方法,
utils/token.jsx
const TOKENKEY = "token_key";function setToken(token) {return localStorage.setItem(TOKENKEY, token);
}function getToken() {return localStorage.getItem(TOKENKEY);
}function clearToken() {return localStorage.removeItem(TOKENKEY);
}export { setToken, getToken, clearToken };
-
localstorage中持久化存储token,逻辑就是在redux的同步方法中,存储token,同时,token的初始化不再是空值,当localstorage中有token时,就取出来,没有就是空值
store/moduls/user.jsx
import { createSlice } from "@reduxjs/toolkit";
import http from "../../utils/http";
import { setToken as _setToken, getToken } from "../../utils/token";
const userStore = createSlice({name: "user",// 数据状态initialState: {// 差异1token: getToken() || "",},// 同步修改方法reducers: {setToken(state, action) {state.token = action.payload;// 存入本地_setToken(state.token);},},
});
8. 请求拦截器中携带token
常规操作,在axios二次封装的http.jsx文件中添加以下代码
// axios请求拦截器
http.interceptors.request.use((config) => {// 导入getToken方法const token = getToken()if (token) {// 请求头携带tokenconfig.headers.Authorization = "Bearer " + token;}return config;},(e) => Promise.reject(e)
);
9. 路由守卫
vue中的路由守卫是在router中实现的,react的做法是封装 AuthRoute
路由鉴权高阶组件,然后将需要鉴权的页面路由配置,替换为 AuthRoute 组件渲染
实现步骤
- 在 components 目录中,创建
AuthRoute/index.jsx
文件 - 登录时,直接渲染相应页面组件
- 未登录时,重定向到登录页面
- 将需要鉴权的页面路由配置,替换为 AuthRoute 组件渲染
AuthRoute/index.jsx
中的代码
import { getToken } from '../../utils/token'
import { Navigate } from 'react-router-dom'const AuthRoute = ({ children }) => {const isToken = getToken()if (isToken) {return <>{children}</>} else {return <Navigate to="/login" replace />}
}export default AuthRoute
Layout页面需要鉴权,所以在路由中修改页面的渲染配置,router/index.jsx
import { createBrowserRouter } from "react-router-dom";import Login from "../pages/Login";
import Layout from "../pages/Layout";
import AuthRoute from "../components/AuthRoute";const router = createBrowserRouter([{path: "/",element: <AuthRoute><Layout /></AuthRoute>,},{path: "/login",element: <Login />,},
]);export default router;
10. 封装接口调用的api
因为后面涉及多个后端接口调用,所以好的做法是把后端接口进行统一的封装
新建apis/user.jsx文件,用于处理用户相关的接口
import http from "../utils/http";export const loginAPI = (data) => {return http({url: "/authorizations",method: "POST",data,});
};
把前面的第5节中redux异步方法请求改写一下
// 异步方法封装
const fetchLogin = (loginForm) => {return async (dispatch) => {const res = await loginAPI(loginForm); // 注意loginAPI要引入dispatch(setToken(res.data.token));};
};
后面其他api也就抽离出来了
三、Layout首页设计
1. 搭建首页基础框架
首页的基础框架长下面这个样子
先填入基础代码
import React, { useEffect, useState } from "react";
import "./index.scss";
import {HomeOutlined,DiffOutlined,EditOutlined,LogoutOutlined,
} from "@ant-design/icons";
import { Breadcrumb, Layout, Menu, theme, Popconfirm } from "antd";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
// import { fetchUserInfo, clearUserInfo } from "../../store/modules/user";
const { Header, Content, Sider } = Layout;const items = [{label: "首页",key: "/",icon: <HomeOutlined />,},{label: "文章管理",key: "/article",icon: <DiffOutlined />,},{label: "创建文章",key: "/publish",icon: <EditOutlined />,},
];const GLayout = () => {const [collapsed, setCollapsed] = useState(false);const {token: { colorBgContainer, borderRadiusLG },} = theme.useToken();return (<Layoutstyle={{minHeight: "100vh",}}><Sidertheme="light"collapsiblecollapsed={collapsed}onCollapse={(value) => setCollapsed(value)}><div className="demo-logo-vertical" /><Menu// theme="dark"defaultSelectedKeys={["/"]}// selectedKeys={selectedKey}mode="inline"items={items}// onClick={clickMenu}/></Sider><Layout><HeaderclassName="header"style={{padding: 0,background: colorBgContainer,}}><div className="logo"></div><div className="user-info"><span className="user-name">React</span><span className="user-logout"><Popconfirmtitle="是否确认退出?"okText="退出"cancelText="取消"// onConfirm={logout}><LogoutOutlined /> 退出</Popconfirm></span></div></Header><ContentclassName="content"style={{margin: "5px 5px",background: colorBgContainer,borderRadius: borderRadiusLG,}}><Outlet /></Content></Layout></Layout>);
};
export default GLayout;
补充对应的样式
.header {display: flex;justify-content: space-between;align-items: center;.logo {width: 200px;height: 60px;background: url('../../assets/global.png') no-repeat center / 160px auto;}.user-info {margin-right: 20px;color: #070707;.user-name {margin-right: 20px;}.user-logout {display: inline-block;cursor: pointer;}}
}.content {height: 100%;}
2. 配置二级路由
就是把左侧的文章管理、创建文章和首页的路由给配置出来
使用步骤
-
在 pages 目录中,分别创建:Home(数据概览)/Article(内容管理)/Publish(发布文章)页面文件夹
-
分别在三个文件夹中创建 index.jsx 并创建基础组件后导出
-
在
router/index.js
中配置嵌套子路由,在Layout
中配置二级路由出口import { createBrowserRouter } from "react-router-dom"; import Layout from "../pages/Layout"; import Login from "../pages/Login"; import AuthRoute from "../components/AuthRoute"; import Home from "../pages/Home"; import Article from "../pages/Article"; import Publish from "../pages/Publish";const router = createBrowserRouter([{path: "/",element: <AuthRoute><Layout /></AuthRoute>,children: [{// path: 'home',index: true,element: <Home />},{path: 'article',element: <Article />},{path: 'publish',element: <Publish />},]},{path: "/login",element: <Login />,}, ]); export default router;
-
使用 Link 修改左侧菜单内容,与子路由规则匹配实现路由切换,前面提供的代码中已经配置好了,就是
<Outlet />
3. 点击菜单跳转至对应的二级路由
- 在menu菜单中添加点击回调函数
const navigate = useNavigate();const clickMenu = (route) => {navigate(route.key);}
-
菜单反向高亮
是个啥意思勒,目前点击菜单,菜单栏是高亮的,但是如果冲地址栏直接输入地址,对应的菜单并不能高亮。。
我在使用elementplus时经常遇到这个问题,一度以为是框架的bug,现在才搞明白,原来是自己没有处理好,处理逻辑是先获取页面当前的地址,然后把menu中的selectedKeys属性设置为当前地址
const GLayout = () => {// 省略部分代码// 获取当前页面地址-----------------1const location = useLocation()const selectedKey = location.pathnamereturn (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">{name}</span><span className="user-logout"><Popconfirm title="是否确认退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"// 将当前页面地址设置为selectedKeys----------------2selectedKeys={selectedKey}items={items}style={{ height: '100%', borderRight: 0 }}onClick={menuClickHandler}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout></Layout></Layout>)
}
4. 头部导航栏显示个人信息
这部分其实应该可以直接登录的时候就直接给了,写在user的store中,不过黑马提供的逻辑是重新调了一个接口,这个接口返回的才是用户信息。别人怎么提供就怎么来吧
实现步骤
-
编写获取个人信息的接口
export const getProfileAPI = () => {return http({url: "/user/profile",}); };
-
在Redux的store中编写获取用户信息的相关逻辑
// 异步方法,获取用户个人信息 const fetchUserInfo = () => {return async (dispatch) => {try {const res = await getProfileAPI();// console.log(res)dispatch(setUserInfo(res.data));} catch (error) {message.error("登录信息失效,请重新登录");}}; };
要把这个方法暴露出去
-
在Layout组件中触发action的执行
-
在Layout组件使用使用store中的数据进行用户名的渲染
以上两步代码如下:
import { fetchUserInfo } from "../../store/modules/user";// 获取用户信息const dispatch = useDispatch();useEffect(() => {dispatch(fetchUserInfo());}, []);const { userInfo } = useSelector((state) => state.user); <span className="user-name">{userInfo.name}</span>
5. 退出登录逻辑
也是常规操作,之前是在pinia中写一个删除store的方法,是个同步方法,redux中差不多
实现步骤
-
为气泡确认框添加确认回调事件,实际上就是onConfirm事件
<Popconfirmtitle="是否确认退出?"okText="退出"cancelText="取消"onConfirm={logout}><LogoutOutlined /> 退出</Popconfirm>
-
在
store/userStore.jsx
中新增退出登录的action函数,在其中删除token// 同步修改方法reducers: {........clearUserInfo(state) {state.token = "";state.userInfo = "";clearToken();},
注意对外暴露出去
-
在回调事件中,调用userStore中的退出action
-
清除用户信息,返回登录页面
const logout = () => {dispatch(clearUserInfo())navigate("/login"); }
6. 处理token失效
一般是token过期后的处理逻辑,后端会返回401代码,响应拦截器中根据这个代码进行路由跳转至登录页面
utils/http.jsx中响应拦截器添加如下代码
import router from "../router";// axios响应式拦截器
http.interceptors.response.use((res) => res.data,(e) => {console.log(e);// 401 -- token失效if(e.response.status === 401){clearToken()// router实例router.navigate('/login')}return Promise.reject(e);}
);export default http;
7. 首页绘制echarts图
echarts图我在vue中画过无数遍了,这里的逻辑基本上一样
- 首先安装echarts
npm i echarts
-
封装一个画图组件
在Home目录下新建components目录,并创建BarChart.jsx组件
封装的代码如下:
import * as echarts from "echarts"; import { useEffect, useRef } from "react"; // 父子组件通讯props const BarChart = ({title,xData,sData,style = { width: "400px", height: "300px" }, }) => {const chartRef = useRef(null);let initChart;const drawChart = () => {if (initChart != null && initChart != "" && initChart != undefined) {initChart.dispose(); //销毁}initChart = echarts.init(chartRef.current);const option = {title: {text: title,},xAxis: {type: "category",data: xData,},yAxis: {type: "value",},series: [{data: sData,type: "bar",},],};initChart.setOption(option);window.addEventListener("resize", () => {initChart.resize();});};useEffect(() => drawChart(), [xData, sData]);return (<><div ref={chartRef} style={style}></div></>); }; export default BarChart;
几个要点记录一下:
- BarChart是子组件,父组件应传递 title, xData, sData, style 这几个属性
- react中获取dom是用的react中useRef钩子,vue中直接就是ref
- useEffect需要监听数据变化,然后重绘图
-
Home组件中调用子组件,并传递子组件所需的数据
import BarChart from "./components/BarChart";const Home = () => {return (<><BarChartxData={["Vue", "React", "Angular"]}sData={[2000, 5000, 1000]}title={"三大框架使用率"}></BarChart><BarChartxData={["Vue", "React", "Angular"]}sData={[200, 500, 100]}title={"三大框架满意度"}style={{ width: "500px", height: "400px" }}></BarChart></>); };export default Home;
最终展示效果
四、发布文章模块
就是下面这个页面
1. 创建基础结构
import {Card,Breadcrumb,Form,Button,Radio,Input,Upload,Space,Select
} from 'antd'
import { PlusOutlined } from '@ant-design/icons'
import { Link } from 'react-router-dom'
import './index.scss'const { Option } = Selectconst Publish = () => {return (<div className="publish"><Cardtitle={<Breadcrumb items={[{ title: <Link to={'/'}>首页</Link> },{ title: '发布文章' },]}/>}><FormlabelCol={{ span: 4 }}wrapperCol={{ span: 16 }}initialValues={{ type: 1 }}><Form.Itemlabel="标题"name="title"rules={[{ required: true, message: '请输入文章标题' }]}><Input placeholder="请输入文章标题" style={{ width: 400 }} /></Form.Item><Form.Itemlabel="频道"name="channel_id"rules={[{ required: true, message: '请选择文章频道' }]}><Select placeholder="请选择文章频道" style={{ width: 400 }}><Option value={0}>推荐</Option></Select></Form.Item><Form.Itemlabel="内容"name="content"rules={[{ required: true, message: '请输入文章内容' }]}></Form.Item><Form.Item wrapperCol={{ offset: 4 }}><Space><Button size="large" type="primary" htmlType="submit">发布文章</Button></Space></Form.Item></Form></Card></div>)
}export default Publish
样式文件
.publish {position: relative;.publish-quill {.ql-editor {min-height: 300px;}}
}.ant-upload-list {.ant-upload-list-picture-card-container,.ant-upload-select {width: 146px;height: 146px;background: #eee;}
}
2. 添加富文本编辑器
实现步骤
-
安装富文本编辑器
npm i react-quill@2.0.0-beta.2
这里可能会报错,应该改成
npm i react-quill@2.0.0-beta.2 --force
-
导入富文本编辑器组件以及样式文件
-
渲染富文本编辑器组件
-
调整富文本编辑器的样式
publish.jsx中的代码
import {Card,Breadcrumb,Form,Button,Radio,Input,Upload,Space,Select, } from "antd"; import { PlusOutlined } from "@ant-design/icons"; import { Link } from "react-router-dom"; import "./index.scss"; import ReactQuill from "react-quill"; import "react-quill/dist/quill.snow.css";const { Option } = Select;const Publish = () => {return (<div className="publish"><Cardtitle={<Breadcrumbitems={[{ title: <Link to={"/"}>首页</Link> },{ title: "发布文章" },]}/>}><FormlabelCol={{ span: 4 }}wrapperCol={{ span: 16 }}initialValues={{ type: 1 }}><Form.Itemlabel="标题"name="title"rules={[{ required: true, message: "请输入文章标题" }]}><Input placeholder="请输入文章标题" style={{ width: 400 }} /></Form.Item><Form.Itemlabel="频道"name="channel_id"rules={[{ required: true, message: "请选择文章频道" }]}><Select placeholder="请选择文章频道" style={{ width: 400 }}><Option value={0}>推荐</Option></Select></Form.Item><Form.Itemlabel="内容"name="content"rules={[{ required: true, message: "请输入文章内容" }]}><ReactQuillclassName="publish-quill"theme="snow"placeholder="请输入内容"></ReactQuill></Form.Item><Form.Item wrapperCol={{ offset: 4 }}><Space><Button size="large" type="primary" htmlType="submit">发布文章</Button></Space></Form.Item></Form></Card></div>); };export default Publish;
3. antD中的select组件获取频道数据
这个数据是从后端获取的
实现步骤
-
使用useState初始化数据和修改数据的方法
const [channels, setChannels] = useState([]);
-
在useEffect中调用接口并保存数据
封装接口代码,apis目录新建article.jsx文件,填写接口请求函数
import http from "../utils/http";export const getChannelAPI = () => {return http({url: "/channels",}); };
Publish.jsx编写请求数据的函数,并在副作用钩子中调用
const fetchChannels = async () => {const res = await getChannelAPI();console.log(res);setChannels(res.data.channels);};useEffect(() => {fetchChannels();}, []);
-
使用数据渲染对应模版
<Select placeholder="请选择文章频道" style={{ width: 400 }}>{channels.map((item) => (<Option value={item.id} key={item.id}>{item.name}</Option>))}</Select>
4. 发布文章
-
先封装发布文章的接口
// 新增 export const publishAPI = (data) => {return http({url: "/mp/articles?draft=false",method: "POST",data,}); };
-
form提交submit的onFinish回调函数
const onFinish = async (values) => {const { channel_id, content, title } = values;const data = {channel_id,content,title,type: 1,cover: {type: 1,images: [],},};await publishAPI(data)};
apis目录新建article.jsx文件,填写接口请求函数
import http from "../utils/http";export const getChannelAPI = () => {return http({url: "/channels",});
};
Publish.jsx编写请求数据的函数,并在副作用钩子中调用
const fetchChannels = async () => {const res = await getChannelAPI();console.log(res);setChannels(res.data.channels);};useEffect(() => {fetchChannels();}, []);
-
使用数据渲染对应模版
<Select placeholder="请选择文章频道" style={{ width: 400 }}>{channels.map((item) => (<Option value={item.id} key={item.id}>{item.name}</Option>))}</Select>
4. 发布文章
-
先封装发布文章的接口
// 新增 export const publishAPI = (data) => {return http({url: "/mp/articles?draft=false",method: "POST",data,}); };
-
form提交submit的onFinish回调函数
const onFinish = async (values) => {const { channel_id, content, title } = values;const data = {channel_id,content,title,type: 1,cover: {type: 1,images: [],},};await publishAPI(data)};
未完待续,后面再补充吧~~~