一、使用 CRA 創建項目
npx create-react-app name
二、按照業務規范整理項目目錄 (重點src目錄)
三、安裝插件
npm install sass -Dnpm install antd --savenpm install react-router-dom
四、配置基礎路由 Router
1. 安裝路由包 react-router-dom
2. 準備兩個基礎路由組件 Layout 和 Login
3. 在 router/index.js 文件中引入組件進行路由配置,導出 router 實例
4. 在入口文件中渲染 <RouterProvider />,傳入? router 實例
router/index.js
import { createBrowserRouter } from "react-router-dom";// 配置路由實例
const router = createBrowserRouter([{path: "/",element: <Layout />,},{path: "/login",element: <Login />,},
]);export default router;
import { RouterProvider } from "react-router-dom";
import router from "./router";const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<RouterProvider router={router} />);
五、登錄
使用 AntD 現成的組件 創建登錄頁的內容結構
?主要組件:Card、Form、Input、Button
<div><Card><img />{/* 登錄表單 */}<Form><Form.Item><Input /></Form.Item> <Form.Item><Input /></Form.Item> </Form></Card></div>
1. 表單校驗實現
表單校驗可以在提交登錄之前,校驗用戶的輸入是否符合預期,如果不符合就阻止提交,顯示錯誤信息
<Form.Itemlabel="Username"name="username"rules={[{ required: true, message: '請輸入用戶名!' }]}><Input size="large" placeholder="請輸入手機號" /></Form.Item>
?增加失焦時校驗
<Form validateTrigger="onBlur">...
</Form>
手機號為有效格式
<Form.Itemname="mobile"// 多條校驗邏輯 先校驗第一條 第一條通過之后再校驗第二條rules={[{required: true,message: '請輸入手機號',},{pattern: /^1[3-9]\d{9}$/,message: '請輸入正確的手機號格式'}]}><Input size="large" placeholder="請輸入手機號" /></Form.Item>
2. 獲取表單數據
當用戶輸入了正確的表單內容,點擊確認按鈕時,需要收集用戶輸入的內容,用來提交接口請求
解決方案:給 Form 組件綁定 onFinish 回調函數,通過回調函數的參數獲取用戶輸入的內容
const onFinish = async (values) => {console.log(values)}<Form onFinish={onFinish} validateTrigger="onBlur">...<Form.Item><Button type="primary" htmlType="submit" size="large" block>登錄</Button></Form.Item></Form>
3. 封裝 request 請求模塊
?在整個項目中會發送很多網絡請求,使用 axios 三方庫做好統一封裝,方便統一管理和復用
npm i axios
?utils/request.js
// axios的封裝處理
import axios from "axios"// 1. 根域名配置
// 2. 超時時間
// 3. 請求攔截器 / 響應攔截器const request = axios.create({baseURL: 'http://geek.itheima.net/v1_0',timeout: 5000
})// 添加請求攔截器
// 在請求發送之前 做攔截 插入一些自定義的配置 [參數的處理]
request.interceptors.request.use((config) => {return config
}, (error) => {return Promise.reject(error)
})// 添加響應攔截器
// 在響應返回到客戶端之前 做攔截 重點處理返回的數據
request.interceptors.response.use((response) => {// 2xx 范圍內的狀態碼都會觸發該函數。// 對響應數據做點什么return response.data
}, (error) => {// 超出 2xx 范圍的狀態碼都會觸發該函數。// 對響應錯誤做點什么return Promise.reject(error)
})export { request }
utils/index.js
// 統一中轉工具模塊函數
// import {request} from '@/utils'import { request } from './request'export {request,
}
4. 使用 redux 管理 token
Token 作為一個用戶的標識數據,需要在很多個模塊中共享,Redux 可以方便的解決共享問題
(1)redux 中編寫獲取 Token 的 異步獲取和同步修改
(2)Login 組件負責提交 action 并且把表單數據傳遞過來
npm i react-redux @reduxjs/toolkit
store/modules/user.js
// 和用戶相關的狀態管理
import { createSlice } from '@reduxjs/toolkit'
import { setToken as _setToken, getToken } from '@/utils'const userStore = createSlice({name: "user",// 數據狀態initialState: {token: getToken() || '',},// 同步修改方法reducers: {setToken (state, action) {state.token = action.payload_setToken(action.payload)},}
})// 解構出actionCreater
const { setToken } = userStore.actions// 獲取reducer函數
const userReducer = userStore.reducer// 登錄獲取token異步方法封裝
const fetchLogin = (loginForm) => {return async (dispatch) => {// 1. 發送異步請求const res = await request.post('authorizations', loginForm)// 2. 提交同步 action 進行 token 存入dispatch(setToken(res.data.token))}
}export { fetchLogin, setToken }export default userReducer
store/index.js
// 組合redux子模塊 + 導出store實例
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './modules/user'export default configureStore({reducer: {user: userReducer}
})
index.js
import { RouterProvider } from 'react-router-dom'
import router from './router'
import { Provider } from 'react-redux'
import store from './store'const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Provider store={store}><RouterProvider router={router} /></Provider>
)
封裝 localStorage - Token 持久化
現存問題:
Redux 存入 Token 之后如果刷新瀏覽器,Token 會丟失(持久化就是防止刷新時丟失 Token)
問題原因:
Redux 是基于瀏覽器內存的儲存方式,刷新時狀態恢復為初始值
utils/token.js
// 封裝基于ls存取刪三個方法
const TOKENKEY = 'token_key'function setToken (token) {return localStorage.setItem(TOKENKEY, token)
}function getToken () {return localStorage.getItem(TOKENKEY)
}function removeToken () {return localStorage.removeItem(TOKENKEY)
}export {setToken,getToken,removeToken
}
utils/index.js
// 統一中轉工具模塊函數
// import {request} from '@/utils'import { request } from './request'
import { setToken, getToken, removeToken } from './token'export {request,setToken,getToken,removeToken
}
pages/login/index.js
import { useDispatch } from 'react-redux'
import { fetchLogin } from '@/store/modules/user'
import { useNavigate } from 'react-router-dom'const Login = () => {const dispatch = useDispatch()const navigate = useNavigate()const onFinish = async (values) => {console.log(values)// 觸發異步action fetchLoginawait dispatch(fetchLogin(values))// 1. 跳轉到首頁navigate('/')// 2. 提示一下用戶message.success('登錄成功')}return (...)
}
5. Axios 請求攔截器注入 Token
Token 作為用戶的一個標識數據,后端很多接口都會以它作為接口權限判斷的依據;請求攔截器注入 Token 之后,所有用到 Axios 實例的接口請求都自動攜帶了 Token
utils/request.js
import { getToken } from "./token"// 添加請求攔截器
// 在請求發送之前 做攔截 插入一些自定義的配置 [參數的處理]
request.interceptors.request.use((config) => {// 操作這個config 注入token數據// 1. 獲取到token// 2. 按照后端的格式要求做token拼接const token = getToken()if (token) {config.headers.Authorization = `Bearer ${token}`}return config
}, (error) => {return Promise.reject(error)
})
6. 使用 Token 做路由權限控制
有些路由頁面的內容信息比較敏感,如果用戶沒有經過登錄獲取到有效 Token,是沒有權限跳轉的,根據 Token 的有無控制當前路由是否可以跳轉,就是路由的權限控制
components/AuthRoute.js
// 封裝高階組件
// 核心邏輯: 有token 正常跳轉 無token 去登錄import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'export function AuthRoute ({ children }) {const token = getToken()if (token) {return <>{children}</>} else {return <Navigate to={'/login'} replace />}
}
router/index.js
import { createBrowserRouter } from 'react-router-dom'
import { AuthRoute } from '@/components/AuthRoute'// 配置路由實例
const router = createBrowserRouter([{path: "/",element: <AuthRoute> <Layout /></AuthRoute>,},{path: "/login",element: <Login />}
])export default router
六、Layout
1. 樣式初始化
樣式 reset
npm install normalize.css
index.js
import 'normalize.css'const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Provider store={store}><RouterProvider router={router} /></Provider>
)
index.scss
html,
body {margin: 0;height: 100%;
}#root {height: 100%;
}
2.? 二級路由配置
(1)準備三個二級路由
(2)router 中通過 children 配置項進行配置
(3)Layout 組件中配置二級路由出口
router/index.js
const router = createBrowserRouter([{path: "/",element: <AuthRoute> <Layout /></AuthRoute>,children: [{index: true,element: <Home />},{path: 'article',element: <Article />},{path: 'publish',element: <Publish />}]},{path: "/login",element: <Login />}
])export default router
pages/Layout/index.js
<Layout><Header>...</Header><Layout><Sider><Menu></Menu></Sider><Layout style={{ padding: 20 }}>{/* 二級路由的出口 */}<Outlet /></Layout></Layout></Layout>
3. 點擊菜單跳轉路由
實現效果:點擊左側菜單可以跳轉到對應的目標路由
思路分析:
(1)左側菜單要和路由形成一一對應的關系
(2)點擊時拿到路由路徑,調用路由方法跳轉(跳轉到對應的路由下面)
?具體操作:
(1)菜單參數 Item 中 key 屬性換成路由的路由地址
(2)點擊菜單時通過 key 獲取路由地址跳轉
?pages/Layout/index.js
const items = [{label: '首頁',key: '/',icon: <HomeOutlined />,},{label: '文章管理',key: '/article',icon: <DiffOutlined />,},{label: '創建文章',key: '/publish',icon: <EditOutlined />,},
]const GeekLayout = () => {const navigate = useNavigate()const onMenuClick = (route) => {const path = route.keynavigate(path)}return (<Layout><Header className="header">...</Header><Layout><Sider><Menumode="inline"theme="dark"defaultSelectedKeys={['1']}onClick={onMenuClick}items={items}style={{ height: '100%', borderRight: 0 }}></Menu></Sider><Layout>{/* 二級路由的出口 */}<Outlet /></Layout></Layout></Layout>)
}
export default GeekLayout
4. 根據當前路由路徑高亮菜單
?實現效果:頁面在刷新時可以根據當前的路由路徑讓對應的左側菜單高亮顯示
思路分析;
(1)獲取當前 url 上的路由路徑
(2)找到菜單組件負責高亮的屬性,綁定當前的路由路徑
// 反向高亮// 1. 獲取當前路由路徑const location = useLocation()console.log(location.pathname)const selectedkey = location.pathname<Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"// 修改selectedKeys={selectedkey}onClick={onMenuClick}items={items}style={{ height: '100%', borderRight: 0 }}></Menu></Sider>
5. 展示個人信息
關鍵問題:用戶信息應該放到哪里維護?
和 Token 令牌類似,用戶的信息通常很有可能在多個組件中都需要共享使用,所以同樣應該放到Redux 中維護
(1)使用 Redux 進行信息管理
(2)Layout 組件中提交 action
(3)Layout 組件中完成渲染
store/modules/user.js
// 和用戶相關的狀態管理
import { createSlice } from '@reduxjs/toolkit'
import { setToken as _setToken, getToken, removeToken } from '@/utils'
import { loginAPI, getProfileAPI } from '@/apis/user'const userStore = createSlice({name: "user",// 數據狀態initialState: {token: getToken() || '',userInfo: {}},// 同步修改方法reducers: {setToken (state, action) {state.token = action.payload_setToken(action.payload)},setUserInfo (state, action) {state.userInfo = action.payload},}
})// 解構出actionCreater
const { setToken, setUserInfo } = userStore.actions// 獲取reducer函數
const userReducer = userStore.reducer// 登錄獲取token異步方法封裝
const fetchLogin = (loginForm) => {return async (dispatch) => {const res = await request.post('/authorizations', loginForm)dispatch(setToken(res.data.token))}
}// 獲取個人用戶信息異步方法
const fetchUserInfo = () => {return async (dispatch) => {const res = await request.get('/user/profile')dispatch(setUserInfo(res.data))}
}export { fetchLogin, fetchUserInfo }export default userReducer
pages/Layout/index.js
// 觸發個人用戶信息actionconst dispatch = useDispatch()useEffect(() => {dispatch(fetchUserInfo())}, [dispatch])const name = useSelector(state => state.user.userInfo.name)<span className="user-name">{name}</span>
6. 退出登錄
(1)提示用戶是否確認要退出(危險操作,二次確認)
(2)用戶確認之后清除用戶信息(Token 以及其他個人信息)
(3)跳轉到登錄頁(為下次登錄做準備)
pages/Layout/index.js
// 退出登錄確認回調const onConfirm = () => {console.log('確認退出')dispatch(clearUserInfo())navigate('/login')}...<span className="user-logout"><Popconfirm title="是否確認退出?" okText="退出" cancelText="取消" onConfirm={onConfirm}><LogoutOutlined /> 退出</Popconfirm></span>
store/modules/user.js
// 同步修改方法reducers: {setToken (state, action) {state.token = action.payload_setToken(action.payload)},setUserInfo (state, action) {state.userInfo = action.payload},clearUserInfo (state) {state.token = ''state.userInfo = {}removeToken()}}
})
7. 處理 Token 失效
什么是 Token 失效?
為了用戶的安全和隱私考慮,在用戶長時間未在網絡中做出任何操作且規定的失效時間到達之后,當前的 Token 就會失效。一旦失效,不能再作為用戶令牌標識請求隱私數據
前端如何知道 Token 已經失效了?
通常在 Token 失效之后再去請求接口,后端會返回401狀態碼,前端可以監控這個狀態,做后續的操作
Token 失效了前端做什么?
(1)在 axios 攔截中監控 401 狀態碼
(2)清除失效 Token, 跳轉登錄
utils/request.js
import router from "@/router"// 添加響應攔截器
// 在響應返回到客戶端之前 做攔截 重點處理返回的數據
request.interceptors.response.use((response) => {// 2xx 范圍內的狀態碼都會觸發該函數。// 對響應數據做點什么return response.data
}, (error) => {// 超出 2xx 范圍的狀態碼都會觸發該函數。// 對響應錯誤做點什么// 監控 401 token失效console.dir(error)if (error.response.status === 401) {removeToken()router.navigate('/login')// 強制刷新window.location.reload()}return Promise.reject(error)
})
七、Home
1. Echarts 基礎圖表渲染
三方圖表插件如何在項目中快速使用起來?
(1)按照圖表插件文檔中的“快速開始”,快速跑起來 Demo
(2)按照業務需求修改配置項做定制處理
npm install echarts
pages/Home/index.js
// 柱狀圖組件
import * as echarts from 'echarts'
import { useEffect, useRef } from 'react'const Home = () => {const chartRef = useRef(null)useEffect(() => {// 保證dom可用 才進行圖表的渲染// 1. 獲取渲染圖表的dom節點const chartDom = chartRef.current// 2. 圖表初始化生成圖表實例對象const myChart = echarts.init(chartDom)// 3. 準備圖表參數const option = {title: {text: title},xAxis: {type: 'category',data: ['Vue', 'React', 'Angular']},yAxis: {type: 'value'},series: [{data: [10, 40, 70],type: 'bar'}]}// 4. 使用圖表參數完成圖表的渲染option && myChart.setOption(option)}, [title])return (<div ref={chartRef} style={{ width: '500px', height: '400px' }}></div>)
}export default Home
2. Echarts 組件封裝實現
pages/Home/index.js?
import BarChart from "./components/BarChart"const Home = () => {return (<div><BarChart title={'三大框架滿意度'} /><BarChart title={'三大框架使用度'} /></div>)
}export default Home
pages/Home/components/BarCharts.js
// 柱狀圖組件
import * as echarts from 'echarts'
import { useEffect, useRef } from 'react'
// 1. 把功能代碼都放到這個組件中
// 2. 把可變的部分抽象成prop參數const BarChart = ({ title }) => {const chartRef = useRef(null)useEffect(() => {// 保證dom可用 才進行圖表的渲染// 1. 獲取渲染圖表的dom節點const chartDom = chartRef.current// 2. 圖表初始化生成圖表實例對象const myChart = echarts.init(chartDom)// 3. 準備圖表參數const option = {title: {text: title},xAxis: {type: 'category',data: ['Vue', 'React', 'Angular']},yAxis: {type: 'value'},series: [{data: [10, 40, 70],type: 'bar'}]}// 4. 使用圖表參數完成圖表的渲染option && myChart.setOption(option)}, [title])return <div ref={chartRef} style={{ width: '500px', height: '400px' }}></div>
}export default BarChart
八、拓展 - API 模塊封裝
現存問題:
當前的接口請求放到了功能實現的位置,沒有在固定的模塊內維護,后期查找維護困難
解決思路:
把項目中的所有接口按照業務模塊以函數的形式統一封裝到 apis 模塊中
apis/user.js
// 用戶相關的所有請求
import { request } from "@/utils"
// 1. 登錄請求
export function loginAPI (formData) {return request({url: '/authorizations',method: 'POST',data: formData})
}// 2. 獲取用戶信息
export function getProfileAPI () {return request({url: '/user/profile',method: 'GET'})
}
store/modules/user.js
import { loginAPI, getProfileAPI } from '@/apis/user'// 登錄獲取token異步方法封裝
const fetchLogin = (loginForm) => {return async (dispatch) => {const res = await loginAPI(loginForm)dispatch(setToken(res.data.token))}
}// 獲取個人用戶信息異步方法
const fetchUserInfo = () => {return async (dispatch) => {const res = await getProfileAPI()dispatch(setUserInfo(res.data))}
}
九、文章發布
1. 創建并熟悉基礎結構
(1)面包屑導航組件 Breadcrumb
(2)表單組件 Form
(3)輸入框組件 Input
(4)下拉框組件 Select - Option
(5)按鈕組件 Button
2. 準備富文本編輯器
(1)安裝 react-quill 富文本編輯器
// react 18
npm i react-quill@2.0.0-beta.2// react 19
npm i react-quill-new --save
(2)導入編輯器組件和配套樣式文件
(3)渲染編輯器組件
(4)調整編輯器組件樣式
pages/Publish/index.js
import ReactQuill from 'react-quill'
import 'react-quill/dist/quill.snow.css'const { Option } = Selectconst Publish = () => {return (<div className="publish"><Cardtitle={<Breadcrumb items={[{ title: <Link to={'/'}>首頁</Link> },{ title: `${articleId ? '編輯' : '發布'}文章` },]}/>}><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="請輸入文章內容"/></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. 頻道數據獲取渲染
(1)根據接口文檔在 APIS 模塊中封裝接口函數
(2)使用 useState 維護數據
(3)在 useEffect 中調用接口獲取數據并存入 state
(4)綁定數據到下拉框組件
apis/article.js
// 封裝和文章相關的接口函數
import { request } from "@/utils"// 1. 獲取頻道列表
export function getChannelAPI () {return request({url: '/channels',method: 'GET'})
}// 2. 提交文章表單
export function createArticleAPI (data) {return request({url: '/mp/articles?draft=false',method: 'POST',data})
}
pages/Publish/index.js ?
// 獲取頻道列表const [channelList, setChannelList] = useState([])useEffect(() => {// 1. 封裝函數 在函數體內調用接口const getChannelList = async () => {const res = await getChannelAPI()setChannelList(res.data.channels)}// 2. 調用函數getChannelList()}, [])
pages/Publish/index.js ??
<Form.Itemlabel="頻道"name="channel_id"rules={[{ required: true, message: '請選擇文章頻道' }]}
><Select placeholder="請選擇文章頻道" style={{ width: 400 }}>{/* value屬性用戶選中之后會自動收集起來作為接口的提交字段 */}{channelList.map(item => <Option key={item.id} value={item.id}>{item.name}</Option>)}</Select>
</Form.Item>
4. 收集表單數據提交表單
(1)使用 Form 組件收集表單數據
(2)按照接口文檔封裝接口函數
(3)按照接口文檔處理表單數據
(4)提交接口并驗證是否成功
pages/Publish/index.js
// 提交表單const onFinish = (formValue) => {console.log(formValue)const { title, content, channel_id } = formValue// 1. 按照接口文檔的格式處理收集到的表單數據const reqData = {title,content,cover: {type: 0,images: [],},channel_id}// 2. 調用接口提交createArticleAPI(reqData)}
5. 上傳文章封面基礎功能實現
(1)使用現成組件搭建結構
(2)按照 Upload 組件添加配置實現上傳
pages/Publish/index.js ??
// 上傳回調const [imageList, setImageList] = useState([])const onChange = (value) => {console.log('正在上傳中', value)setImageList(value.fileList)}<Form.Item label="封面"><Form.Item name="type"><Radio.Group><Radio value={1}>單圖</Radio><Radio value={3}>三圖</Radio><Radio value={0}>無圖</Radio></Radio.Group></Form.Item>{/* listType: 決定選擇文件框的外觀樣式showUploadList: 控制顯示上傳列表*/}<UploadlistType="picture-card"showUploadListaction={'http://geek.itheima.net/v1_0/upload'}name='image'onChange={onChange}><div style={{ marginTop: 8 }}><PlusOutlined /></div></Upload></Form.Item>
6. 實現切換封面類型
實現效果:只有當前模式為單圖或者三圖模式時才顯示上傳組件
(1)獲取到當前的封面類型
(2)對上傳組件進行條件渲染
pages/Publish/index.js ??
// 切換圖片封面類型const [imageType, setImageType] = useState(0)const onTypeChange = (e) => {console.log('切換封面了', e.target.value)setImageType(e.target.value)}...<FormlabelCol={{ span: 4 }}wrapperCol={{ span: 16 }}// 控制單選框區域 初始的時候為 0,圖片數量為 0initialValues={{ type: 0 }}onFinish={onFinish}form={form}>...<Form.Item label="封面"><Form.Item name="type"><Radio.Group onChange={onTypeChange}><Radio value={1}>單圖</Radio><Radio value={3}>三圖</Radio><Radio value={0}>無圖</Radio></Radio.Group></Form.Item>{/* listType: 決定選擇文件框的外觀樣式showUploadList: 控制顯示上傳列表*/}{imageType > 0 && <UploadlistType="picture-card"showUploadListaction={'http://geek.itheima.net/v1_0/upload'}name='image'onChange={onChange}maxCount={imageType}fileList={imageList}><div style={{ marginTop: 8 }}><PlusOutlined /></div></Upload>}</Form.Item>...</Form>
7. 控制上傳圖片的數量
實現的效果:
(1)單圖模式時,最多能上傳一張圖片
(2)三圖模式時,最多能上傳三張圖片
如何實現:
(1)找到限制上傳數量的組件屬性
(2)使用 imageType 進行綁定控制
pages/Publish/index.js ??
{imageType > 0 && <UploadlistType="picture-card"showUploadListaction={'http://geek.itheima.net/v1_0/upload'}name='image'onChange={onChange}// 控制圖片上傳數量maxCount={imageType}fileList={imageList}><div style={{ marginTop: 8 }}><PlusOutlined /></div></Upload>}
8. 發布帶封面的文章
pages/Publish/index.js ??
// 提交表單const onFinish = (formValue) => {console.log(formValue)// 校驗封面類型imageType是否和實際的圖片列表imageList數量是相等的if (imageList.length !== imageType) return message.warning('封面類型和圖片數量不匹配')const { title, content, channel_id } = formValue// 1. 按照接口文檔的格式處理收集到的表單數據const reqData = {title,content,cover: {type: imageType, // 封面模式images: imageList.map(item => item.response.data.url), // 圖片列表},channel_id}// 2. 調用接口提交createArticleAPI(reqData)}
十、文章列表模塊
1. 功能描述和靜態結構創建
?pages/Article/index.js
// 引入漢化包 時間選擇器顯示中文
import locale from 'antd/es/date-picker/locale/zh_CN'const Article = () => {...return (<div><Cardtitle={<Breadcrumb items={[{ title: <Link to={'/'}>首頁</Link> },{ title: '文章列表' },]} />}style={{ marginBottom: 20 }}><Form initialValues={{ status: null }}><Form.Item label="狀態" name="status"><Radio.Group><Radio value={''}>全部</Radio><Radio value={1}>待審核</Radio><Radio value={2}>審核通過</Radio></Radio.Group></Form.Item><Form.Item label="頻道" name="channel_id"><Selectplaceholder="請選擇文章頻道"style={{ width: 120 }}><Option value="jack">jack</Option><Option value="jack">jack</Option></Select></Form.Item><Form.Item label="日期" name="date">{/* 傳入locale屬性 控制中文顯示*/}<RangePicker locale={locale}></RangePicker></Form.Item><Form.Item><Button type="primary" htmlType="submit" style={{ marginLeft: 40 }}>篩選</Button></Form.Item></Form></Card>{/* 表格區域 */}<Card title={`根據篩選條件共查詢到 ${count} 條結果:`}><Table rowKey="id" columns={columns} dataSource={data} /></Card></div>)
}export default Article
2. 渲染頻道數據
在文章管理里面和創建文章里面都要使用 channelList,把這段代碼封裝成 hook
使用自定義 hook
(1)創建一個 use 打頭的函數
(2)在函數中封裝業務邏輯,并 return 出組件中要用到的狀態數據
(3)組件中導入函數執行并結構狀態數據使用
hooks/useChannel.js
// 封裝獲取頻道列表的邏輯
import { useState, useEffect } from 'react'
import { getChannelAPI } from '@/apis/article'
function useChannel () {// 1. 獲取頻道列表所有的邏輯// 獲取頻道列表const [channelList, setChannelList] = useState([])useEffect(() => {// 1. 封裝函數 在函數體內調用接口const getChannelList = async () => {const res = await getChannelAPI()setChannelList(res.data.channels)}// 2. 調用函數getChannelList()}, [])// 2. 把組件中要用到的數據return出去return {channelList}
}export { useChannel }
pages/Publish/index.js ??
import { useChannel } from '@/hooks/useChannel'const Publish = () => {// 獲取頻道列表const { channelList } = useChannel()...
}
?pages/Article/index.js
import { useChannel } from '@/hooks/useChannel'const Article = () => {const { channelList } = useChannel()
...return (<div>...<Form.Item label="頻道" name="channel_id"><Selectplaceholder="請選擇文章頻道"style={{ width: 120 }}>{channelList.map(item => <Option key={item.id} value={item.id}>{item.name}</Option>)}</Select></Form.Item>...</div>)
}export default Article
3. 渲染 table 文章列表
(1)封裝請求接口
(2)使用 useState 維護狀態數據
(3)使用 useEffect 發送請求
(4)在組件上綁定對應屬性完成渲染
apis/articles.js
// 獲取文章列表
export function getArticleListAPI (params) {return request({url: "/mp/articles",method: 'GET',params})
}
pages/Article/index.js
const Article = () => {// 獲取文章列表const [list, setList] = useState([])const [count, setCount] = useState(0)useEffect(() => {async function getList () {const res = await getArticleListAPI(reqData)setList(res.data.results)setCount(res.data.total_count)}getList()}, [reqData])
...return ({/* 表格區域 */}<Card title={`根據篩選條件共查詢到 ${count} 條結果:`}><Table rowKey="id" columns={columns} dataSource={list} /></Card>)
}
4. 適配文章狀態
實現效果:根據文章不同狀態在狀態列顯示不同 Tag
實現思路:
(1)如果要適配的狀態只有兩個 - 三元條件渲染
(2)如果要適配的狀態有多個 - 枚舉渲染
const Article = () => {const navigate = useNavigate()const { channelList } = useChannel()// 準備列數據// 定義狀態枚舉const status = {1: <Tag color='warning'>待審核</Tag>,2: <Tag color='success'>審核通過</Tag>,}const columns = [...{title: '狀態',dataIndex: 'status',// data - 后端返回的狀態status 根據它做條件渲染// data === 1 => 待審核// data === 2 => 審核通過render: data => status[data]},...]