React全家桶开发「新闻后台管理项目」实战(前端项目+源码)

您所在的位置:网站首页 千锋教育开源 React全家桶开发「新闻后台管理项目」实战(前端项目+源码)

React全家桶开发「新闻后台管理项目」实战(前端项目+源码)

2023-08-21 22:25| 来源: 网络整理| 查看: 265

使用React全家桶实现一个新闻发布管理系统

本项目1.0完成于2022年3月8日,请注意时效~ // 暂不可用!项目部署预览地址:点击查看 // 暂不可用!项目Github地址(包含所有源码、数据):点击查看如有疑问请私信博客,期待你的star! 项目Gitee地址(包含所有源码、数据):点击查看如有疑问请私信博客,期待你的star! React教程参考:千锋2022版React全家桶教程

一、项目目标 1.1 个人期望

经过了下列文档&刷题,期望实战中提高。

MDN:web/html/css/js文档 React官方文档 FCC:响应式网页设计(html5等)/js算法与数据结构(ES6、面向对象编程、函数式编程、算法等)/前端开发库(react/redux/sass等)刷题 技术目标:使用React全家桶 React组件开发 React Hooks React Router Recat Redux Antd组件库 1.2 产品选择

可借鉴的有:网易云音乐PC项目、后台管理系统项目。结合使用帆软数据可视化产品的经验,选择做后台管理项目。业务交互选择新闻后台管理。

1.3 项目描述

实现一个新闻发布管理平台,业务目标:

用户登录 游客访问:浏览新闻 用户管理:新增用户、修改用户、删除用户、禁用用户 权限管理:角色管理、页面访问权限控制、侧边栏权限控制 新闻业务:撰写新闻、草稿箱、新闻审核、新闻发布及下线等 1.4 适合人群 对前端有兴趣 对HTML/CSS/JS/REACT有一定了解 希望有一定的的那个项目经验 1.5 推荐用时

60h~100h

二、技术选型 create-react-app:脚手架 React Hooks:函数式编程,用过的都说真的爽 React Router V6:路由控制访问,V6升级了许多东西 Recat Redux:状态管理,组件通信 Antd组件库:你为什么要使用react? axios:实现网络请求 JSON Server:生成数据接口 react-tsparticles:登陆页面粒子美化 draft-js:富文本编辑 draftjs-to-html:富文本转换html html-to-draftjs:html转换富文本 Echarts:数据可视化(柱状图、饼图) Sass: CSS辅助工具,实现变量、嵌套、导入 http-proxy-middleware:开发环境反向代理跨域(前期使用练手,JSON Server不需要~);引入后需要重启服务器 CSS Modules: CSS模块化,选取class.moduleTest或id选择器,将CSS module文件引入style变量,设置className={style.moduleTest} 三、项目模块文档 3.1 登录

  实现用户登录功能:用户进入登陆页面,输入必填项账号及密码,点击登录校验账号密码,登录成功后保存状态,跳转至home页面;若登陆失败弹出“用户名或密码不匹配”。   实现效果: (首页使用了粒子效果太大了,展示不出来)

3.2 首页

  首页展示四个模块:用户最常浏览、用户点赞最多、用户信息、新闻分类。用户最常浏览模块展示浏览量最多的6个新闻标题;用户点赞最多模块展示点赞量最多的6个新闻标题;用户信息展示用户头像&名称&角色&地区,并设有按钮弹出展示该用户已发布新闻分类的饼图;新闻分类使用柱状图展示所有用户的新闻分类数量。其中,新闻标题可点击预览新闻内容。   实现效果: image

3.3 用户管理

  用户管理页面展示用户信息列表及用户操作:包括新增用户、区域筛选、用户状态开关、删除用户、编辑用户等。用户信息列表展示区域、角色、名称、状态、操作(删除、编辑)。超级管理员可以添加、删除、编辑所有用户;区域管理员尽可以新增、删除、编辑本用户及本区域下的区域编辑用户;区域编辑没有本页面权限。 image

3.4 权限管理

  权限管理包含两个页面:角色列表;权限列表。   角色列表展示角色ID、角色名称、角色操作(删除角色、编辑角色权限)。   权限列表展示页面ID、权限名称、权限路径、操作(删除路径、路径配置状态)。   实现效果: image

3.5 新闻管理

  新闻管理包含三个页面:撰写新闻、草稿箱、新闻分类。   撰写新闻包括:新闻标题、新闻分类(下拉选择)、新闻内容、新闻提交。其中新闻提交要包括保存草稿箱及提交审核两个操作。   点击保存草稿箱,跳转至草稿箱页面,并在右下侧通知用户相关消息,草稿箱页面显示新闻ID、新闻标题(新闻标题可点击预览新闻内容)、作者、分类、操作(删除、修改、提交审核)。   点击提交审核,将跳转至审核管理-审核列表,并在右下侧通知用户相关消息。   新闻分类页面展示分类ID、分类名称(可修改)、操作(删除)。   实现效果: image

