新闻业务--草稿箱
本人之前写的侧边栏渲染有点问题,超级管理员和其他的不兼容,所以修改了一下SideMenu:
import React, { useState, useEffect } from'react';
import { Layout, Menu } from 'antd';
import { useNavigate } from'react-router-dom';
import axios from 'axios';
import {UserOutlined,SettingOutlined,UploadOutlined,VideoCameraOutlined,AuditOutlined,FormOutlined,HomeOutlined,
} from '@ant-design/icons';
import './index.css';
import { useLocation } from'react-router-dom';const { SubMenu } = Menu;
const { Sider } = Layout;// **手动映射菜单项对应的图标**
const iconMap = {首页: <HomeOutlined />,用户管理: <UserOutlined />,用户列表: <UserOutlined />,权限管理: <SettingOutlined />,新闻管理: <FormOutlined />,审核管理: <AuditOutlined />,发布管理: <UploadOutlined />,
};function SideMenu() {const [menu, setMenu] = useState([]);const location = useLocation(); // 获取当前的路径useEffect(() => {axios.get('http://localhost:3000/rights?_embed=children').then((res) => {setMenu(res.data);}).catch((error) => {console.error('获取菜单数据失败:', error);// 可根据情况设置默认菜单数据或提示用户});
}, []);const navigate = useNavigate();const tokenData = JSON.parse(localStorage.getItem('token')) || {};
const { role = {} } = tokenData;
let allRights = [];// 兼容数组结构(普通角色)和对象结构(超级管理员)
if (Array.isArray(role.rights)) {allRights = role.rights;
} else if (typeof role.rights === 'object' && role.rights !== null) {const { checked = [], halfChecked = [] } = role.rights;allRights = [...checked, ...halfChecked];
}const checkPermission = (item) => {// 检查用户是否具有访问权限return item.pagepermisson && allRights.includes(item.key);};const renderMenu = (menuList) => {return menuList.map((item) => {const icon = iconMap[item.title] || <VideoCameraOutlined />; // 默认图标if (item.children?.length > 0 && checkPermission(item)) {return (<SubMenu key={item.key} icon={icon} title={item.title}>{renderMenu(item.children)}</SubMenu>);}return (checkPermission(item) && (<Menu.Itemkey={item.key}icon={icon}onClick={() => navigate(item.key)}>{item.title}</Menu.Item>));});};//找到路径const selectKeys = [location.pathname];//分割字符串const openKeys = ['/' + location.pathname.split('/')[1]];return (<Sider trigger={null} collapsible><div style={{ display: 'flex', height: '100%', flexDirection: 'column' }}><div className="logo">新闻发布系统</div><div style={{ flex: 1, overflow: 'auto' }}><Menutheme="dark"mode="inline"selectedKeys={selectKeys}defaultOpenKeys={openKeys}>{renderMenu(menu)}</Menu></div></div></Sider>);
}export default SideMenu;
列表
用一下之前写的RightList改一下代码:
import React,{useState,useEffect} from 'react';
import { Button,Table,Modal} from 'antd';
import { EditOutlined,DeleteOutlined,VerticalAlignTopOutlined,UploadOutlined,ExclamationCircleOutlined } from '@ant-design/icons';
import axios from 'axios';
import { data } from 'react-router-dom';
import { render } from 'nprogress';
const { confirm } = Modal;function NewsDraft() {const [dataSource,setdataSource]=useState([])const {username} = JSON.parse(localStorage.getItem('token'))useEffect(()=>{axios.get(`/news?author=${username}&auditState=0&_expand=category`).then(res=>{const list = res.datasetdataSource(list)})},[username])const columns = [{title: 'ID',dataIndex: 'id',render:(id)=>{return <b>{id}</b>}},{title: '新闻标题',dataIndex: 'title',},{title: '作者',dataIndex: 'author',},{title: '分类',dataIndex: 'category',render:(category)=>{return category.title}},{title: '操作',render:(record)=>{return <div><Button type="primary" shape="circle" icon={<EditOutlined />} disabled={record.pagepermisson === undefined }/>{/* 如果没有配置权限,就不显示 */}<Button danger type="primary" shape="circle" icon={<DeleteOutlined />} onClick={()=>confirmMethod(record)}/><Button shape="circle" icon={<UploadOutlined />}/></div>}},];const confirmMethod = (record) => {confirm({title: 'Do you Want to delete these items?',icon: <ExclamationCircleOutlined />,onOk() {deleteMethod(record)},onCancel() {console.log('Cancel');},});console.log('确认删除')};const deleteMethod = (record) => {// console.log(record);setdataSource(dataSource.filter(data=>data.id !== record.id))axios.delete(`/news/${record.id}`)};return (<div><Table dataSource={dataSource} columns={columns} pagination={{//一页显示几条数据pageSize:5}}rowKey={record=>record.id}/></div>);
}export default NewsDraft;
可以正常的显示和删除
预览路由及新闻
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Button, Typography, Space, Row, Col, Divider } from 'antd';
import { LeftOutlined } from '@ant-design/icons';
import axios from 'axios';const { Title } = Typography;export default function NewsPreview() {const { id } = useParams();const navigate = useNavigate();const [newsInfo, setNewsInfo] = useState(null);useEffect(() => {axios.get(`http://localhost:3000/news/${id}?_expand=category&_expand=role`).then(res => {setNewsInfo(res.data);});}, [id]);const auditMap = ['未审核', '审核中', '已通过', '未通过'];const publishMap = ['未发布', '待发布', '已上线', '已下线'];const colorMap = ['red', 'orange', 'green', 'gray'];return newsInfo && (<div style={{ padding: 24 }}>{/* 顶部标题区 */}<div style={{ display: 'flex', alignItems: 'center', marginBottom: 16 }}><Button type="text" icon={<LeftOutlined />} onClick={() => navigate(-1)} /><Title level={4} style={{ margin: 0 }}>{newsInfo.title}<span style={{ fontSize: 14, marginLeft: 12, color: '#999' }}>{newsInfo.category.title}</span></Title></div>{/* 信息栏 */}<Row gutter={[24, 16]}><Col span={8}><div>创建者:{newsInfo.author}</div><div>区域:{newsInfo.region}</div><div>访问数量:<span style={{ color: 'green' }}>{newsInfo.view}</span></div></Col><Col span={8}><div>创建时间:{newsInfo.createTime ? new Date(newsInfo.createTime).toLocaleString() : '-'}</div><div>审核状态:<span style={{ color: colorMap[newsInfo.auditState] }}>{auditMap[newsInfo.auditState]}</span></div><div>点赞数量:<span style={{ color: 'green' }}>{newsInfo.star}</span></div></Col><Col span={8}><div>发布时间:{newsInfo.publishTime ? new Date(newsInfo.publishTime).toLocaleString() : '-'}</div><div>发布状态:<span style={{ color: colorMap[newsInfo.publishState] }}>{publishMap[newsInfo.publishState]}</span></div><div>评论数量:<span style={{ color: 'blue' }}>{newsInfo.comment || 0}</span></div></Col></Row><Divider />{/* 正文内容 */}<div dangerouslySetInnerHTML={{ __html: newsInfo.content }} style={{ padding: 16, background: '#fff' }} /></div>);
}
这就是预览路由,,科文老师是用了antd的页头,但是在antd5中已经弃用了,所以采用Row和Col实现
更新新闻
很多都是复用之前newsAdd的组件的内容(主要是进行一下父子通信):
import React, { useState, useEffect,useRef } from 'react'
import {Breadcrumb,Typography,Button,Space,Steps,message,theme,Checkbox,Form,Input,Select,notification,
} from 'antd'
import style from './News.module.css'
import axios from 'axios'
import { set } from 'nprogress'
import NewsEditor from '../../../../src/components/news-manage/NewsEditor.jsx'
import { useNavigate } from 'react-router-dom'const { Title } = Typography
const description = 'This is a description'
const { Option } = Selectconst onFinish = (values) => {console.log('Success:', values)
}
const onFinishFailed = (errorInfo) => {console.log('Failed:', errorInfo)
}export default function NewsAdd(props) {const { token } = theme.useToken()const [current, setCurrent] = useState(0)const [categoryList, setCategoryList] = useState([])const [formInfo, setFormInfo] = useState({})//用于存储新闻内容const [content, setContent] = useState("")const User = JSON.parse(localStorage.getItem('token')) const NewsForm = useRef(null)const steps = [{title: '基本信息',content: (<Formname="basic"ref = {NewsForm}labelCol={{span: 4,}}wrapperCol={{span: 20,}}style={{maxWidth: 600,margin: '25px',}}initialValues={{remember: true,}}onFinish={onFinish}onFinishFailed={onFinishFailed}autoComplete="off"><Form.Itemlabel="新闻标题"name="title"rules={[{required: true,message: 'Please input your username!',},]}><Input></Input></Form.Item><Form.Itemlabel="新闻分类"name="categoryId"rules={[{required: true,message: 'Please input your username!',},]}><Select>{categoryList.map((item) => (<Select.Option value={item.id} key={item.id}>{item.title}</Select.Option>))}</Select></Form.Item></Form>),},{title: '新闻内容',// 留一个回调函数用于子传父content: <NewsEditor getContent={(value)=>{// console.log(value) setContent(value)}}></NewsEditor>,},{title: '新闻提交',content: '保存草稿 审核提交',},]const next = () => {if(current === 0){NewsForm.current.validateFields().then((res) => {setFormInfo(res)setCurrent(current + 1)}).catch((err) => {console.log(err)})}else{if(content === "" || content.trim()==="<p></p>"){//如果收集的是空的就不放行message.error('新闻内容不能为空')return}else{setCurrent(current + 1)}}}const prev = () => {setCurrent(current - 1)}const items = steps.map((item) => ({key: item.title,title: item.title,}))useEffect(() => {axios.get('/categories').then((res) => {setCategoryList(res.data)})}, [])// 在useEffect中处理历史内容(可以从服务器或父组件传递过来)useEffect(() => {// 如果props.content有值(历史内容),则初始化为该内容if (props.content) {setContent(props.content); // 假设历史内容是props.content}}, [props.content]);const handleContentChange = (newContent) => {setContent(newContent); // 更新内容};// return (// <div>// {/* 将历史内容传递给 NewsEditor */}// <NewsEditor content={content} getContent={handleContentChange} />// {/* 其他代码 */}// </div>// );const navigate = useNavigate()const handleSave = (auditState) => {axios.post('/news', {...formInfo,"content":content,"region": User.region?User.region:"全球","author": User.username,"roleId": User.roleId,"auditState": auditState,"publishState": 0,"createTime": Date.now(),"star":0,"view":0,"publishState": 0,}).then((res) => {//这个写法已经舍弃了// props.history.push(auditState===0?'/news-manage/draft':'/audit-manage/list')navigate(auditState === 0 ? '/news-manage/draft' : '/audit-manage/list')notification.info({message:`通知`,description:`您可以到${auditState===0?'草稿箱':'审核列表'}查看您的新闻`,placement: 'bottomRight',})})
}return (<div style={{ marginBottom: 24 }}><Breadcrumbitems={[{ title: 'Home' }, { title: 'List' }, { title: 'App' }]}/><Space style={{ justifyContent: 'space-between', width: '100%' }}><Title level={2} style={{ margin: '16px 0' }}>撰写新闻</Title><Space><Button>Cancel</Button><Button type="primary">Submit</Button></Space></Space><Steps current={current} items={items} /><div>{steps[current].content}</div><divstyle={{marginTop: 24,}}>{current < steps.length - 1 && (<Button type="primary" onClick={() => next()}>Next</Button>)}{current === steps.length - 1 && (<><Buttontype="primary"style={{margin: '0 8px',}}onClick={() => handleSave(0)}>保存草稿</Button><Button type="primary" onClick={() => handleSave(1)}>提交审核</Button></>)}{current > 0 && (<Buttonstyle={{margin: '0 8px',}}onClick={() => prev()}>Previous</Button>)}</div></div>)
}
这是编辑:
import React, { useEffect, useState } from 'react'
import { Editor } from 'react-draft-wysiwyg'
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
import { EditorState, convertToRaw,ContentState } from 'draft-js'
import htmlToDraft from 'html-to-draftjs';
import draftToHtml from 'draftjs-to-html';
import { set } from 'nprogress';export default function NewsEditor(props) {useEffect(() => {const html = props.content;if(html===undefined) returnconst contentBlock = htmlToDraft(html);if (contentBlock) {const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);const editorState = EditorState.createWithContent(contentState);setEditorState(editorState)}}, [props.content])// 定义 editorState 初始状态const [editorState, setEditorState] = useState()// 处理 editorState 更新const onEditorStateChange = (newState) => {setEditorState(newState)}return (<div><EditoreditorState={editorState}toolbarClassName="toolbarClassName"wrapperClassName="wrapperClassName"editorClassName="editorClassName"onEditorStateChange={onEditorStateChange}onBlur={()=>{//进行回调props.getContent(draftToHtml(convertToRaw(editorState.getCurrentContent())))}}/></div>)
}
在更新的时候记得把axios请求改成patch,否则还是会新建:
import React, { useState, useEffect,useRef, } from 'react'
import {Breadcrumb,Typography,Button,Space,Steps,message,theme,Checkbox,Form,Input,Select,notification,
} from 'antd'
import style from './News.module.css'
import axios from 'axios'
import { set } from 'nprogress'
import NewsEditor from '../../../../src/components/news-manage/NewsEditor.jsx'
import { useNavigate,useParams, } from 'react-router-dom'
import { LeftOutlined } from '@ant-design/icons';const { Title } = Typography
const description = 'This is a description'
const { Option } = Selectconst onFinish = (values) => {console.log('Success:', values)
}
const onFinishFailed = (errorInfo) => {console.log('Failed:', errorInfo)
}export default function NewsUpdate(props) {const { token } = theme.useToken()const [current, setCurrent] = useState(0)const [categoryList, setCategoryList] = useState([])const { Title } = Typography;const [formInfo, setFormInfo] = useState({})const [content, setContent] = useState("")const User = JSON.parse(localStorage.getItem('token')) const NewsForm = useRef(null)const steps = [{title: '基本信息',content: (<Formname="basic"ref = {NewsForm}labelCol={{span: 4,}}wrapperCol={{span: 20,}}style={{maxWidth: 600,margin: '25px',}}initialValues={{remember: true,}}onFinish={onFinish}onFinishFailed={onFinishFailed}autoComplete="off"><Form.Itemlabel="新闻标题"name="title"rules={[{required: true,message: 'Please input your username!',},]}><Input></Input></Form.Item><Form.Itemlabel="新闻分类"name="categoryId"rules={[{required: true,message: 'Please input your username!',},]}><Select>{categoryList.map((item) => (<Select.Option value={item.id} key={item.id}>{item.title}</Select.Option>))}</Select></Form.Item></Form>),},{title: '新闻内容',// 留一个回调函数用于子传父content: <NewsEditor getContent={(value)=>{// console.log(value) setContent(value)}} content={content}></NewsEditor>,},{title: '新闻提交',content: '保存草稿 审核提交',},]const next = () => {if(current === 0){NewsForm.current.validateFields().then((res) => {setFormInfo(res)setCurrent(current + 1)}).catch((err) => {console.log(err)})}else{if(content === "" || content.trim()==="<p></p>"){//如果收集的是空的就不放行message.error('新闻内容不能为空')return}else{setCurrent(current + 1)}}}const prev = () => {setCurrent(current - 1)}const items = steps.map((item) => ({key: item.title,title: item.title,}))useEffect(() => {axios.get('/categories').then((res) => {setCategoryList(res.data)})}, [])//取出历史信息const { id } = useParams();useEffect(() => {axios.get(`http://localhost:3000/news/${id}?_expand=category&_expand=role`).then(res => {// setNewsInfo(res.data);let {content} = res.dataNewsForm.current.setFieldsValue(res.data)setContent(content);});}, [id]);const navigate = useNavigate()const handleSave = (auditState) => {axios.patch(`/news/${id}`, {...formInfo,"content":content,"region": User.region?User.region:"全球","author": User.username,"roleId": User.roleId,"auditState": auditState,"publishState": 0,"createTime": Date.now(),"star":0,"view":0,"publishState": 0,}).then((res) => {//这个写法已经舍弃了// props.history.push(auditState===0?'/news-manage/draft':'/audit-manage/list')navigate(auditState === 0 ? '/news-manage/draft' : '/audit-manage/list')notification.info({message:`通知`,description:`您可以到${auditState===0?'草稿箱':'审核列表'}查看您的新闻`,placement: 'bottomRight',})})
}return (<div style={{ marginBottom: 24 }}><Breadcrumbitems={[{ title: 'Home' }, { title: 'List' }, { title: 'App' }]}/><Space style={{ justifyContent: 'space-between', width: '100%' }}><Space align="center"><Buttonicon={<LeftOutlined />}type="text"onClick={() => navigate(-1)}/><Title level={2} style={{ margin:'16px 0' }}>更新新闻</Title></Space>{/* 右边:按钮组 */}<Space><Button>Cancel</Button><Button type="primary">Submit</Button></Space></Space><Steps current={current} items={items} /><div>{steps[current].content}</div><divstyle={{marginTop: 24,}}>{current < steps.length - 1 && (<Button type="primary" onClick={() => next()}>Next</Button>)}{current === steps.length - 1 && (<><Buttontype="primary"style={{margin: '0 8px',}}onClick={() => handleSave(0)}>保存草稿</Button><Button type="primary" onClick={() => handleSave(1)}>提交审核</Button></>)}{current > 0 && (<Buttonstyle={{margin: '0 8px',}}onClick={() => prev()}>Previous</Button>)}</div></div>)
}
提交审核
现在要实现点击按钮可以提交到审核列表
import React, { useState, useEffect } from 'react'
import { Button, Table, Modal } from 'antd'
import {EditOutlined,DeleteOutlined,VerticalAlignTopOutlined,UploadOutlined,ExclamationCircleOutlined,
} from '@ant-design/icons'
import axios from 'axios'
import { data } from 'react-router-dom'
import { render } from 'nprogress'
import { useNavigate } from 'react-router-dom'
import { notification } from 'antd'const { confirm } = Modalfunction NewsDraft(props) {const [dataSource, setdataSource] = useState([])const navigate = useNavigate()const { username } = JSON.parse(localStorage.getItem('token'))useEffect(() => {axios.get(`/news?author=${username}&auditState=0&_expand=category`).then((res) => {const list = res.datasetdataSource(list)})}, [username])const columns = [{title: 'ID',dataIndex: 'id',render: (id) => {return <b>{id}</b>},},{title: '新闻标题',dataIndex: 'title',render: (title, item) => {return <a href={`#/news-manage/preview/${item.id}`}>{title}</a>},},{title: '作者',dataIndex: 'author',},{title: '分类',dataIndex: 'category',render: (category) => {return category.title},},{title: '操作',render: (record) => {return (<div><Buttontype="primary"shape="circle"icon={<EditOutlined />}onClick={() => {navigate(`/news-manage/update/${record.id}`)}}/>{/* 如果没有配置权限,就不显示 */}<Buttondangertype="primary"shape="circle"icon={<DeleteOutlined />}onClick={() => confirmMethod(record)}/>{/* 在这添加提交事件 */}<Buttonshape="circle"icon={<UploadOutlined />}onClick={() => handleCheck(record.id)}/></div>)},},]//做补丁请求const handleCheck = (id) => {axios.patch(`/news/${id}`, {// 正在审核auditState: 1,}).then((res) => {navigate('/audit-manage/list')notification.info({message: `通知`,description: `您可以到审核列表查看您的新闻`,placement: 'bottomRight',})})}const confirmMethod = (record) => {confirm({title: 'Do you Want to delete these items?',icon: <ExclamationCircleOutlined />,onOk() {deleteMethod(record)},onCancel() {console.log('Cancel')},})console.log('确认删除')}const deleteMethod = (record) => {// console.log(record);setdataSource(dataSource.filter((data) => data.id !== record.id))axios.delete(`/news/${record.id}`)}return (<div><TabledataSource={dataSource}columns={columns}pagination={{//一页显示几条数据pageSize: 5,}}rowKey={(record) => record.id}/></div>)
}export default NewsDraft