We have finished the permission, let’s back to focus the busines of publish news.
use Header Basic to perform the title.
1 2 3 4 5 6 7 8
| import { PageHeader } from 'antd' ... <PageHeader className="site-page-header" onBack={() => null} title="Add news" subTitle="This is for adding news" />
|
and we use Step Basic for the progress.
1 2 3 4 5
| <Steps current={current}> <Step title="基本信息" description="新闻标题,新闻分类" /> <Step title="新闻内容" description="新闻主体内容" /> <Step title="新闻提交" description="保存草稿或者提交审核" /> </Steps>
|
chose Form Basic for the input and select button
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div style={{ marginTop: '50px' }}> <Form {...layout}> <Form.Item name="title" label="新闻标题" rules={[{ required: true, message: "Please input news title." }]}> <Input /> </Form.Item> <Form.Item name="categoryId" label="新闻分类" rules={[{ required: true }]}> <Select> { categoryList.map(item => { return <Option value={item.id} key={item.id}>{item.title}</Option> }) } </Select> </Form.Item> </Form> </div>
|
above we called step 1, next to step 2
2. Add news content.
use react draft editor for edit news content.
1 2 3
| yarn add react-draft-wysiwyg draft-js draftjs-to-html html-to-draftjs mkdir src/components/news-manage touch src/components/news-manage/NewsEditor.js
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { convertToRaw } from 'draft-js' import draftToHtml from 'draftjs-to-html'; import React, { useState } from 'react' import { Editor } from 'react-draft-wysiwyg' import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
export default function NewsEditor() { const [editorState, setEditorState] = useState("") return ( <div> <Editor editorState={editorState} toolbarClassName='toolbarClassName' wrapperClassName='wrapperClassName' editorClassName='editorClassName' onEditorStateChange={editorState => setEditorState(editorState)} onBlur={() => { console.log(draftToHtml(convertToRaw(editorState.getCurrentContent()))) }} /> </div> ) }
|
3. Add save in progress 3
use Notification Basic in step 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const handleSave = auditState => { axios.post('/api/news', { ...formInfo, "author": User.username, "region": User.region ? User.region : 'Global', "roleId": User.roleId, "auditState": auditState, "publishState": 0, "content": content, "createTime": Date.now(), "star": 0, "view": 0 }).then(res => { navigate(auditState === 0 ? '/news-manage/draft' : '/audit-manage/list'); notification.info({ message: `Notification`, description: `Please go to ${auditState === 0 ? "draft box" : "audit box"} for reviewing your news`, placement: 'bottomRight', }); }); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| import { Button, Table, Modal } from 'antd'; import { DeleteOutlined, EditOutlined, ExclamationCircleOutlined, UploadOutlined, } from '@ant-design/icons';
import axios from 'axios'; import React, { useEffect, useState } from 'react'
const { confirm } = Modal;
export default function NewsDraft() { const [dataSource, setDataSource] = useState([]);
const { username } = JSON.parse(localStorage.getItem('token'));
const columns = [ { title: 'ID', dataIndex: 'id', render: (id) => <b>{id}</b> }, { title: '新闻标题', dataIndex: 'title', }, { title: '新闻分类', dataIndex: 'category', render: category => { return category.title } }, { title: '作者', dataIndex: 'author', }, { title: '操作', render: item => { return <div> <Button shape='circle' icon={<DeleteOutlined />} onClick={() => confirmDelete(item)} /> <Button type='primary' shape='circle' icon={<EditOutlined />} /> <Button danger shape='circle' icon={<UploadOutlined />} />
</div> } }, ];
const confirmDelete = item => { confirm({ title: 'Do you Want to delete this item?', icon: <ExclamationCircleOutlined />, onOk() { realDelete(item) }, onCancel() { console.log('Cancel'); }, }); }
const realDelete = (item) => { setDataSource(dataSource.filter(data => data.id !== item.id)); axios.delete(`/api/news/${item.id}`); }
useEffect(() => { axios.get(`/api/news?_expand=category&author=${username}&auditState=0`).then(res => { setDataSource(res.data); }) }, [username]);
return ( <div> <Table dataSource={dataSource} columns={columns} pagination={{ pageSize: 5 }} rowKey={item => item.id} />; </div> ) }
|
support news preview function.
1 2
| touch src/views/sandbox/news-manage/NewsPreview.js yarn add moment
|
moment is used for format DateTime.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import { PageHeader, Descriptions } from 'antd' import axios from 'axios'; import moment from 'moment'; import React, { useEffect, useState } from 'react' import { useParams } from 'react-router';
export default function NewsPreview() { const [newsInfo, setNewsInfo] = useState(null);
const params = useParams();
useEffect(() => { axios.get(`/api/news/${params.id}?_expand=category&_expand=role`).then(res => { console.log(res.data); setNewsInfo(res.data); }) }, [params.id]);
const auditList = ['未审核', '审核中', '已通过', '未通过']; const publishList = ['未发布', '待发布', '已发布', '已下线'];
return ( newsInfo && <div> <PageHeader onBack={() => window.history.back()} title={newsInfo.title} subTitle={newsInfo.category?.title}> <Descriptions size="small" column={3}> <Descriptions.Item label="创建者">{newsInfo.author}</Descriptions.Item> <Descriptions.Item label="创建时间">{moment(newsInfo.createTime).format("YYYY/MM/DD HH:mm:ss")}</Descriptions.Item> <Descriptions.Item label="发布时间">{newsInfo.publishTime ? moment(newsInfo.publishTime).format("YYYY/MM/DD HH:mm:ss") : "-"}</Descriptions.Item> <Descriptions.Item label="区域">{newsInfo.region}</Descriptions.Item> <Descriptions.Item label="审核状态"><span style={{ color: "red" }}>{auditList[newsInfo.auditState]}</span></Descriptions.Item> <Descriptions.Item label="发布状态"><span style={{ color: "red" }}>{publishList[newsInfo.publishState]}</span></Descriptions.Item> <Descriptions.Item label="访问数量">{newsInfo.view}</Descriptions.Item> <Descriptions.Item label="点赞数量">{newsInfo.star}</Descriptions.Item> <Descriptions.Item label="评论数量">0</Descriptions.Item> </Descriptions> </PageHeader> <div dangerouslySetInnerHTML={{ __html: newsInfo.content }} style={{ margin: "0 24px", border: "1px solid gray" }}> </div> </div> ) }
|
support edit news
1
| touch src/views/sandbox/news-manage/NewsUpdate.js
|
most of code can copy from NewsAdd.js and modify to as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
| import { PageHeader, Steps, Button, Form, Input, Select, message, notification } from 'antd' import axios from 'axios'; import React, { useEffect, useState, useRef } from 'react' import { useNavigate, useParams } from 'react-router'; import NewsEditor from '../../../components/news-manage/NewsEditor'; import style from './News.module.css' const { Step } = Steps; const { Option } = Select;
export default function NewsUpdate() { const layout = { labelCol: { span: 4 }, wrapperCol: { span: 20 }, };
const User = JSON.parse(localStorage.getItem('token'));
const [current, setCurrent] = useState(0); const [categoryList, setCategoryList] = useState([]); const [formInfo, setFormInfo] = useState({}); const [content, setContent] = useState('')
const newsForm = useRef(null); const navigate = useNavigate(); const params = useParams();
const handlePrevious = () => { setCurrent(current - 1); }
const handleNext = () => { 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(`News content can not be empty!`); } else { setCurrent(current + 1); } } }
const handleSave = auditState => { axios.patch(`/api/news/${params.id}`, { ...formInfo, "auditState": auditState, "content": content, }).then(res => { navigate(auditState === 0 ? '/news-manage/draft' : '/audit-manage/list'); notification.info({ message: `Notification`, description: `Please go to ${auditState === 0 ? "draft box" : "audit box"} for reviewing your news`, placement: 'bottomRight', }); }); }
useEffect(() => { axios.get(`/api/categories`).then(res => { setCategoryList(res.data); }) }, []);
useEffect(() => { axios.get(`/api/news/${params.id}?_expand=category&_expand=role`).then(res => { const { title, categoryId, content } = res.data;
newsForm.current.setFieldsValue({ title, categoryId }); setContent(content); }) }, [params.id]);
return ( <div> <PageHeader className="site-page-header" onBack={() => navigate('/news-manage/draft')} title="Update news" subTitle="This is for update news" /> <Steps current={current}> <Step title="基本信息" description="新闻标题,新闻分类" /> <Step title="新闻内容" description="新闻主体内容" /> <Step title="新闻提交" description="保存草稿或者提交审核" /> </Steps> <div style={{ marginTop: '50px' }}> <div className={current === 0 ? '' : style.active}> <Form {...layout} name='basic' ref={newsForm}> <Form.Item name="title" label="新闻标题" rules={[{ required: true, message: "Please input news title." }]}> <Input /> </Form.Item> <Form.Item name="categoryId" label="新闻分类" rules={[{ required: true }]}> <Select> { categoryList.map(item => { return <Option value={item.id} key={item.id}>{item.title}</Option> }) } </Select> </Form.Item> </Form> </div> </div>
<div className={current === 1 ? '' : style.active}> <NewsEditor getContent={value => { // console.log(value); setContent(value); }} content={content}></NewsEditor> </div>
<div className={current === 2 ? '' : style.active}> </div>
<div style={{ marginTop: '50px' }}> {current > 0 && <Button onClick={handlePrevious}> 上一步</Button>} {current < 2 && <Button type='primary' onClick={handleNext}> 下一步</Button>} { current === 2 && <sapn> <Button type='primary' onClick={() => handleSave(0)}>保存草稿箱</Button> <Button danger onClick={() => handleSave(1)}>提交审核</Button> </sapn> } </div> </div> ) }
|
and the NewsEditor looks like below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import { ContentState, convertToRaw, EditorState } from 'draft-js' import draftToHtml from 'draftjs-to-html'; import htmlToDraft from 'html-to-draftjs'; import React, { useEffect, useState } from 'react' import { Editor } from 'react-draft-wysiwyg' import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
export default function NewsEditor(props) { const [editorState, setEditorState] = useState("");
useEffect(() => { const html = props.content;
if (html === undefined) { return; }
const contentBlock = htmlToDraft(html); if (contentBlock) { const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks); const editorState = EditorState.createWithContent(contentState); setEditorState(editorState); } }, [props.content]);
return ( <div> <Editor editorState={editorState} toolbarClassName='toolbarClassName' wrapperClassName='wrapperClassName' editorClassName='editorClassName' onEditorStateChange={editorState => setEditorState(editorState)} onBlur={() => { props.getContent(draftToHtml(convertToRaw(editorState.getCurrentContent()))); }} /> </div> ) }
|
support Audit feature. bind a handleAudit event then fine for supporting this feature.
1 2 3 4 5 6 7 8 9 10 11 12 13
| const handleAudit = id => { axios.patch(`/api/news/${id}`, { auditState: 1 }).then(res => { navigate('/audit-manage/list'); notification.info({ message: `Notification`, description: `Please go to audit box for checking your news`, placement: 'bottomRight', }); }); }
|