前端React实战项目 全球新闻发布系统

1.项目概述

本项目技术栈全面且新颖,非常适合大家学习参考。如有问题请留言,谢谢。

部分组件未展示,文档只展示关键页面的组件 如需查看请前往项目仓库项目地址

1.1 项目名称

该项目名为全球新闻发布系统

1.2 背景

该系统旨在解决传统新闻管理方式存在的流程繁琐、权限混乱、发布效率低等问题。本新闻管理系统旨在通过现代化的前端技术栈,构建一个功能完善、操作便捷、权限清晰的新闻内容管理平台,实现新闻从创建、编辑、审核到发布的全流程管理,满足不同角色用户的使用需求。

该系统解决了新闻管理过程中的核心痛点:

  • 缺乏统一的内容管理平台,信息分散
  • 审核流程不规范,责任不明确
  • 权限控制不精细,存在安全隐患
1.3 功能定位 / 目标用户

该项目作为新闻管理系统,其目标用户是面相非游客使用人员如管理员,审核员,编辑等角色

1.4 项目亮点 / 核心优势
  • 1.现代化技术栈组合
  • 2.完整的权限控制
  • 3.模块化、分层结构清晰
  • 4.功能覆盖较全面
  • 5.响应式布局

2.技术栈

2.1 前端技术栈:

技术栈使用:

  • JavaScript
  • React18
  • React-rotuer v7现代路由管理
  • Redux状态管理
  • vite脚手架工具
  • SASScss预处理器
  • ReduxToolkitredux管理工具

页面组件:Ant Design

2.2 富文本方案与内容处理

React Draft Wysiwyg 基于Draft.js的可视化编辑器组件

Draft.js 富文本编辑器

2.3 工具库

Axios Dayjs lodashjs

2.4 模拟后端 API:

Jsonserver v0.17.4

2.5 代码质量 与风格

ESLint插件规范代码质量与格式

