菜单网址,https://ant.design/components/menu-cn/
Menu 菜单组件
Menu.Item 菜单项
修改src/index.js导航菜单
import React from 'react';
import ReactDom from 'react-dom';
import {Route,Link,BrowserRouter as Router} from "react-router-dom";
import Login from "./component/login";
import Reg from "./component/reg";
import {Menu} from 'antd'; //加载antd的组件
import 'antd/lib/menu/style'; //加载组件的样式css
import { HomeFilled,LoginOutlined,RotateRightOutlined,AccountBookFilled } from '@ant-design/icons';
const App = () => (
<Router>
<div>
<div>
<Menu mode="horizontal" theme="dark" style={{ lineHeight: '64px' }} defaultSelectedKeys= {['home']} >
<Menu.Item key="home" icon={<HomeFilled />} ><Link to="/">主页</Link></Menu.Item>
<Menu.Item key="login" icon={<LoginOutlined />}><Link to="/login">登录</Link></Menu.Item>
<Menu.Item key="reg" icon={<RotateRightOutlined />}><Link to="/reg">注册</Link></Menu.Item>
<Menu.Item key="about" icon={<AccountBookFilled />}><Link to="/about">关于</Link></Menu.Item>
</Menu>
</div>
<div>
<Route path="/about" component={About} />
<Route path="/login" component={Login} />
<Route path="/reg" component={Reg} />
<Route exact path="/" component={Home} />
</div>
</div>
</Router>
);
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function About() {
return (
<div>
<h2>About</h2>
</div>
);
}
ReactDom.render(<App />, document.getElementById('root'));
import React from 'react';
import ReactDom from 'react-dom';
import {Route,Link,BrowserRouter as Router} from "react-router-dom";
import {Menu,Layout} from 'antd'; //加载antd的组件
import Login from "./component/login";
import Reg from "./component/reg";
import 'antd/lib/menu/style'; //加载组件的样式css
import 'antd/lib/layout/style';
import { HomeFilled,LoginOutlined,RotateRightOutlined,AccountBookFilled } from '@ant-design/icons';
const {Header,Content,Footer} = Layout; // 上中下
const App = () => (
<Router>
<Layout>
<Header>
<Menu mode="horizontal" theme="dark" style={{ lineHeight: '64px' }} defaultSelectedKeys= {['home']} >
<Menu.Item key="home" icon={<HomeFilled />} ><Link to="/">主页</Link></Menu.Item>
<Menu.Item key="login" icon={<LoginOutlined />}><Link to="/login">登录</Link></Menu.Item>
<Menu.Item key="reg" icon={<RotateRightOutlined />}><Link to="/reg">注册</Link></Menu.Item>
<Menu.Item key="about" icon={<AccountBookFilled />}><Link to="/about">关于</Link></Menu.Item>
</Menu>
</Header>
<Content style={{ padding: '8px 50px' }}>
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
<Route path="/about" component={About} />
<Route path="/login" component={Login} />
<Route path="/reg" component={Reg} />
<Route exact path="/" component={Home} />
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>xddweb©2019-2020 </Footer>
</Layout>
</Router>
);
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function About() {
return (
<div>
<h2>About</h2>
</div>
);
}
ReactDom.render(<App />, document.getElementById('root'));
url | method | 说明 |
---|---|---|
/posts/ | POST | 提交博文的title、content,成功返回json,包含post_id,title |
/posts/id | GET | 返回博文详情 返回json,id,title、author_id、postdate(时间戳)、content |
/posts/ | GET | 返回博文标题列表,分页 |
import axios from 'axios';
import {observable} from 'mobx';
class PostService{
@observable msg = '';
pub(title,content){
console.log(title);
axios.post('/api/posts/',{
title,content
})/* dev server 会代理 */
.then(
response => {
console.log(response.data);
console.log(response.status);
this.msg = '博文提交成功';
}
).catch(
error => {
console.log(error);
this.msg = '博文提交失败'; //+信息显示
}
)
}
}
const postService = new PostService();
export {postService};
使用Form组件,https://ant.design/components/form-cn/
创建component/pub.js文件
// component/pub.js
import React from 'react';
import {observer} from 'mobx-react';
import {Form,Icon,Input,Button,message} from 'antd';
import {inject} from '../utils';
import {postService as service} from '../service/post';
import 'antd/lib/form/style';
import 'antd/lib/icon/style';
import 'antd/lib/input/style';
import 'antd/lib/button/style';
import 'antd/lib/message/style';
const FormItem = Form.Item;
const { TextArea } = Input;
@inject({service})
@observer
export default class Pub extends React.Component {
handleSubmit(event){
event.preventDefault();
const [ title,content ] = event.target;// event.target返回form,而form是表单控件的数组
this.props.service.pub(title.value,content.value);
}
render(){
let msg = this.props.service.msg;
return (
<Form layout="vertical" onSubmitCapture={this.handleSubmit.bind(this)}>
<Form.Item label="标题" labelCol={{span:4}} wrapperCol={{span:14}} >
<Input placeholder="标题" />
</Form.Item>
<Form.Item label="内容" labelCol={{span:4}} wrapperCol={{span:14}} >
<TextArea rows={10} />
</Form.Item>
<FormItem wrapperCol={{span:14,offset:4}}>
<Button type="primary" htmlType="submit" >提交</Button>
</FormItem>
</Form>
)
}
componentDidUpdate(prevProps,prevState){ //+ 渲染后显示消息组件
if (prevProps.service.msg){
message.info(prevProps.service.msg,3,setTimeout(() => prevProps.service.msg = '',1000))
}
}
}
https://github.com/margox/braft-editor
安装插件
使用yarn安装最新版本
$ yarn add braft-editor
安装指定版本
$ yarn add [email protected]
安装2.x.x最高版本
$ yarn add braft-editor@^2.3.1
使用npm安装
$ npm install braft-editor --save
组件使用
import BraftEditor from 'braft-editor';
import 'braft-editor/dist/index.css';
参考https://braft.margox.cn/demos/basic
修改component/pub.js文件
import React from 'react';
import {observer} from 'mobx-react';
import {Form,Icon,Input,Button,message} from 'antd';
import {inject} from '../utils';
import {postService as service} from '../service/post';
import BraftEditor from 'braft-editor';
import 'braft-editor/dist/index.css'
import 'antd/lib/form/style';
import 'antd/lib/icon/style';
import 'antd/lib/input/style';
import 'antd/lib/button/style';
import 'antd/lib/message/style';
const FormItem = Form.Item;
const { TextArea } = Input;
@inject({service})
@observer
export default class Pub extends React.Component {
//创建一个state,关联组件和组件输出的HTML内容
state = {
editorState : BraftEditor.createEditorState(''),
outputHTML : ""
}
handleSubmit(event){
event.preventDefault();
const [ title,content ] = event.target;// event.target返回form,而form是表单控件的数组
console.log(title,content);
const {outputHTML = ''} = this.state;
console.log(outputHTML);
// this.props.service.pub(title.value,content.value);
this.props.service.pub(title.value,outputHTML);
}
// 组件内容变化更新state,引发渲染
handleChange = (editorState) =>{
this.setState({
editorState:editorState,
outputHTML:editorState.toHTML()
});
}
render(){
let msg = this.props.service.msg;
// 排除按钮
const excludeControls = [
'letter-spacing',
'line-height',
'clear',
'headings',
'list-ol',
'list-ul',
'remove-styles',
'subscript',
'hr',
'text-align'
];
const {editorState,outputHTML} = this.state;
return (
<Form layout="vertical" onSubmitCapture={this.handleSubmit.bind(this)}>
<Form.Item label="标题" labelCol={{span:4}} wrapperCol={{span:14}} >
<Input placeholder="标题" />
</Form.Item>
<Form.Item label="内容" labelCol={{span:4}} wrapperCol={{span:14}} >
<BraftEditor value={editorState} excludeControls={excludeControls}
onChange = {this.handleChange.bind(this)} contentStyle={{height:400}} />
</Form.Item>
<FormItem wrapperCol={{span:14,offset:4}}>
<Button type="primary" htmlType="submit" >提交</Button>
</FormItem>
</Form>
)
}
componentDidUpdate(prevProps,prevState){ //+ 渲染后显示消息组件
if (prevProps.service.msg){
message.info(prevProps.service.msg,3,setTimeout(() => prevProps.service.msg = '',1000))
}
}
}
header中的jwt,由于与后台Django Server通信,身份认证需要jwt,这个要放到request header中。使用axios的API axios.post(url[data,config])
可以使用第三个参数config。config是一个对象,字典中设置headers字段,该字段的值依然是对象,都是键值对形式。
修改service/post.js文件
// service/post.js
import axios from 'axios';
import {observable} from 'mobx';
import store from 'store';
import expire from 'store/plugins/expire';
store.addPlugin(expire)
class PostService{
@observable msg = '';
constructor(){
this.axios = axios.create({
baseURL: '/api/posts'
})
}
getJwt(){
return store.get("token",'')
}
pub(title,content){
console.log(title);
axios.post('/api/posts/',{
title,content
},{
headers:{'jwt': this.getJwt()}
})/* dev server 会代理 */
.then(
response => {
console.log(response.data);
console.log(response.status);
this.msg = '博文提交成功';
}
).catch(
error => {
console.log(error);
this.msg = '博文提交失败'; //+信息显示
}
)
}
}
const postService = new PostService();
export {postService};
index.jsp
中增加如下代码import Post from "./component/post"; //详情页
<Route path="/post/:id" component={Post} />
yarn add moment
。// component/post.js
import React from 'react';
import { observer } from 'mobx-react';
import {message,Card,Empty} from 'antd';
import {inject} from '../utils';
import {postService as service} from '../service/post';
import moment from 'moment';
import 'antd/lib/message/style';
import 'antd/lib/card/style';
import 'antd/lib/empty/style';
@inject({service})
@observer
export default class Post extends React.Component {
constructor(props){
super(props);
let {id = -1} = props.match.params;
console.log(id);
this.props.service.getPost(id);
}
render(){
let msg = this.props.service.msg;
console.log(this.props.service.post);
const {title = "", content = "",author,postdate} = this.props.service.post;
if (title){
return (
<Card title={title} extra={<a href="#">{author}</a>} >
<p>{moment(postdate * 1000).format("YYYY-MM-DD hh:mm:ss")}</p>
<p dangerouslySetInnerHTML={{__html:content}}></p>
</Card>
);
}
else {
return <Empty />;
}
}
componentDidUpdate(prevProps,prevState){ //+ 渲染后显示消息组件
if (prevProps.service.msg){
message.info(prevProps.service.msg,3,()=> prevProps.service.msg = '');
}
}
}
如果使用了富文本编辑器,那么显示的时候,发现不能按照网页标签显示。原因是为了安全,防止xss攻击,React不允许直接按照HTML显示。
使用dangerouslySetInnerHTML属性,这个名字提醒使用者很危险。
<p>{content}</p>
修改为
<p dangerouslySetInnerHTML={{__html:content}}> </p>
修改service/post.js代码如下
// service/post.js
import axios from 'axios';
import {observable} from 'mobx';
import store from 'store';
import expire from 'store/plugins/expire';
store.addPlugin(expire)
class PostService{
@observable msg = '';
@observable post = {}; //文章
constructor(){
this.axios = axios.create({
baseURL: '/api/posts/'
})
}
getJwt(){
return store.get("token",'')
}
pub(title,content){
console.log(title);
axios.post('/api/posts/',{
title,content
},{
headers:{'jwt': this.getJwt()}
})/* dev server 会代理 */
.then(
response => {
console.log(response.data);
console.log(response.status);
this.msg = '博文提交成功';
}
).catch(
error => {
console.log(error);
this.msg = '博文提交失败'; //+信息显示
}
)
}
getPost(id){
this.axios.get(id).then(
response => { //此函数要注意this的问题
this.post = response.data.post;
}
).catch(
error => {
console.log(error);
this.post = {}
this.msg = "文章加载失败"; //信息显示
}
)
}
}
const postService = new PostService();
export {postService};
// service/index.js
import React from 'react';
import ReactDom from 'react-dom';
import {Route,Link,BrowserRouter as Router} from "react-router-dom";
import {Menu,Layout} from 'antd'; //加载antd的组件
import Login from "./component/login";
import Reg from "./component/reg";
import Pub from "./component/pub"; // 发布页
import Post from "./component/post"; //详情页
import L from './component/list'; //列表页
import 'antd/lib/menu/style'; //加载组件的样式css
import 'antd/lib/layout/style';
import { HomeFilled,LoginOutlined,RotateRightOutlined,AccountBookFilled,BarcodeOutlined,PullRequestOutlined} from '@ant-design/icons';
const {Header,Content,Footer} = Layout; // 上中下
const App = () => (
<Router>
<Layout>
<Header>
<Menu mode="horizontal" theme="dark" style={{ lineHeight: '64px' }} defaultSelectedKeys= {['home']} >
<Menu.Item key="home" icon={<HomeFilled />} ><Link to="/">主页</Link></Menu.Item>
<Menu.Item key="login" icon={<LoginOutlined />}><Link to="/login">登录</Link></Menu.Item>
<Menu.Item key="reg" icon={<RotateRightOutlined />}><Link to="/reg">注册</Link></Menu.Item>
<Menu.Item key="pub" icon={<PullRequestOutlined />}><Link to="/pub">发布</Link></Menu.Item>
<Menu.Item key="bars" icon={<BarcodeOutlined />}><Link to="/list">文章列表</Link></Menu.Item>
<Menu.Item key="about" icon={<AccountBookFilled />}><Link to="/about">关于</Link></Menu.Item>
</Menu>
</Header>
<Content style={{ padding: '8px 50px' }}>
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
<Route path="/about" component={About} />
<Route path="/login" component={Login} />
<Route path="/reg" component={Reg} />
<Route path="/pub" component={Pub} />
<Route path="/list" component={L} />
<Route path="/posts/:id" component={Post} />
<Route exact path="/" component={Home} />
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>xddweb©2019-2020 </Footer>
</Layout>
</Router>
);
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function About() {
return (
<div>
<h2>About</h2>
</div>
);
}
ReactDom.render(<App />, document.getElementById('root'));
"antd":"^3.1.5"
。然后$ npm update
,更新成功后,就可以使用List组件了。或者$yarn upgrade antd@^3.1.5
.// component/list.js
import React from 'react';
import {observer} from 'mobx-react';
import {message,List,Empty} from 'antd';
import {inject} from '../utils';
import {postService as service} from '../service/post';
import {Link} from 'react-router-dom';
import 'antd/lib/message/style';
import 'antd/lib/list/style';
@inject({service})
@observer
export default class L extends React.Component{
constructor(props) {
super(props);
props.service.list();
}
render() {
const {posts:data=[],pagination} = this.props.service.posts;
if (data.length){
return (
<List
header={<div>博文列表</div>}
footer={<div>Footer</div>}
bordered
dataSource = {data}
renderItem={item => (
<List.Item>
<Link to={'/posts/'+item.id}>{item.title}</Link>
</List.Item>
)}
/>
)
}
return <Empty />;
}
}
<List bordered dataSource = {data}
renderItem={item => (
<List.Item>
<Link to={'/posts/'+item.id}>{item.title}</Link>
</List.Item>
)}
/>
<Link to={'/post/' + item.id}>{item.title} </Link> 这是详情页的链接
import axios from 'axios';
import {observable} from 'mobx';
import store from 'store';
import expire from 'store/plugins/expire';
store.addPlugin(expire)
class PostService{
@observable msg = '';
@observable post = {}; //文章
@observable posts = {'posts':[],'pagination':{page:1,size:20,pages:0,total:0}}; //列表
constructor(){
this.axios = axios.create({
baseURL: '/api/posts/'
})
}
getJwt(){
return store.get("token",'')
}
pub(title,content){
console.log(title);
axios.post('/api/posts/',{
title,content
},{
headers:{'jwt': this.getJwt()}
})/* dev server 会代理 */
.then(
response => {
console.log(response.data);
console.log(response.status);
this.msg = '博文提交成功';
}
).catch(
error => {
console.log(error);
this.msg = '博文提交失败'; //+信息显示
}
)
}
getPost(id){
this.axios.get(id).then(
response => { //此函数要注意this的问题
this.post = response.data.post;
}
).catch(
error => {
console.log(error);
this.post = {}
this.msg = "文章加载失败"; //信息显示
}
)
}
list(){
this.axios.get().then(
response => { //注意此函数的this问题
this.posts = response.data;
}
).catch(
error =>{
console.log(error);
this.post = {}
this.msg = '文章列表加载失败';// 信息显示
}
)
}
}
const postService = new PostService();
export {postService};
分页使用了Pagination组件,在L组件的render函数的List组件中使用pagination属性,这个属性放入一个pagination对象,有如下属性
(pageNo,pageSize)=>{}
,即切换是获得当前页码和页内行数。可参考https://ant.design/components/list-cn/#components-list-demo-vertical
修改component/list.js代码如下:
// component/list.js
import React from 'react';
import {observer} from 'mobx-react';
import {message,List,Empty} from 'antd';
import {inject} from '../utils';
import {postService as service} from '../service/post';
import {Link} from 'react-router-dom';
import 'antd/lib/message/style';
import 'antd/lib/list/style';
@inject({service})
@observer
export default class L extends React.Component{
constructor(props) {
super(props);
props.service.list();
}
onChange(pageNumber){
console.log(pageNumber);
this.props.service.list(pageNumber);
}
render() {
const {posts:data=[],pagination} = this.props.service.posts;
if (data.length){
const {page:current=1,total ,size:pageSize} = pagination;
console.log(current,total,pageSize);
return (
<List
header={<div>博文列表</div>}
bordered
dataSource = {data}
renderItem={item => (
<List.Item>
<Link to={'/posts/'+item.id}>{item.title}</Link>
</List.Item>
)}
pagination={{
current,
total,
pageSize,
onChange:this.onChange.bind(this)
}}
/>
)
}
return <Empty />;
}
}
import axios from 'axios';
import {observable} from 'mobx';
import store from 'store';
import expire from 'store/plugins/expire';
store.addPlugin(expire)
class PostService{
@observable msg = '';
@observable post = {}; //文章
@observable posts = {'posts':[],'pagination':{page:1,size:5,pages:0,total:0}}; //列表
constructor(){
this.axios = axios.create({
baseURL: '/api/posts/'
})
}
getJwt(){
return store.get("token",'')
}
pub(title,content){
console.log(title);
axios.post('/api/posts/',{
title,content
},{
headers:{'jwt': this.getJwt()}
})/* dev server 会代理 */
.then(
response => {
console.log(response.data);
console.log(response.status);
this.msg = '博文提交成功';
}
).catch(
error => {
console.log(error);
this.msg = '博文提交失败'; //+信息显示
}
)
}
getPost(id){
this.axios.get(id).then(
response => { //此函数要注意this的问题
this.post = response.data.post;
}
).catch(
error => {
console.log(error);
this.post = {}
this.msg = "文章加载失败"; //信息显示
}
)
}
list(page=1,size=2){
this.axios.get(`?page=${page}&size=${size}`).then(
response => { //注意此函数的this问题
this.posts = response.data;
}
).catch(
error =>{
console.log(error);
this.post = {}
this.msg = '文章列表加载失败';// 信息显示
}
)
}
}
const postService = new PostService();
export {postService};
import {LocaleProvider} from 'antd';
import moment from 'moment';
import zh_CN from 'antd/lib/locale-provider/zh_CN'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
ReactDom.render(<LocaleProvider locale={zh_CN}><App /></LocaleProvider>, document.getElementById('root'));