項目制作流程

一、使用 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]},...]

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/81529.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/81529.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/81529.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ngx_http_random_index_module 模塊概述

一、使用場景 隨機內容分發 當同一目錄下存放多份等價內容&#xff08;如多張輪播圖、不同版本靜態頁面等&#xff09;時&#xff0c;可通過隨機索引實現負載均衡或流量分散。A/B 測試 通過目錄請求自動隨機分配用戶到不同測試組&#xff0c;無需后端邏輯參與。動態“首頁”選…

智能權限守護者:基于Python描述符的動態角色控制實現

智能權限守護者:基于Python描述符的動態角色控制實現 引言:當描述符遇見權限管理 在Python的魔法方法體系中,描述符(Descriptor)以其優雅的屬性訪問控制機制著稱。當我們將描述符與RBAC(基于角色的訪問控制)模型結合,就能創造出既靈活又安全的動態權限管理系統。本文…

Linux 的 UDP 網絡編程 -- 回顯服務器,翻譯服務器

目錄 1. 回顯服務器 -- echo server 1.1 相關函數介紹 1.1.1 socket() 1.1.2 bind() 1.1.3 recvfrom() 1.1.4 sendto() 1.1.5 inet_ntoa() 1.1.6 inet_addr() 1.2 Udp 服務端的封裝 -- UdpServer.hpp 1.3 服務端代碼 -- UdpServer.cc 1.4 客戶端代碼 -- UdpClient.…

Linux 內核等待機制詳解:prepare_to_wait_exclusive 與 TASK_INTERRUPTIBLE

1. prepare_to_wait_exclusive 函數解析 1.1 核心作用 prepare_to_wait_exclusive 是 Linux 內核中用于將進程以獨占方式加入等待隊列的關鍵函數,其主要功能包括: 標記獨占等待:通過設置 WQ_FLAG_EXCLUSIVE 標志,表明此等待條目是獨占的。 安全入隊:在自旋鎖保護下,將條…

【Android構建系統】了解Soong構建系統

背景介紹 在Android7.0之前&#xff0c;Android使用GNU Make描述和執行build規則。Android7.0引入了Soong構建系統&#xff0c;彌補Make構建系統在Android層面變慢、容易出錯、無法擴展且難以測試等缺點。 Soong利用Kati GNU Make克隆工具和Ninja構建系統組件來加速Android的…

信息學奧賽一本通 1539:簡單題 | 洛谷 P5057 [CQOI2006] 簡單題

【題目鏈接】 ybt 1539&#xff1a;簡單題 洛谷 P5057 [CQOI2006] 簡單題 【題目考點】 1. 樹狀數組 模板題及講解&#xff1a;洛谷 P3374 【模板】樹狀數組 【解題思路】 解法1&#xff1a;樹狀數組 該有01構成數組初值都為0。 某位置的元素被修改奇數次后值為1&#x…

倉頡開發語言入門教程:搭建開發環境

倉頡開發語言作為華為為鴻蒙系統自研的開發語言&#xff0c;雖然才發布不久&#xff0c;但是它承擔著極其重要的歷史使命。作為鴻蒙開發者&#xff0c;掌握倉頡開發語言將成為不可或缺的技能&#xff0c;今天我們從零開始&#xff0c;為大家分享倉頡語言的開發教程&#xff0c;…

玉米籽粒發育

成熟玉米籽粒的結構 玉米籽粒的組成 成熟的玉米籽粒主要由以下三部分組成&#xff1a; 母體組織&#xff1a;包括種皮、胎座和花梗。種皮由珠被發育而來&#xff0c;起到保護種子的作用&#xff0c;并在種子的休眠和萌發中發揮重要作用。胚&#xff1a;包含根分生組織、莖分…

sherpa-ncnn:音頻處理跟不上采集速度 -- 語音轉文本大模型

目錄 1. 問題報錯2. 解決方法 1. 問題報錯 報錯&#xff1a; An overrun occurred, which means the RTF of the current model on your board is larger than 1. You can use ./bin/sherpa-ncnn to verify that. Please select a smaller model whose RTF is less than 1 fo…

Postman一直打不開的解決辦法

Postman 是一款非常流行的開源 API 開發工具&#xff0c;主要用于構建、測試、調試和文檔化應用程序接口&#xff08;API&#xff09;。但有時它的性能不會特別穩定&#xff0c;功能限制和擴展性不足&#xff1b;應用于開發、測試、運維等環節&#xff0c;尤其在開發 RESTful A…