基础规则来源

  • 继承了 ESLint 官方推荐的基础规则(js.configs.recommended
  • 集成了 React Hooks 最新推荐规则(reactHooks.configs['recommended-latest']
  • 包含 React Refresh 针对 Vite 的配置(reactRefresh.configs.vite

文件范围

适用于所有 .js.jsx 文件
语言特性

  • 支持 ES2020 及最新 ECMAScript 特性
  • 启用 JSX 语法支持
  • 使用 ES 模块(sourceType: 'module')
  • 识别浏览器环境全局变量

语言风格:整体风格偏向现代 React 开发规范,强调代码质量和 Hooks 正确使用,同时兼容 Vite 开发环境的特性。

3.项目结构与模块说明

3.1 目录结构
3.2 各层职责说明
  • 1.components 负责各个页面中可复用的组件 如表单 富文本编辑器 页头等等
  • 2.router 负责管理整个系统的路由部分
  • 3.Store 负责管理Redux状态管理的实例与Store
  • 4.unti 负责存放一些可以复用的函数 和axios请求拦截器
  • 5.view负责的是整个系统的页面部分 也是整个开发部分中最重要的部分
  • 6.其他部分 包括了vite配置 jsonserver的模拟数据等等
3.3 模块划分与功能简介
新闻管理模块:

组件:包括了新闻列表组件 新闻审核管理组件 新闻预览组件 新闻分类组件

介绍新闻管理模块的核心地位,阐述其涵盖新闻录入、编辑、发布等操作,及对新闻标题、内容、分类等信息管理功能,强调其对新闻平台有序运作的关键作用。

用户权限模块:

组件:权限列表组件 角色列表组件 用户列表组件

说明用户权限模块的重要性,讲解其对用户访问资源和执行操作能力的控制配置,如不同用户角色权限设置,以及通过RBAC模型等方式实现权限管理,保障系统数据安全。

(RBAC 基于角色的访问控制管理 比如先定义权限 设置为对应角色身份分配权限 并分配给部分用户角色身份 )

审核发布模块:

组件:审核列表组件(展示从草稿箱提交给审核模块的新闻的审核状态) 审核新闻组件 已发布列表组件 未发布列表组件 已下线新闻组件

解释审核发布模块在内容输出环节的关键作用,讲述稿件审核流程,包括合规性、错别字等检查,以及发布功能,如选择发布渠道、时间,确保发布内容质量和及时性。

系统框架模块:

组件 首页展示组件 登录页面 layout布局组件

阐述系统框架模块对整个系统的支撑意义,介绍其从业务设计思路、拓扑结构到功能模块组成,像项目管理系统从项目管理周期等维度设计架构,为各功能模块提供运行基础。

3.4 路由设计 / 路由守卫机制
1.路由设计
  1. 路由库与模式
    • 使用 react-router-dom v7 作为路由管理库,采用 createHashRouter 创建路由实例,采用哈希模式(URL 带 #)管理路由。
    • 路由配置集中在 src/router/index.jsx,通过嵌套结构定义路由层级。
  2. 路由结构
    • 公共路由 :登录页(/login)、新闻浏览页(/news)、新闻详情页(/detail/:id),无需权限即可访问。
    • 私有路由 :系统主功能区(/ 路径下),包含首页、用户管理、新闻管理等模块,需登录后访问。
    • 嵌套路由 :主功能区通过 children 配置子路由,如 /user-manage/list/news-manage/draft 等,对应不同功能页面。
  3. 路由组件挂载
    • 通过 RouterProviderApp.jsx 中挂载路由实例,实现路由的全局管理
2.路由守卫设计

1.登录状态守卫(PrivateRouter.jsx

  • 作用:验证用户是否登录,未登录则重定向到登录页。

  • 实现逻辑

    • localStorage 读取 token(存储用户信息)。

    • token 不存在,通过 navigate 重定向到 /login

    • token 存在,渲染被包裹的子组件(如系统主页面 NewSandBox)。

      复制代码
      // 核心代码
      const PrivateRouter = ({ children }) => {
        const navigate = useNavigate();
        useEffect(() => {
          if (!localStorage.getItem('token')) {
            navigate('/login', { replace: true });
          }
        }, [navigate]);
        return localStorage.getItem('token') ? children : null;
      };

    2.权限细粒度校验(OutletDefend.jsx

    • 作用:验证登录用户是否有权限访问当前路由,无权限则重定向到 404 页面。

    • 实现逻辑

      • 获取权限数据 :通过 API 加载所有系统权限(/rights/children)和当前用户的权限(从 token 中解析角色 ID,再请求 /roles/:id)。

      • 路径匹配 :处理带参数的路由(如 /news-manage/preview/:id),通过替换参数为 :id 实现权限匹配。

      • 权限校验

        1. 检查用户权限列表是否包含当前路径。
        2. 检查该权限的 pagepermissonrouterpermisson 是否为 1(启用状态)。
      • 无权限处理 :重定向到 /404 页面;有权限则渲染子路由(Outlet

        复制代码
        // 核心校验逻辑
        const hasPermission = () => {
          const currentPath = location.pathname;
          const checkPath = currentPath.replace(/\/\d+$/, "/:id"); // 处理参数路由
          const hasUserRight = userRights.includes(checkPath) || userRights.includes(currentPath);
          if (!hasUserRight) return false;
          
          const rightInfo = allRights.find(item => item.key === checkPath || item.key === currentPath);
          return rightInfo ? (rightInfo.pagepermisson || rightInfo.routerpermisson) === 1 : true;
        };

4.主要功能详解

4.1用户管理模块:

1.用户列表组件
关键页面展示:

主页面:

添加用户表单:

功能说明:

用户列表组件是系统用户管理的核心操作入口,支持管理员对用户信息进行全生命周期管理:

  • 超级管理员可查看和管理系统内所有用户,区域管理员仅能管理所属区域内的用户及区域编辑角色用户
  • 提供用户信息的新增、编辑、删除功能,支持通过区域和角色筛选快速定位目标用户
  • 可通过用户状态开关(Switch 组件)控制用户的登录权限,默认用户(系统初始管理员)的状态不可修改
  • 操作按钮权限差异化:默认用户的编辑和删除按钮处于禁用状态,确保系统基础权限稳定性
技术实现要点:

这里我们将逐一对该模块的状态管理,接口调用,数据结构,以及数据处理做出解释。

  1. 数据管理

    • 用户数据: 包含 id username password roleState(用户状态) ``default(是否是默认用户) region roleId(用户联查角色数据)
    • 关联数据: 通过_expand=role联查角色信息,用来获取角色名称等关联数据
    • 下拉列表筛选数据: 首先是区域数据 optionArea 与角色名称数据 optionRole这两个数据用与antd中的Select组件用来筛选用户列表
  2. 状态管理

    • 使用useState管理用户列表数据(dataSource)、表单弹窗状态(open/updataOpen)、表单引用(FormRef/updataFormRef),表单数据(optionRole/optionArea/),管理region是否可见(isUpdateDisable)
    • 通过使用useRef获取表单实例 实现表单验证和数据去重
    • 利用isUpdateDisable状态控制编辑表单中区域选择框的禁用状态
  3. 接口调用与数据管理

    采用 Axios 进行 RESTful API 调用,实现用户数据的增删改查:

    • 获取用户列表:GET /users?_expand=role
    • 添加用户:POST /users,并在成功后本地更新列表数据
    • 编辑用户:PATCH /users/:id,同步更新本地缓存的角色信息
    • 删除用户:DELETE /users/:id,同时过滤本地列表数据
    • 权限过滤逻辑:根据登录用户角色 ID(role.id)过滤数据,超级管理员(id=1)获取全部数据,区域管理员仅获取所属区域数据
  4. 数据处理与交互函数:

1.数据加载函数

(1)用户列表数据加载

复制代码
const getRightList = async () => {
  const res = await axios.get("http://localhost:3000/users?_expand=role");
  if (id === 1) { // 超级管理员
    setDataSource(res.data);
  } else { // 区域管理员/编辑
    setDataSource([
      ...res.data.filter(item => item.username === username), // 自身数据
      ...res.data.filter(item => item.region === region && item.roleId === 3) // 管辖区域的编辑
    ]);
  }
};
  • 作用:根据登录用户权限加载对应范围的用户数据(超级管理员可见全部,区域管理员仅见自身及管辖区域编辑)
  • 数据关联 :通过_expand=role联查角色信息,避免前端二次请求

(2)表单选项数据加载

复制代码
const getFormData = async () => {
  // 加载区域选项
  const formRegionRes = await axios.get("http://localhost:3000/regions");
  setOptionArea(formRegionRes.data);
  
  // 加载角色选项并格式化
  const formRoleRes = await axios.get("http://localhost:3000/roles");
  formRoleRes.data.forEach(element => {
    element.label = element.roleName; // 显示文本
    element.value = element.id; // 实际值
  });
  setOptionRole(formRoleRes.data);
};
  • 作用 :加载区域和角色下拉框的选项数据,并格式化为 Ant Design Select 组件所需的label-value结构
  • 解释 :这里必须使用lable-value结构 如果继续使用roleNameid的结构 就会导致optionRole数据中的roleNameid是两个数组类型的数据,进而引发再下拉菜单中无法渲染出角色名称的问题
  • 时机:组件挂载时执行,确保表单渲染时选项已就绪
  1. 数据操作函数

(1)新增用户

复制代码
const handleOK = () => {
  FormRef.current.validateFields().then(res => {
    setOpen(false);
    FormRef.current.resetFields();
    const postData = { ...res, roleState: true, default: false };
    
    axios.post("http://localhost:3000/users", postData).then(response => {
      setDataSource([
        ...dataSource,
        { ...response.data, 
          role: optionRole.filter(item => item.id === res.roleId)[0] 
        }
      ]);
    });
  });
};
  • 流程:表单验证 → 构造提交数据 → 发送 POST 请求 → 本地数据即时更新(避免二次请求)
  • 挂载位置:这个函数主要用在新增用户的模态框表单中 点击模态框的确定按钮就会触发
  • 数据处理:手动关联角色信息(因后端返回数据不含完整角色对象)

(2)更新用户

复制代码
const handleUpdataOK = () => {
  updataFormRef.current.validateFields().then(res => {
    setupdataOpen(false);
    // 本地更新
    setDataSource(dataSource.map(item => 
      item.id === currentUpdate.id 
        ? { ...item, ...res, role: optionRole.filter(data => data.id === res.roleId)[0] }
        : item
    ));
    // 后端同步
    axios.patch(`http://localhost:3000/users/${currentUpdate.id}`, res);
  });
};
  • 特点 :采用乐观更新策略,先更新本地数据提升体验,再同步至后端(乐观更新策略在发送请求到后端之前,先假设请求会成功,并提前更新本地界面状态,待后端确认成功后再最终确认状态
  • 挂载位置:挂载在更新用户信息的模态框表单中
  • 关联处理:通过角色 ID 过滤本地角色列表,补充完整角色信息

(3)删除用户

复制代码
const confirmDelete = (item) => {
  // 本地删除
  setDataSource(dataSource.filter(data => data.id !== item.id));
  // 后端删除
  axios.delete(`http://localhost:3000/users/${item.id}`);
};
  • 逻辑:确认后立即从本地数据源移除该用户,同时发送删除请求
  • 权限控制 :默认用户(item.default === true)的删除按钮禁用
  1. 辅助处理函数

(1)更新表单初始化

复制代码
  const handleUpdate = (item) => {
    setupdataOpen(true);
    if (item.roleId === 1) {
      //禁用地区 角色是超级管理
      setIsUpdateDisable(true);
    } else {
      //取消禁用
      setIsUpdateDisable(false);
    }
    // console.log(isUpdateDisable);

    setTimeout(() => {
        //setFieldsValue(item)将初表单的初始值填充成当前行表示的数据
      updataFormRef.current.setFieldsValue(item); // 增加一定的延迟确保 Modal 已渲染
    }, 100);

    setCurrentUpdate(item);
  };
  • 作用:打开编辑弹窗时初始化表单数据,并根据角色 ID 控制区域选择框权限
  • 设置延迟 :设置延迟是为了保证Modal模态框已经完成渲染,保证表单的DOM能够正常被获取到

(2)表格筛选逻辑

复制代码
// 区域筛选
onFilter: (value, record) => {
  if (value === "全球") return record.region === "";
  return value === record.region;
}

// 角色筛选
onFilter: (value, record) => {
  return value === record.role.roleName;
}
  • 特点:针对 "全球区域" 做特殊处理(后端存储为空字符串),通过角色名称匹配实现筛选
2.用户信息填写(更新)表单:

1.组件作用

用户信息表单组件,用于用户的新增和编辑场景,提供用户名、密码、区域和角色的输入功能。通过权限控制动态限制可选选项,并支持跨组件表单操作(校验、重置、赋值)。 该组件就是上面在用户列表组件中展示的模态框表单。

2.组件通信与组件方法

这些属性都是父组件向子组件传递的必要属性

属性名 类型 说明 示例
optionRole array 角色下拉框原始数据(需包含labelvalueroleName [{label: '超级管理员', value: 1, roleName: '超级管理员'}, ...]
optionArea array 区域下拉框原始数据(需包含valuetitle [{value: '亚洲', title: '亚洲'}, ...]
isUpdateDisable boolean 编辑场景下控制区域选择框是否禁用(优先级低于角色选择联动) true(编辑超级管理员时)

表中的方法都是通过ref向外暴漏的

方法名 说明 参数示例
validateFields 校验表单所有字段,返回校验结果 -
resetFields 重置表单所有字段值为初始状态 -
setFieldsValue 为表单设置初始值(用于编辑场景回显) {username: 'admin', region: '亚洲'}

3.核心功能实现

  • (1)权限驱动的选项过滤:

    基于当前登录用户角色(role.id)动态过滤可选选项,防止越权操作,用来获取表单所需的角色列表与地域列表:

    复制代码
    // 区域选项过滤逻辑
    const getFilteredRegionOptions = () => {
      if (id === 1) return optionArea; // 超级管理员:所有区域
      return optionArea.filter(item => item.value === region); // 区域管理员/编辑:仅自身区域
    };
    
    // 角色选项过滤逻辑
    const getFilteredRoleOptions = () => {
      if (id === 1) return optionRole; // 超级管理员:所有角色
      if (id === 2) return optionRole.filter(item => item.roleName === "区域编辑"); // 区域管理员:仅区域编辑
      return optionRole.filter(item => item.value === id); // 编辑:仅自身角色
    };
  • (2)角色与区域联动控制

    选择超级管理员角色时自动禁用区域选择并清空区域值,保证数据一致性:

    复制代码
    <Select
      onChange={(value) => {
        if (value === 1) { // 选择超级管理员
          setIsDisable(true);
          ref.current.setFieldsValue({ region: "" }); // 清空区域
        } else {
          setIsDisable(false);
        }
      }}
    />
  • (3)跨组件通信设计

    通过 forwardRefuseImperativeHandle 暴露表单方法,简化父组件操作:

    逐行解析:

    1. const [form] = Form.useForm();

      这是 Ant Design Form 组件提供的表单实例创建方法,通过解构赋值获取 form 实例。该实例包含了一系列操作表单的核心方法(如校验、重置、赋值等),用于管理表单的状态和行为。

    2. useImperativeHandle(ref, () => ({ ... }));

      这是 React 的 Hook,用于自定义子组件暴露给父组件的 ref 内容。它接收两个参数:

      • 第一个参数 ref:父组件传递过来的 ref 对象。
      • 第二个参数:一个函数,返回一个对象,该对象定义了父组件可通过 ref 访问的方法。
    3. 暴露的方法

      • validateFields: () => form.validateFields()

        暴露表单校验方法,父组件调用时会触发 Ant Design Form 的校验逻辑,返回校验结果(通常是包含表单数据的 Promise)。

      • resetFields: () => form.resetFields()

        暴露表单重置方法,调用后会将表单所有字段重置为初始值。

      • setFieldsValue: (values) => form.setFieldsValue(values)

        暴露表单赋值方法,父组件可通过传递键值对对象(如{ username: 'admin' }),为表单字段设置值(常用于编辑场景的数据回显)。

    核心作用:

    通过 useImperativeHandle 精确控制暴露给父组件的方法,避免父组件直接访问子组件内部的 form 实例,既满足了跨组件操作表单的需求(如父组件触发校验、重置),又保证了组件的封装性,防止父组件误操作内部状态。

    复制代码
    const [form] = Form.useForm();
    useImperativeHandle(ref, () => ({
      validateFields: () => form.validateFields(),
      resetFields: () => form.resetFields(),
      setFieldsValue: (values) => form.setFieldsValue(values),
    }));

4.2权限管理模块:

1.权限列表组件:
关键页面展示:

主页面

子权限节点树:

操作按钮

功能说明:

权限列表组件用于管理系统内所有权限项的展示、状态切换和删除操作,支持一级权限与二级子权限的树形结构展示,实现了权限启用 / 禁用的动态切换及权限项的删除功能,是系统权限管理模块的核心组件。 也是RBAC模式的重要组成部分。

技术实现要点:
  1. 数据管理

    • 采用树形层级结构存储权限数据,包含两类权限项:
      • 一级权限:包含idlabel(权限名称)、key(权限路径,如/user-manage)、grade: 1(标识一级)、children(二级权限数组,无二级权限时设为null)、pagepermisson(页面访问权限,1 为启用,0 为禁用)等字段。
      • 二级权限:包含idlabelkey(如/user-manage/add)、grade: 2(标识二级)、rightId(关联的一级权限 ID)、pagepermisson等字段。
    • 使用dataSource状态来保存从后端获取的Rights列表的数据,如上条所示。并对数据做了初始化,从接口获取数据后就对一级权限的children做特殊处理 ------ 若children.length === 0则设为null,避免表格渲染空树形结构。
  2. 状态管理

    核心状态dataSource用于存储完整的权限列表(含树形结构),初始值为空数组,通过useState定义。

    状态更新机制

    • 组件挂载时,通过useEffect调用接口初始化dataSource
    • 权限删除或状态切换时,通过setDataSource([...dataSource])触发组件重新渲染(因为侧边栏上的视图和权限列表是强制关联的 所以在我们将数据的i)。
    • 二级权限删除时,直接修改对应一级权限的children数组,再通过状态更新刷新视图。
  3. 数据调用与接口管理

    • 数据获取 :组件挂载时调用/rights?_embed=children",一次性获取所有一级权限及其关联的二级子权限(通过_embed=children实现关联查询)。
    • 权限删除
      • 一级权限:调用/rights/${item.id}",同时从dataSource中过滤掉该权限项。
      • 二级权限:调用/children/${item.id}",并从对应一级权限的children数组中移除该子项。
    • 权限状态切换
      • 一级权限:调用/rights/${item.id}", { pagepermisson: 新状态 }更新后端状态。
      • 二级权限:调用/children/${item.id}", { pagepermisson: 新状态 }同步状态变更。
  4. 数据处理与交互函数:

    • confirmDelete(删除权限)

      复制代码
        const confirmDelete = (item) => {
          // 删除前端状态
          setDataSource(dataSource.filter((data) => data.id !== item.id));
          if (item.grade === 1) {
            axios.delete(`http://localhost:3000/rights/${item.id}`).then((err) => {
              if (err) {
                console.log(err);
              }
            });
          } else {
         		 //二级子项数据删除
            let list = dataSource.filter((data) => data.id === item.rightId);
            // console.log(list);
            list[0].children = list[0].children.filter((data) => data.id !== item.id);
            setDataSource([...dataSource]);
            axios.delete(`http://localhost:3000/children/${item.id}`);
          }
        };
      • 区分权限等级(grade)执行不同删除逻辑:一级权限直接从dataSource中移除,二级权限需从父级children数组中过滤。

      • 前后端同步:删除操作先更新前端状态(优化用户体验),再调用对应接口同步至后端。

    • switchPermission(切换权限状态)

      复制代码
        const switchPermission = (item) => {
            item.pagepermisson = item.pagepermisson === 1 ? 0 : 1;
            setDataSource([...dataSource]); //用来触发重新渲染
            // 控制权限状态 分为一级权限和二级权限 控制后端的pagepermisson
            if (item.grade === 1) {
              // 使用patch请求(补丁更新) 只更新pagepermisson
              axios.patch(`http://localhost:3000/rights/${item.id}`, {
                //item.pagepermisson在前端状态的时候已经更新了
                pagepermisson: item.pagepermisson,
              });
            } else {
              axios.patch(`http://localhost:3000/children/${item.id}`, {
                pagepermisson: item.pagepermisson,
              });
            }
          };
      • 前端状态反转:通过item.pagepermisson = item.pagepermisson === 1 ? 0 : 1直接修改权限状态。

      • 强制刷新:通过setDataSource([...dataSource])触发表格重新渲染,展示最新状态。

      • 接口同步:根据权限等级调用不同patch接口,仅更新pagepermisson字段(局部更新,减少数据传输)。

4.3角色管理模块:

关键页面展示:

主页面

操作页面

功能说明:

角色列表组件是系统权限管理的核心模块之一,用于展示和管理系统内所有角色信息,支持角色的删除操作及权限分配功能。通过树形结构的权限选择器,实现角色与权限的关联配置,是 RBAC(基于角色的访问控制)模型的关键实现载体。

技术实现要点:
  1. 数据管理

    • 核心数据结构
      • 角色数据:包含id(角色 ID)、roleName(角色名称)、roleType(角色类型)、rights(权限路径数组,如['/user-manage', '/news-manage'])等字段。
      • 权限数据:采用树形结构,一级权限包含idlabelkeygrade:1children(二级权限数组);二级权限包含idlabelkeygrade:2rightId(关联一级权限 ID)等字段(与RightList组件共用权限数据结构)。
    • 数据关联 :角色的rights字段存储所选权限的key值数组,与权限数据的key字段一一对应,实现角色与权限的映射。
    • 特殊处理 :通过_embed=children参数一次性获取权限数据及其子权限,避免多次请求。
  2. 状态管理

    • 核心状态
      • dataSource:存储角色列表数据,初始为空数组,通过useState定义。
      • rightList:存储完整权限树形数据,用于权限分配弹窗的树形选择器渲染,保存的是权限数据
      • isModalOpen:控制权限分配弹窗的显示 / 隐藏状态。
      • currentRight:暂存当前角色的权限配置(用于弹窗内权限选择的实时更新)。
      • currentID:记录当前操作的角色 ID,用于权限提交时的精准更新。
    • 状态更新机制
      • 组件挂载时,通过useEffect并行请求角色列表和权限列表数据,初始化dataSourcerightList
      • 角色删除时,通过filter生成新数组更新dataSource,确保引用变化触发重渲染。
      • 权限分配时,通过map更新目标角色的rights字段,保持其他角色数据不变。
  3. 数据调用与接口管理

    • 数据获取

      • 角色列表:/roles获取所有角色信息。
      • 权限列表:/rights?_embed=children获取带二级权限的完整权限树。
    • 角色删除 :调用/roles/${item.id},同步删除后端角色数据,前端通过filter即时移除对应角色。

    • 权限分配

      • 打开弹窗时,记录当前角色的rightscurrentRight,作为树形选择器的初始选中值。

      • currentRight只保存一个数组 数组中是树形组件所有被勾选的权限

      • 提交时,调用/roles/${currentID}, 更新数据{ rights: currentRight },将更新后的权限数组同步至后端。

  4. 数据处理与交互函数

    • confirmDelete(删除角色)

      复制代码
        const confirmDelete = (item) => {
            //前端删除逻辑
            setDataSource(dataSource.filter((data) => data.id !== item.id));
            //后端删除逻辑
            axios.delete(`http://localhost:3000/roles/${item.id}`).then((err) => {
              if (err) {
                console.log(err);
              }
            });
          };
      • 前端逻辑:通过dataSource.filter过滤掉目标角色 ID,生成新数组更新状态,实现即时删除效果。

      • 后端同步:根据角色 ID 调用delete接口,确保前后端数据一致。

      • 交互反馈:通过Popconfirm组件实现删除确认,防止误操作。

    • showModal(打开权限分配弹窗)

      复制代码
        const showModal = (item) => {
          setIsModalOpen(true);
          //将当前未操作的权限保存起来
          setCurrentRight(item.rights);
          //记录当前操作的角色id
          setCurrentID(item.id);
        };
      • 状态初始化:记录当前角色的rightscurrentRight,作为树形选择器的checkedKeys初始值。

      • 上下文保存:存储当前角色 ID 到currentID,用于后续提交时定位角色。

    • handleOK(提交权限分配)

      复制代码
        const handleOK = (currentID) => {
          setIsModalOpen(false);
          //同步datasource
          setDataSource(
            dataSource.map((item) =>
              item.id === currentID ? { ...item, rights: currentRight } : item
            )
          );
          //同步后端
          axios.patch(`http://localhost:3000/roles/${currentID}`, {
            rights: currentRight,
          });
        };
      • 状态更新:通过map遍历角色列表,仅更新当前角色(id === currentID)的rights字段为currentRight,保持其他角色数据不变。

      • 接口同步:使用patch请求局部更新角色的权限配置,减少数据传输量。

    • onCheck(权限选择变化)

      复制代码
        const handleOK = (currentID) => {
          setIsModalOpen(false);
          //同步datasource
          setDataSource(
            dataSource.map((item) =>
              item.id === currentID ? { ...item, rights: currentRight } : item
            )
          );
          //同步后端
          axios.patch(`http://localhost:3000/roles/${currentID}`, {
            rights: currentRight,
          });
        };
      • 实时同步:树形选择器勾选状态变化时,将最新的选中key数组(checkedKeys.checked)更新到currentRight,确保弹窗内选择状态与状态同步。
    • 表格与树形组件配置

      • 表格通过rowKey={(item) => item.id}指定唯一标识,优化渲染性能。
      • 树形选择器Tree配置:
        • checkable开启勾选功能,checkedKeys={currentRight}实现受控组件逻辑。
        • checkStrictly={true}关闭父子关联勾选,支持独立选择任意权限。
        • fieldNames={``{ title: "label", key: "key" }}映射权限数据字段与树形组件要求的字段,确保正确渲染。

4.4新闻管理模块:

说明

新闻管理模块是系统核心功能之一,提供新闻全生命周期管理,包括新闻的创建、编辑、预览、草稿管理及分类维护。基于富文本编辑技术实现内容创作,通过分步流程引导用户完成新闻发布,支持草稿保存与审核提交,满足专业新闻发布流程需求。

1.撰写新闻组件
关键页面展示:

首页:

撰写:

提交新闻

功能说明:

撰写新闻组件是新闻管理系统的核心创作入口,提供三步式新闻发布流程,支持草稿保存与审核提交功能。通过分步引导用户完成新闻标题填写、富文本内容编辑及最终提交操作,实现规范化的内容创作流程。

技术实现要点:

1. 分步流程设计

  • 流程拆解 :将新闻创作拆分为 3 个逻辑步骤,降低用户认知负荷

    • 步骤 1(基本信息):收集新闻标题与分类信息
    • 步骤 2(新闻内容):通过富文本编辑器创作内容
    • 步骤 3(提交选项):提供保存草稿或提交审核的出口
  • 状态管理

    • current:记录当前步骤索引(0-2),控制步骤切换与内容展示
    • steps:配置步骤元数据(标题、描述、内容组件)
    • 步骤切换通过next()/prev()方法实现,包含表单验证逻辑
  • 核心代码

    const next = () => {
    if (current === 0 && (!newsTitle || !newsCategory)) {
    alert("请填写完整信息");
    return;
    }
    if (current === 1 && (news.content === "" || news.content === "

    \n")) {
    alert("请填写内容");
    return;
    }
    setCurrent(current + 1);
    };

2.表单数据处理

  • 核心状态
    • newsTitle:存储新闻标题(受控组件值)
    • newsCategory:存储所选分类 ID
    • newsContent:存储富文本转换后的 HTML 内容
    • categoryList:存储分类选项数据(格式化为 Ant Design Select 要求的{label, value}结构)
  • 数据关联
    • 通过useMemo缓存派生状态news,聚合标题、分类 ID 和内容,避免不必要的重计算
    • 分类数据从/categories接口获取,实现动态加载

3.富文本集成

  • 组件交互 :集成NewsEditor子组件,通过getContent回调接收 HTML 格式内容
  • 数据流转 :富文本编辑器内容变化时同步更新newsContent状态,确保表单数据一致性

4.技术要点实现

表单数据处理

  • 受控组件设计

    • 新闻标题:通过newsTitle状态和onChange事件实现双向绑定
    • 新闻分类:使用Select组件,通过handleChange同步选中的分类 ID
  • 分类数据加载

    复制代码
    useEffect(() => {
      axios.get("http://localhost:3000/categories").then((res) => {
        const formattedData = res.data.map(item => ({
          label: item.title,
          value: item.id
        }));
        setCategoryList(formattedData);
      });
    }, []);
  • 派生状态优化 :使用useMemo缓存新闻对象,避免不必要的重计算

    复制代码
    const news = useMemo(() => ({
      title: newsTitle,
      categoryId: newsCategory,
      content: newsContent,
    }), [newsTitle, newsCategory, newsContent]);

富文本编辑器集成

  • 组件通信 :通过getContent····················转换后的 HTML 内容

    复制代码
    <NewsEditor
      getContent={(value) => {
        setNewsContent(value);
      }}
    />
  • 内容转换 :在子组件NewsEditor中通过draftToHtml将编辑内容转为 HTML 格式存储

数据提交与状态管理

  • 提交逻辑 :根据auditState参数区分保存草稿(0)和提交审核(1)

    复制代码
    const handleSave = (auditState) => {
      axios.post("http://localhost:3000/news", {
        ...news,
        region: user.region || "全球",
        author: user.username,
        roleId: user.roleId,
        auditState,
        publishState: 0,
        createTime: Date.now(),
        star: 0,
        view: 0
      }).then(() => {
        // 处理成功逻辑
      });
    };
  • 操作反馈 :使用 Ant Design 的notification组件提供操作结果提示

    复制代码
    const [api, contextHolder] = notification.useNotification();
    const openNotification = (placement, auditState) => {
      api.success({
        message: auditState === 0 ? "保存成功" : "提交成功",
        description: `您可以在${auditState === 0 ? "草稿箱" : "审核列表"}中查看`,
        placement,
        duration: 3,
      });
    };

路由与权限关联

  • 页面跳转:提交成功后根据操作类型跳转到对应列表页

    复制代码
    if (auditState === 0) navigator("/news-manage/draft");
    else navigator("/audit-manage/list");
  • 用户信息获取 :从localStorage中读取当前登录用户信息,自动填充作者、区域等元数据

    复制代码
    const user = JSON.parse(localStorage.getItem("token"));

错误处理机制

  • 提交失败处理:捕获 API 请求错误并显示错误提示

    复制代码
    .catch((error) => {
      api.error({
        message: "操作失败",
        description: "保存新闻时发生错误,请重试",
        placement: "bottomRight",
      });
      console.error("Save error:", error);
    });
  • 前置校验:在步骤切换时进行数据完整性检查,阻止无效提交

相关联组件:新闻更新组件

实现方法:新闻更新组件本质上和撰写新闻组件是一样的 差异在于会在处理副作用的时候预先将表格信息填写到Form表单中,并且最后的提交的请求发送的是patch更新储存

复制代码
  useEffect(()=>{
    axios.get(`http://localhost:3000/news/${id}?_expand=category&_expand=role`).then((res)=>{
      const newsData = res.data;      
      // 设置状态
      setNewsContent(newsData.content || "")
      setNewsTitle(newsData.title || "")
      setNewsCategory(newsData.categoryId || "")
      
      // 设置表单值
      form.setFieldsValue({
        title: newsData.title || "",
        categoryId: newsData.categoryId || ""
      });
    }).catch(error => {
      console.error('获取数据失败:', error);
    })
  },[id, form])
2.预览新闻组件
关键页面展示:
功能说明:

新闻预览组件用于展示新闻的完整内容及相关元数据,提供新闻查看、返回上一页和编辑功能。该组件主要应用于新闻管理系统中,供用户预览已创建的新闻内容,验证新闻发布效果,并支持对自己创建的新闻进行编辑操作。

技术实现要点:
  • 数据管理

    • 数据获取 :通过useParams获取当前新闻 ID,结合useEffect钩子从接口http://localhost:3000/news/${id}?_expand=category&_expand=role请求新闻详情数据,包括关联的分类和角色信息
    • 数据状态 :使用useState定义newsData状态存储新闻完整信息,初始值为空对象
    • 数据依赖 :将id作为useEffect的依赖项,确保路由参数变化时重新请求数据
    • 数据展示 :通过条件渲染{newsData && ...}避免数据请求延迟导致的空值错误
  • 状态管理

    • 用户信息 :从localStorage中读取当前登录用户信息(username),用于控制编辑按钮的权限
    • 导航状态 :使用useNavigate获取导航函数,实现页面跳转功能
    • 动态状态 :通过getStatusTag函数根据auditState(审核状态)和publishState(发布状态)动态生成状态标签,直观展示新闻当前状态
  • 数据调用与接口管理

    • 接口设计 :采用 RESTful 风格 API,通过GET请求获取单条新闻详情,请求时使用_expand参数关联查询分类信息
    • 时间格式化 :使用dayjs库对时间戳进行格式化处理,统一展示格式为YYYY-MM-DD HH:mm:ss
    • 内容渲染 :通过dangerouslySetInnerHTML属性解析富文本内容(HTML 格式),实现新闻内容的完整展示
  • 数据处理与交互函数

    • 返回功能handleBack函数通过window.history.back()实现返回上一页操作

      复制代码
        useEffect(()=>{
           axios.get(`http://localhost:3000/news/${id}?_expand=category&_expand=role`).then(res=>{
             setNewsData(res.data)
           })
         },[id]) // 添加 id 作为依赖项
         
         const handleBack = () => {
           console.log('返回按钮被点击');
           window.history.back()
           // 您的返回逻辑
         };
    • 编辑功能handleEdit函数通过navigate跳转到新闻编辑页面(/news-manage/update/${id}),并通过disabled属性控制只有新闻作者可编辑

      复制代码
        const handleEdit = () => {
          console.log('编辑按钮被点击');
          // 您的编辑逻辑
          navigate(`/news-manage/update/${id}`)
        };
    • 状态标签生成getStatusTag函数根据审核状态和发布状态的组合条件,返回不同颜色和文本的Tag组件,清晰标识新闻状态

      复制代码
        const getStatusTag = (auditState, publishState) => {
          if (auditState === 0 || auditState===1) {
            return <Tag color="orange">未审核</Tag>;
          } else if (auditState === 2) {
            if (publishState === 0) {
              return <Tag color="green">审核通过</Tag>;
            } else if (publishState === 2) {
              return <Tag color="cyan">已发布</Tag>;
            } else if(publishState===3){
              return <Tag color="red">已下线</Tag>;
            }else{
              return <Tag color="orange">待发布</Tag>;
            }
          } else {
            return <Tag color="red">审核不通过</Tag>;
          }
        };
    • 空值处理 :使用可选链操作符(category?.title)和默认值(|| '未分类')处理可能的空数据,增强组件健壮性

4.5审核管理模块:

1.审核新闻组件
关键页面展示:
功能说明:

审核组件用于新闻审核流程中的审核操作,主要面向管理员角色(超级管理员和区域管理员),提供对提交审核的新闻进行审核通过或驳回的功能。该组件根据管理员权限展示不同范围的待审核新闻,支持查看新闻详情并执行审核操作,是新闻发布流程中的关键环节。

技术实现要点:
  1. 数据管理:

    • 数据源定义 :通过dataSource状态存储待审核新闻的列表

    • 数据筛选规则

      • 超级管理员(id=1)可查看所有待审核新闻(auditState=1
      • 区域管理员仅能查看本区域内、且非本人提交的待审核新闻(region=${region}&author_ne=${username}
      • 通过 _expand=category 关联查询分类信息,用于展示新闻所属分类名称
    • 数据结构:每条数据包含新闻标题、作者、分类、ID 等核心字段,支持审核操作的标识与关联

  2. 状态管理

    • 用户信息状态 :从 localStorage 中读取当前登录用户的 region(区域)、role.id(角色 ID)和 username(用户名),用于权限控制和数据筛选
    • 列表数据状态dataSource 状态实时存储待审核新闻列表,操作后立即更新本地数据以优化用户体验
    • 权限状态区分:通过角色 ID 区分超级管理员和区域管理员,实现不同的数据访问权限控制
  3. 数据调用与接口管理

    • 数据请求接口 :使用查询参数实现数据筛选,_expand=category 关联查询分类信息

      复制代码
      // 超级管理员获取所有待审核新闻
      axios.get(`http://localhost:3000/news?_expand=category&auditState=1`)
      
      // 区域管理员获取权限范围内的待审核新闻
      axios.get(`http://localhost:3000/news?region=${region}&_expand=category&auditState=1&author_ne=${username}`)
    • 状态更新接口

  4. 数据处理与交互函数

    • 源数据获取

      通过使用LocalStorage中存储的当前登录用户信息中的角色id来判断身份,以获取不用的数据源

      复制代码
      const getRightList = async () => {
        if (id === 1) {
          const res = await axios.get(
            `http://localhost:3000/news?_expand=category&auditState=1`
          );
          setDataSource(res.data);
        } else {
          //普通管理员 只负责自己的区域 并且不能审核自己的新闻
          const res = await axios.get(
            `http://localhost:3000/news?region=${region}&_expand=category&auditState=1&author_ne=${username}`
          );
          setDataSource(res.data);
        }
      };
    • 表格列定义

      • 新闻标题:通过锚点链接跳转到预览页面(#/news-manage/preview/${item.id}),支持查看完整内容
      • 作者:直接展示新闻作者信息
      • 新闻分类:渲染关联查询到的分类名称(category.title
      • 操作列:提供 "通过" 和 "驳回" 按钮,执行对应的审核操作
    • 审核操作函数

      • 审核通过:点击按钮后,从本地列表中移除该新闻(filter 方法),同时调用接口将 auditState 更新为 2(审核通过),publishState 更新为 1(待发布)

        复制代码
        ()=>{
        setDataSource(dataSource.filter((data) => data.id !== item.id));
        axios.patch(`http://localhost:3000/news/${item.id}`,{auditState:2,publishState:1})
        }
      • 审核驳回:点击按钮后,从本地列表中移除该新闻,调用接口将 auditState 更新为 3(审核不通过)

        复制代码
        ()=>{
        setDataSource(dataSource.filter((data) => data.id !== item.id));
        axios.patch(`http://localhost:3000/news/${item.id}`,{auditState:3})
        }
    • 权限控制逻辑:通过角色 ID 动态调整数据请求范围,确保不同级别管理员只能看到权限范围内的待审核新闻,区域管理员无法审核自己提交的新闻

2.已审核列表组件
关键页面展示:
功能说明:

审核 List 组件用于展示当前登录编辑用户提交的新闻审核状态列表,支持查看新闻详情、根据不同审核状态执行不同操作(撤销提交、发布新闻、修改内容)。该组件主要面向普通编辑角色,帮助其跟踪自己提交的新闻在审核流程中的状态变化,并进行相应的后续操作。

技术实现要点:
  1. 数据管理

    • 数据源定义 :通过 dataSource 状态存储审核列表数据,初始值为空数组
    • 数据过滤规则
      • 仅展示当前登录用户(username)提交的新闻
      • 排除草稿状态(auditState=0)、已发布(publishState=2)和已下线(publishState=3)的新闻
      • 通过 _expand=category 关联查询分类信息,用于展示新闻分类名称
    • 数据获取时机 :组件挂载时通过 useEffect 钩子触发数据请求,依赖项为空数组确保只执行一次
    • 数据结构 :每条数据包含新闻标题、作者、分类、审核状态等核心字段,以及用于操作的 id 标识
  2. 状态管理

    • 用户信息状态 :从 localStorage 中读取当前登录用户的 username,用于数据筛选和权限控制
    • 导航状态 :通过 useNavigate 获取路由导航函数,实现操作后的页面跳转
    • 列表数据状态dataSource 状态实时反映当前列表数据,支持动态更新(如撤销、发布操作后)
    • 审核状态映射 :通过 getAuditStateStatusTag 函数将数字类型的 auditState 转换为可视化的标签组件
  3. 数据调用与接口管理

    • 数据请求接口

      复制代码
      // 获取当前用户的审核中新闻
      axios.get(`http://localhost:3000/news/?author=${username}&_expand=category`)
      • 使用查询参数 author 筛选当前用户的新闻,_expand=category 关联查询分类信息
    • 状态更新接口

      • 撤销提交:

        复制代码
          axios.patch(\`http://localhost:3000/news/${item.id}, {auditState:0})
      • 发布新闻:

        复制代码
          axios.patch(`http://localhost:3000/news/${item.id}, {publishState:2, publishTime:Date.now()})
    • 接口交互时机:在相应操作按钮的点击事件中触发,同步更新本地数据和服务器数据

  4. 数据处理与交互函数:

    • 渲染数据获取

      • 使用过滤函数分别排除 草稿状态(auditState=0)、已发布(publishState=2)和已下线(publishState=3)的新闻

      复制代码
        const getRightList = async () => {
              const res = await axios.get(
                `http://localhost:3000/news?author=${username}&_expand=category`
              );
              setDataSource(res.data.filter((item)=>{
                return item.auditState!=0&&item.publishState!=2&&item.publishState!=3
              }));
            };
    • 审核状态标签生成

      复制代码
      const getAuditStateStatusTag = (auditState) => {
        if (auditState === 1) return <Tag color="orange">未审核</Tag>;
        if (auditState === 2) return <Tag color="green">审核通过</Tag>;
        if (auditState === 3) return <Tag color="red">审核不通过</Tag>;
      };

      根据审核状态返回不同颜色和文本的标签组件,直观展示状态信息

    • 表格列定义

      • 新闻标题:通过锚点链接跳转到预览页面(#/news-manage/preview/${item.id}
      • 新闻分类:渲染关联查询到的分类名称(category.title
      • 审核状态:调用 getAuditStateStatusTag 生成状态标签
      • 操作列:根据当前审核状态动态展示操作按钮
    • 操作处理函数

      • 撤销提交(auditState=1):过滤本地列表数据并更新服务器审核状态为草稿(0)

        复制代码
          onClick={()=>{
                  setDataSource(dataSource.filter((data) => data.id !== item.id));
                    // 将新闻发布状态改为待发布
                  axios.patch(`http://localhost:3000/news/${item.id}`,{publishState:2,publishTiem:Date.now()}).then(()=>{
                    navigate('/publish-manage/published')
                  });
                }}
      • 发布新闻(auditState=2):更新发布状态为已发布(2)并记录发布时间,成功后跳转到已发布列表

        复制代码
          ()=>{
          setDataSource(dataSource.filter((data) => data.id !== item.id));
          // 将新闻发布状态改为待发布
          axios.patch(`http://localhost:3000/news/${item.id}`,{publishState:2,publishTiem:Date.now()}).then(()=>{
          navigate('/publish-manage/published')
          });
          }
      • 修改内容(auditState=3):跳转到新闻编辑页面(/news-manage/update/${item.id}

    • 列表更新策略 :操作后通过 setDataSource 立即更新本地列表数据,提升交互体验,同时同步更新服务器数据

4.6发布管理模块:

关键页面展示:

待发布页面

已发布页面

已下线页面

功能说明:

发布管理模块主要用于管理新闻的发布状态,包含三个核心子页面:

  • 未发布新闻Unpublished.jsx):展示审核通过但尚未发布的新闻,支持发布操作
  • 已发布新闻Published.jsx):展示当前处于发布状态的新闻,支持下线操作
  • 已下线新闻Sunset.jsx):展示已从发布状态下线的新闻,支持删除操作

模块面向具有新闻发布权限的编辑角色,提供直观的新闻状态管理界面,支持通过操作按钮快速变更新闻发布状态,并实时更新展示列表。

技术实现要点
  1. 数据管理
  • 数据源设计 :通过 usePublishInfo 自定义 Hook 统一管理发布列表数据,根据 publishState 参数区分不同状态的新闻
    • publishState=1:未发布新闻
    • publishState=2:已发布新闻
    • publishState=3:已下线新闻
  • 数据筛选规则
    • 仅展示当前登录用户创建的新闻(通过 author=${user.username} 筛选)
    • 关联查询新闻分类信息(通过 _expand=category 参数)
    • 仅包含审核通过的新闻(auditState=2
  • 数据结构:每条数据包含新闻 ID、标题、作者、分类、发布状态等核心字段,支持操作标识与关联
  1. 状态管理
  • 用户状态 :从 localStorage 读取当前登录用户信息(username),用于数据筛选和权限控制
  • 列表状态dataSource 状态存储当前页面的新闻列表数据,setDataSource 方法用于更新列表状态
  • 发布状态 :通过 publishState 属性区分不同子页面的新闻状态,实现组件复用
  • 操作状态:操作按钮点击后立即更新本地列表状态,同时同步服务器数据,提升交互体验
  1. 数据调用与接口管理
  • 数据请求接口

    复制代码
    // 获取指定状态的新闻列表
    axios.get(`http://localhost:3000/news?author=${user.username}&auditState=2&_expand=category&publishState=${publishState}`)
  • 状态更新接口

  • 接口封装 :通过自定义 Hook usePublishInfo 封装数据请求逻辑,实现数据获取与状态管理的复用

  1. 数据处理与交互函数
  • 通用列表组件PublishList.jsx 作为通用列表组件,通过接收 publishState 属性实现不同状态新闻列表的复用

  • 动态操作按钮 :根据 publishState 动态展示操作按钮:

    • 未发布页面:显示 "发布" 按钮
    • 已发布页面:显示 "下线" 按钮
    • 已下线页面:显示 "删除" 按钮
  • 操作处理函数

    复制代码
    // 发布新闻
    const uploadNews = async (item) => {
      await axios.patch(`http://localhost:3000/news/${item.id}`, {
        publishState: 2,
        publishTime: Date.now()
      });
      setDataSource(dataSource.filter((data) => data.id !== item.id));
    };
    
    // 下线新闻
    const BreakdownNews = async (item) => {
      await axios.patch(`http://localhost:3000/news/${item.id}`, {
        publishState: 3,
      });
      setDataSource(dataSource.filter((data) => data.id !== item.id));
    };
    
    // 删除新闻
    const deleteNews = async (item) => {
      await axios.delete(`http://localhost:3000/news/${item.id}`);
      setDataSource(dataSource.filter((data) => dataid !== item.id));
    };
  • 表格列定义

    • 新闻标题:通过锚点链接跳转到预览页面
    • 新闻分类:展示关联的分类名称
    • 操作列:根据发布状态展示对应的操作按钮
组件设计
  • 页面组件

    • Unpublished.jsx:未发布新闻页面,传递 publishState=1 给列表组件
    • Published.jsx:已发布新闻页面,传递 publishState=2 给列表组件
    • Sunset.jsx:已下线新闻页面,传递 publishState=3 给列表组件
  • 通用组件

    • PublishList.jsx:核心列表组件,接收 publishState 属性,渲染对应状态的新闻列表
    • usePublishInfo.jsx:自定义 Hook,封装数据请求与状态管理逻辑
  • 权限控制:仅展示当前登录用户创建的新闻,确保数据隔离

  • 流程控制:严格遵循新闻发布流程:

    • 审核通过(auditState=2)→ 未发布(publishState=1)→ 已发布(publishState=2)→ 已下线(publishState=3
  • 操作限制:根据新闻当前状态提供合法操作,避免无效状态转换

4.7页面框架模块:

1.顶部导航栏
关键页面展示:
功能说明:

顶部导航栏是新闻管理系统的全局导航组件,位于页面顶部,提供系统核心操作入口和用户信息展示。主要功能包括:

  • 侧边栏折叠 / 展开控制
  • 当前登录用户信息展示
  • 用户角色显示
  • 退出登录功能

该组件适配系统整体设计风格,与侧边导航栏联动,为用户提供一致的操作体验。

技术实现要点:

1.状态管理

  • 侧边栏折叠状态 :通过 Redux 全局状态管理(collapsedStore)维护侧边栏折叠状态

    • 使用 useSelector 获取当前折叠状态(collapsed

    • 通过 dispatch(changeCollapsed()) 触发状态变更

    复制代码
      //collapsedStore的Redux实例
      import { createSlice } from "@reduxjs/toolkit";
    
      /* 持久化collapsedStore */
      const collapsedStore = createSlice({
          name: 'collapsed',
          initialState:{
              collapsed: false
          },
          reducers: {
              changeCollapsed(state, action) {
                 state.collapsed = action.payload
              }
          }
      })
    
      export const { changeCollapsed } = collapsedStore.actions
      const collapsedReducer = collapsedStore.reducer
      export default collapsedReducer
  • 用户信息 :从 localStorage 中读取登录用户信息(token),包含用户名和角色信息

2.组件结构

  • 基于 Ant Design 的 Layout.Header 构建容器
  • 左侧放置侧边栏折叠 / 展开按钮
  • 右侧展示用户信息和下拉菜单
  • 使用 Dropdown 组件实现用户操作菜单

3.核心功能实现

  • 侧边栏控制

    复制代码
    // 切换侧边栏折叠状态
    <Button
      type="text"
      icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
      onClick={()=>dispatch(changeCollapsed(!collapsed))}
    />
  • 用户信息展示

    复制代码
    // 显示当前登录用户名
    <span>欢迎{users.username}使用</span>
    
    // 头像与下拉菜单
    <Dropdown menu={{ items }}>
      <a onClick={(e) => e.preventDefault()}>
        <Space>
          <Avatar icon={<UserOutlined />} />
          <DownOutlined />
        </Space>
      </a>
    </Dropdown>
  • 退出登录功能

    复制代码
    // 清除本地存储并刷新页面
    onClick={() => {
      localStorage.removeItem("token");
      window.location.reload();
    }}
2.侧边导航栏
关键页面展示
功能说明:

侧边导航栏,根据登录用户的权限动态的生成侧边领,当前当前路由自动高亮选中菜单项,与顶部导航栏联动的折叠 / 展开功能,支持多级菜单展示(父菜单与子菜单层级结构)

技术实现要点
  1. 数据获取与权限过滤

    • 获取用户信息:

      复制代码
      const  {role: { rights } } = JSON.parse(localStorage.getItem("token"));
    • 获取当前路由为当前选中菜单高亮:

      复制代码
      const [selectedKeys, setSelectedKeys] = useState([location.pathname]);
      ...other code
       return(
          <Menu
      		//....
              selectedKeys={selectedKeys}
      		//...
        />
      )
    • 从后端获取权限列表并处理数据

      • newRes:用来保存处理完的权限列表数据

      • newResChildren: 用来储存权限的子权限

        const getItems = async () => {
        const res = await axios.get(
        "http://localhost:3000/rights?_embed=children"
        );
        //处理首页部分标签不需要展示问题
        //处理完的新结果
        let newRes = [];
        res.data.forEach((item) => {
        // 分开处理 处理存在children的和不存在的
        if (item.children.length === 0 && rights.includes(item.key)) {
        delete item.children;
        newRes.push(item);
        } else {
        let newResChildren = [];
        item.children.forEach((element) => {
        //如果是首页就添加到新的children数组中去 并且还要判断当前登录用户是不是包含该权限
        if (element.pagepermisson === 1 && rights.includes(element.key)) {
        newResChildren.push(element);
        }
        });
        //只有当用户有父级权限或者有子级权限时,才添加该菜单项
        // 如果用户没有某个模块的权限,且该模块下没有任何可访问的子菜单,该模块就不会显示在侧边菜单中
        // 如果用户有父级权限,无论子菜单如何,都会显示该模块
        if (rights.includes(item.key) || newResChildren.length > 0) {
        newRes.push({ ...item, children: newResChildren });
        }
        }
        });
        setItems(newRes);

        复制代码
        const setIcon = (item, iconMap) => {
          // 创建副本以避免直接修改原数组
          const newItems = JSON.parse(JSON.stringify(item));
          newItems.forEach((element) => {
            for (let key in iconMap) {
              if (key === element.label) {
                element.icon = iconMap[key];
              }
            }
          });
          return newItems;
        };
        setItems(setIcon(newRes, iconMap));

        };

  2. 菜单ICON映射

    1. icon的映射表:

      复制代码
      const iconMap = {
          首页: <HomeOutlined />,
          用户管理: <UserOutlined />,
          用户列表: <UserOutlined />,
          权限管理: <SettingOutlined />,
          新闻管理: <FormOutlined />,
          审核管理: <AuditOutlined />,
          发布管理: <UploadOutlined />,
      };
    2. 映射函数

      复制代码
        const setIcon = (item, iconMap) => {
          // 创建副本以避免直接修改原数组
          const newItems = JSON.parse(JSON.stringify(item));
          newItems.forEach((element) => {
            for (let key in iconMap) {
              if (key === element.label) {
                element.icon = iconMap[key];
              }
            }
          });
          return newItems;
        };
  3. 联动路由

    • 使用 useLocation 获取当前路由路径,自动高亮对应菜单项

    • 通过 selectedKeys 状态维护选中状态

    • 点击菜单项时通过 useNavigate 实现路由跳转

    复制代码
      // 路由联动核心逻辑
      const location = useLocation();
      const [selectedKeys, setSelectedKeys] = useState([location.pathname]);
    
      // 点击菜单跳转路由
      onClick={(item) => {
        navigater(item.key);
        window.scrollTo(0, 0);
      }}
  4. 折叠状态管理

    • 通过 ReduxcollapsedStore 获取全局折叠状态

    • 与顶部导航栏的折叠 / 展开按钮联动

    • 响应式调整侧边栏宽度

      复制代码
      // 折叠状态获取
      const {collapsed}=useSelector(state=>state.collapsed)
      
      // 折叠组件配置
      <Sider trigger={null} collapsible collapsed={collapsed}>
        {/* 菜单内容 */}
      </Sider>
  5. 菜单数据处理

    • 将处理好的菜单item重新使用过滤函数过滤,保证antd组件不会因为数据问题而出错

    复制代码
      const filterMenuItems = (items) => {
        return items.map((item) => {
          // 创建一个新的对象,只包含Menu组件需要的属性
          const filteredItem = {
            key: item.key,
            label: item.label,
            icon: item.icon,
            children: item.children ? filterMenuItems(item.children) : undefined,
          };
        
          return filteredItem;
        });
      };

5.部署 / 运行说明

5.1 环境要求(Node 版本、包管理器等)

复制代码
Node.js >= 16.0.0
npm >= 8.0.0 或 yarn >= 1.22.0

5.2 克隆 / 安装依赖步骤

复制代码
git clone https://gitee.com/lenlers/news-release-system---act.git
cd newssystem

5.3 启动开发环境(前端 dev + 启动 JSON Server)

复制代码
安装依赖
npm install
# 或者
yarn install

npm run server 启动后端服务
npm run dev 启动前端页面                            
相关推荐
码上成长6 小时前
qiankun 微前端完全入门指南 - 从零到精通
前端
HuangYongbiao6 小时前
Rspack Tree-Shaking 原理:Rust 让 Tree-Shaking 更彻底?
前端
Boale_H6 小时前
前端流水线连接npm私有仓库
前端·npm·node.js
yoyoma6 小时前
一文搞懂浏览器垃圾回收机制:从原理到面试答题全攻略
前端·javascript
HuWentao6 小时前
如何创建自我更新的通用项目脚本
前端·flutter
不一样的少年_6 小时前
女朋友被链接折磨疯了,我写了个工具一键解救
前端·javascript·浏览器
Zyx20076 小时前
CSS 超级武器:Stylus 与 Flexbox 强强联手,打造极致响应式动画界面(上篇)
前端·css
烛阴6 小时前
超越面向对象:用函数式思维重塑你的Lua代码
前端·lua
微知语7 小时前
Cell 与 RefCell:Rust 内部可变性的双生子解析
java·前端·rust