3.6 审核管理

  审核管理包括两个页面:审核新闻、审核列表。   审核新闻页面展示待审核的新闻项,内容有:新闻标题、作者、分类、操作(通过、驳回)。点击通过或驳回在右下侧通知用户相关消息。   审核列表展示本用户在审核阶段的新闻,内容有:新闻标题、作者、分类、审核状态、操作。若审核状态为未通过、操作为更新;若审核状态为已通过、操作为发布。点击更新可编辑新闻内容(类似撰写新闻的页面);点击发布则跳转至已发布页面,并在右下侧通知用户相关消息。   实现效果: image

3.7 发布管理

  发布管理包括三个页面:待发布、已发布、已下线。   待发布页面展示本用户审核通过仍未发布的新闻,内容有新闻标题、作者、分类、操作(发布)。   已发布页面展示本用户已发布的新闻,内容有新闻标题、作者、分类、操作(下线)。   已下线页面展示本用户已下线的新闻,内容有新闻标题、作者、分类、操作(删除)。   实现效果: image

四、项目规范 文件夹、文件名称统一小写 JS组件采用大驼峰命名,比那辆采用小驼峰命名 使用hooks编写 丰富注释 rudux:每个模块有自己独立的reducer,通过combineReducer进行合并 五、技术文档 接口:使用JSON Server部署本地数据接口(http://localhost:5000) 功能 接口地址 调用案例 用户 /users 获取用户及其角色权限/users?_expand=role 角色权限 /roles 子菜单 /children 父菜单 /rights 取父子菜单/rights?_embed=children 新闻分类 /categories 区域 /regions 新闻 /news 获取对应新闻内容、分类及作者权限/news/${id}?_expand=category&_expand=role// 审核状态、发布状态映射(数组id即为状态码)const auditList = ["未审核", '审核中', '已通过', '未通过']const publishList = ["未发布", '待发布', '已上线', '已下线'] 路由架构 image // V6实例 import React from 'react' import { HashRouter as Router, Routes, Route, Navigate } from "react-router-dom" import Login from '../views/login/Login' import NewsSandBox from '../views/sandbox/NewsSandBox' import News from '../views/news/News' import Detail from '../views/news/Detail' export default function IndexRouter() { return ( {/* {localStorage.getItem("token")?:} */} ) } 简单数据处理:使用lodash进行简单数据处理 renderBarView(_.groupBy(res.data, item => item.category.title)) 顶栏控制侧边栏伸缩 // 顶栏组件 const mapStateToProps = ({CollapsedReducer: {isCollapsed}})=>{ return { isCollapsed } } const mapDispatchToProps = { changeCollapsed(){ return { type:"change_collapsed" } } } export default connect(mapStateToProps,mapDispatchToProps)(TopHeader) // 侧边栏组件 // 侧边栏伸缩,使用connect const mapStateToProps = ({CollapsedReducer: {isCollapsed}})=>({isCollapsed}) export default connect(mapStateToProps)(SideMenu)

实现效果: image

数据加载Loading,状态持久化 // Redux store设置,使用黑名单避免&实现持久化 import {createStore,combineReducers} from 'redux' import {CollapsedReducer} from './reducers/CollapsedReducer' import {LoadingReducer} from './reducers/LoadingReducer' import { persistStore, persistReducer } from 'redux-persist' import storage from 'redux-persist/lib/storage' // defaults to localStorage for web const persistConfig = { key: 'hangyi', storage, blacklist: ['LoadingReducer'] } const reducer = combineReducers({ CollapsedReducer, LoadingReducer }) const persistedReducer = persistReducer(persistConfig, reducer) const store = createStore(persistedReducer); const persistor = persistStore(store) export { store, persistor } // Reducer设置 export const CollapsedReducer = (prevState={ isCollapsed:false },action)=>{ let {type} =action switch(type){ case "change_collapsed": let newstate = {...prevState} newstate.isCollapsed = !newstate.isCollapsed return newstate default: return prevState } } JSON Server方法 //取数据 get // axios.get("http://localhost:8000/posts/2").then(res=>{ // console.log(res.data) // }) // 增 post // axios.post("http://localhost:8000/posts",{ // title:"33333", // author:"xiaoming" // }) // 更新 put // axios.put("http://localhost:8000/posts/1",{ // title:"1111-修改" // }) // 更新 patch // axios.patch("http://localhost:8000/posts/1",{ // title:"1111-修改-11111" // }) // 删除 delete // axios.delete("http://localhost:8000/posts/1") // _embed // axios.get("http://localhost:8000/posts?_embed=comments").then(res=>{ // console.log(res.data) // }) // _expand // axios.get("http://localhost:8000/comments?_expand=post").then(res=>{ // console.log(res.data) // }) 权限:页面本身权限配置+用户角色权限配置 image // 解构当前用户的页面权限 const {role: {rights}} = JSON.parse(localStorage.getItem("token")); // 检查登录用户页面权限方法 const checkPagePermission = (item) => { return item.pagepermisson && rights.includes(item.key) }; // 导航方法 const navigate = useNavigate(); // 截取当前URL路径 const location = useLocation(); const selectedkeys = location.pathname; const openkeys = ["/" + location.pathname.split("/")[1]]; // 侧边栏内容列表 const renderMenu = (menuList) => { return menuList.map(item => { // 检查每一项是否有下级列表(使用可选链语法)&& 页面权限 if (item.children?.length > 0 && checkPagePermission(item)) { return {renderMenu(item.children)} } return checkPagePermission(item) && navigate(item.key) }>{item.title} }) } 多级用户管理:在添加用户、编辑用户时,超级管理员可以随意添加、区域管理员可以添加编辑本人及本区域下的区域编辑。 // 代码节选 const {roleId,region,username} = JSON.parse(localStorage.getItem("token")); // 初始化用户权限列表 useEffect(() => { const roleObj = { "1":"superadmin", "2":"admin", "3":"editor" } axios.get("/users?_expand=role").then(res => { const list = res.data; setdataSource(roleObj[roleId]==="superadmin"?list:[ // 超级管理员不限制,区域管理员:自己+自己区域编辑,区域编辑:看不到用户列表 ...list.filter(item=>item.username===username), ...list.filter(item=>item.region===region&& roleObj[item.roleId]==="editor") ]) }) }, [roleId,region,username]); // 控制区域、角色的新增&编辑权限 // 父组件传递 regionList={regionList} roleList={roleList}等参数 // 子组件props.regionList {props.regionList.map(item => { return {item.title} })} 可编辑单元格 // 参考https://ant.design/components/table-cn/#components-table-demo-edit-cell // 使用Context来实现跨层级的组件数据传递 const EditableContext = React.createContext(null); const EditableRow = ({ index, ...props }) => { const [form] = Form.useForm(); return ( ); }; const EditableCell = ({ title, editable, children, dataIndex, record, handleSave, ...restProps }) => { const [editing, setEditing] = useState(false); const inputRef = useRef(null); const form = useContext(EditableContext); useEffect(() => { if (editing) { inputRef.current.focus(); } }, [editing]); const toggleEdit = () => { setEditing(!editing); form.setFieldsValue({ [dataIndex]: record[dataIndex], }); }; const save = async () => { try { const values = await form.validateFields(); toggleEdit(); handleSave({ ...record, ...values }); } catch (errInfo) { console.log('Save failed:', errInfo); } }; let childNode = children; if (editable) { childNode = editing ? ( ) : ( {children} ); } return {childNode}; }; ··· item.id} components={{ body: { row: EditableRow, cell: EditableCell, } }} /> 六、疑难巧点 6.1 react routerV6新版本 Routes代替Switch element代替component: // V6 element={} // history component={Login} Navigate干掉了Redirect: // V6 // history localStorage.getItem("token")?:}/> useNavigate, useLocation等代替withRouter,props.history.push等的方法 // V6 const location = useLocation(); const selectedkeys = location.pathname; const openkeys = ["/" + location.pathname.split("/")[1]]; // history const selectKeys = [props.location.pathname] const openKeys = ["/"+props.location.pathname.split("/")[1]] // 导航方法 // V6 const navigate = useNavigate(); navigate(item.key) // history props.history.push(item.key) 6.2 axios拦截

可以实现:

简化每次axios请求的代码量 实现加载数据时有提示 import axios from 'axios' import {store} from '../redux/store' axios.defaults.baseURL="http://localhost:5000" // axios.defaults.headers // axios.interceptors.request.use // axios.interceptors.response.use axios.interceptors.request.use(function (config) { // Do something before request is sent // 显示loading store.dispatch({ type:"change_loading", payload:true }) return config; }, function (error) { // Do something with request error return Promise.reject(error); }); // Add a response interceptor axios.interceptors.response.use(function (response) { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data store.dispatch({ type:"change_loading", payload:false }) //隐藏loading return response; }, function (error) { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error store.dispatch({ type:"change_loading", payload:false }) //隐藏loading return Promise.reject(error); }); 七、待补充 性能优化:useMemo、useCallback和memo等 redux hooks 八、附录 8.1 命令表 // 创建react-app npx create-react-app my-app // 进入该目录 cd my-app // 启动工程 npm start // npm安装相关依赖(例如antd) npm i --save antd // JSON Server启动(使用db.json文件,本地5000端口) json-server --watch db.json --port 5000

本文来自博客园,作者:沧浪浊兮,转载请注明原文链接:https://www.cnblogs.com/shixiu/p/15983351.html



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3