問題|對只允許輸入的變量是否進行了更改

“對只允許輸入的變量是否進行了更改”這一問題的核心是&#xff1a;在編程中&#xff0c;某些變量被設計為僅用于輸入&#xff08;只讀&#xff09;&#xff0c;但在代碼中可能被意外修改&#xff0c;導致潛在錯誤。以下是詳細解釋&#xff1a; 1. 什么是“只允許輸入的變量”…

RPC與SOAP的區別

一.RPC&#xff08;遠程過程調用&#xff09;和SOAP&#xff08;簡單對象訪問協議&#xff09;均用于實現分布式系統中的遠程通信&#xff0c;但兩者在設計理念、協議實現及應用場景上存在顯著差異。 二.對比 1.設計理念 2.協議規范 3.技術特性 4.典型應用場景 5.總結 三.總結…

c#的內存指針操作(僅用于記錄)

c#也可以直接操作內存指針&#xff0c;如下為示例&#xff1a; unsafe {byte[] a {1,2,3};fixed (byte* p1 a, p2 &a[^1]){Debugger.Log(1, "test", $"max index:{p2-p1}");Debugger.Log(1, "test", $"address:{(long)p1:X}")…

Jsp技術入門指南【十三】基于 JSTL SQL 標簽庫實現 MySQL 數據庫連接與數據分頁展示

Jsp技術入門指南【十三】基于 JSTL SQL 標簽庫實現 MySQL 數據庫連接與數據分頁展示 前言一、回顧SQL標簽的內容1. 什么是JSTL SQL標簽&#xff1f;2.為什么要用SQL標簽&#xff1f;3.第一步&#xff1a;引入SQL標簽庫4. SQL標簽的核心功能&#xff1a;連接數據庫標簽常用屬性&…

羽毛球訂場小程序源碼介紹

基于ThinkPHP、FastAdmin以及UniApp開發的羽毛球訂場小程序源碼&#xff0c;這款小程序旨在為羽毛球愛好者提供便捷的場地預訂服務。 該小程序前端采用UniApp框架開發&#xff0c;具有良好的跨平臺兼容性&#xff0c;可以一鍵發布至iOS和Android平臺&#xff0c;極大地提高了開…

Unreal Engine: Windows 下打包 AirSim項目 為 Linux 平臺項目

環境&#xff1a; Windows: win10, UE4.27, Visual Studio 2022 Community.Linux: 22.04 windows環境安裝教程&#xff1a; 鏈接遇到的問題&#xff08;問題&#xff1a;解決方案&#xff09; 點擊Linux打包按鈕&#xff0c;跳轉至網頁而不是執行打包流程&#xff1a;用VS打開項…

SpringBoot 3.x 集成 MyBatisPlus

文章目錄 集成 MyBatisPlus第 1 步:創建 SpringBoot 項目第 2 步:添加 MyBatisPlus 依賴第 3 步:編寫 CRUD 代碼創建 Entity創建 Mapper創建 Service編寫 Controller第 4 步:執行初始化 SQL第 5 步:配置第 6 步:測試測試 ControllerMapper 層單元測試參考?? 目標 1:基…

java基礎-抽象類和抽象方法

1.abstract 可以修飾&#xff1a;類、方法 &#xff08;1&#xff09;修飾類&#xff1a; 類不能被實例化&#xff1b; 抽象類一定有構造器&#xff0c;便于子類實例化時調用&#xff1b; &#xff08;2&#xff09;修飾方法&#xff1a;抽象方法 只有方法的聲明&#xff…

解決電腦問題(8)——網絡問題

電腦網絡出現問題的原因較為復雜&#xff0c;以下是從網絡連接、網絡配置以及網絡環境等方面的常見問題及解決方法&#xff1a; 網絡連接問題 檢查物理連接&#xff1a;對于有線網絡&#xff0c;檢查網線是否插好&#xff0c;網線有無破損、斷裂等情況。對于無線網絡&#xff…

ubuntu 20.04 ping baidu.coom可以通,ping www.baidu.com不通 【DNS出現問題】解決方案

ping baidu.coom可以通&#xff0c;ping www.baidu.com不通【DNS出現問題】解決方案 檢查IPV6是否有問題 # 1. 檢查 IPv6 地址&#xff0c;記住網絡接口的名稱 ip -6 addr show# 2. 測試本地 IPv6&#xff0c;eth0換成自己的網絡接口名稱 ping6 ff02::1%eth0# 3. 檢查路由 ip…