感谢无私开源的程序员们~~~代码因为你们更加美腻~

//根index.js
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import './index.js';
import App from './App';
import { GlobalStyle, GithubMarkdownCss, Icon } from './style.js' // 添加全局样式
import Toast from 'react-toast-mobile';
import 'react-toast-mobile/lib/react-toast-mobile.css';
import * as serviceWorker from './serviceWorker'; const Apps = () => {
return (
<Fragment>
<Toast />
<GlobalStyle />
<Icon/>
<GithubMarkdownCss />
<App />
</Fragment>
)
} ReactDOM.render(<Apps />, document.getElementById('root')); // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
//app.js
import React, { Component, Fragment } from 'react';
//引入redux
import { Provider } from 'react-redux'
//store
import store from './store'
import { BrowserRouter, Route, Switch } from 'react-router-dom'; // BrowserRouter HashRouter
//对应一些页面
import Topic from './pages/topic'
import Detail from './pages/detail'
import User from './pages/user'
import Login from './common/login'
import Create from './pages/create'
import Mine from './pages/mine'
import Message from './pages/message'
import ErrorPage from './common/errorPage'
import Auth from './common/auth' class App extends Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Fragment>
<Switch>
<Route path="/" exact component={Topic}></Route>
<Route path="/detail/:id" component={Detail}></Route>
<Route path="/user/:id" component={User}></Route>
<Route path="/login" component={Login}></Route>
<Auth path="/create" component={Create}></Auth>
<Auth path="/mine" component={Mine}></Auth>
<Auth path="/message" component={Message}></Auth>
<Route path="/404" exact component={ErrorPage}/>
<Route path="*" component={ ErrorPage } />
</Switch>
</Fragment>
</BrowserRouter>
</Provider >
);
}
} export default App;

封装的时间

//index.js
export function formatDate(str) {
var date = new Date(str);
var time = new Date().getTime() - date.getTime(); //现在的时间-传入的时间 = 相差的时间(单位 = 毫秒)
if (time < 0) {
return '';
} else if (time / 1000 < 60) {
return '刚刚';
} else if ((time / 60000) < 60) {
return parseInt((time / 60000)) + ' 分钟前';
} else if ((time / 3600000) < 24) {
return parseInt(time / 3600000) + ' 小时前';
} else if ((time / 86400000) < 31) {
return parseInt(time / 86400000) + ' 天前';
} else if ((time / 2592000000) < 12) {
return parseInt(time / 2592000000) + ' 月前';
} else {
return parseInt(time / 31536000000) + ' 年前';
}
}
//http.js
import axios from 'axios';
import qs from "qs";
import { T } from 'react-toast-mobile'; // axios 配置
axios.defaults.timeout = 10000;
axios.defaults.baseURL = 'https://cnodejs.org/api/v1' // http request 拦截器
axios.interceptors.request.use(config => {
T.loading()
let user = localStorage.user
if (config.method === 'post') {
config.data = qs.stringify(config.data)
if (user) {
config.data = config.data + `&accesstoken=${JSON.parse(user).accesstoken}`
}
}
if (config.method === 'get') {
if (user) {
config.params = Object.assign(config.params, { accesstoken: JSON.parse(user).accesstoken })
}
}
return config
},
err => {
return Promise.reject(err);
}); // http response 拦截器
axios.interceptors.response.use(response => {
T.loaded()
return response;
},
error => {
T.loaded()
return Promise.reject(error)
}); export default axios;

redux用的是

redux-thunk

//index.js
import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk' // 默认action只能是对象,thunk能让action是一个函数 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, composeEnhancers(
applyMiddleware(thunk)
)) export default store
//reducer.js
import { combineReducers } from 'redux-immutable'
import { reducer as header } from './../pages/header/store'
import { reducer as topic } from './../pages/topic/store'
import { reducer as detail } from './../pages/detail/store'
import { reducer as user } from './../pages/user/store'
import { reducer as login } from './../common/login/store'
import { reducer as create } from './../pages/create/store'
import { reducer as message } from './../pages/message/store'
import { reducer as replies } from './../pages/replies/store' const reducer = combineReducers({
header,
topic,
detail,
user,
login,
create,
message,
replies
}) export default reducer

store的用法,每一处是在对于的组件页面中使用的,没有抽出来

//reducer.js
import { actionTypes } from './index'
import { fromJS } from 'immutable' // '全部','精华','分享','问答','招聘'
const defaultState = fromJS({
navList: [
{
type: 'all',
text: '全部'
},
{
type: 'good',
text: '精华'
},
{
type: 'share',
text: '分享'
},
{
type: 'ask',
text: '问答'
},
{
type: 'job',
text: '招聘'
},
],
tab: 'all',
}) const reducer = (state = defaultState, action) => {
switch (action.type) {
case actionTypes.CHANGE_TAB:
return state.set('tab', action.data)
default:
return state
}
} export default reducer
//header/index.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { HeaderWrapper, NavList, NavItem } from "./style";
import { actionCreators } from "./store";
import { actionCreators as topicActionCreators } from "./../topic/store";
import { Link } from "react-router-dom"; class Header extends Component {
render() {
let { navList, changeTab } = this.props;
let newNavList = navList.toJS();
return (
<HeaderWrapper>
<NavList>
{newNavList.map(it => {
return (
<NavItem key={it.type} onClick={() => changeTab(it.type)}>
<Link to={"/?tab=" + it.type}>{it.text}</Link>
</NavItem>
);
})}
</NavList>
</HeaderWrapper>
);
}
} const mapState = state => {
return {
navList: state.getIn(["header", "navList"])
};
}; const mapDispatch = dispatch => {
return {
changeTab(type) {
// let page = 1, limit = 15
dispatch(topicActionCreators.clearTopicList([]));
dispatch(topicActionCreators.changePage(1));
dispatch(actionCreators.changeTab(type));
// dispatch(topicActionCreators.getTopic(page, limit, type))
}
};
}; export default connect(
mapState,
mapDispatch
)(Header);

//index.js
import React, { Component } from "react";
import { connect } from "react-redux";
import Footer from "./../../common/footer";
import TopNav from "./../../common/topnav";
import {
MessageWrapper,
MessageList,
MessageItem,
MessageItemLeft,
MessageItemRight,
MessageNothing
} from "./style";
import { actionCreators } from "./store";
import { formatDate } from "./../../utils";
import { Link } from "react-router-dom"; class Message extends Component {
render() {
let { messageList } = this.props;
let newMessageList = messageList.toJS();
if (JSON.stringify(newMessageList) === "{}") return null;
return (
<MessageWrapper>
<TopNav title={"消息"} />
{newMessageList.has_read_messages.length ||
newMessageList.hasnot_read_messages.length ? (
<MessageList>
{newMessageList.hasnot_read_messages.map((it, index) => {
return (
<MessageItem key={index}>
<MessageItemLeft>
<Link to={"/user/" + it.author.loginname}>
<img src={it.author.avatar_url} alt="" />
</Link>
</MessageItemLeft>
<MessageItemRight>
<div className="item-hd">
<span className="name">
<Link to={"/user/" + it.author.loginname}>
{it.author.loginname}
</Link>
</span>
<span className="time">
{formatDate(it.reply.create_at)}
</span>
</div>
<div className="item-bd">
在话题{" "}
<Link to={"/detail/" + it.topic.id}>
{it.topic.title}
</Link>
回复了你
</div>
</MessageItemRight>
</MessageItem>
);
})}
{newMessageList.has_read_messages.map((it, index) => {
return (
<MessageItem key={index}>
<MessageItemLeft>
<Link to={"/user/" + it.author.loginname}>
<img src={it.author.avatar_url} alt="" />
</Link>
</MessageItemLeft>
<MessageItemRight>
<div className="item-hd">
<span className="name">
<Link to={"/user/" + it.author.loginname}>
{it.author.loginname}
</Link>
</span>
<span className="time">
{formatDate(it.reply.create_at)}
</span>
</div>
<div className="item-bd">
回复了你的话题{" "}
<Link to={"/detail/" + it.topic.id}>
{it.topic.title}
</Link>
</div>
</MessageItemRight>
</MessageItem>
);
})}
</MessageList>
) : (
<MessageNothing>暂无消息</MessageNothing>
)}
<Footer />
</MessageWrapper>
);
}
componentDidMount() {
let loginState = localStorage.user;
if (this.props.isLogined || loginState) {
this.props.getMessage();
}
}
} const mapState = state => {
return {
messageList: state.getIn(["message", "messageList"]),
isLogined: state.getIn(["login", "isLogined"])
};
}; const mapDispatch = dispatch => {
return {
getMessage() {
dispatch(actionCreators.getMessageCount());
}
};
}; export default connect(
mapState,
mapDispatch
)(Message);
//src/pages/replies/index.js
import React, { PureComponent, Fragment } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";
import { Link } from "react-router-dom";
import { RepliesWrapper, RepliesTextarea, RepliesButton } from "./style";
import { T } from "react-toast-mobile"; class Replies extends PureComponent {
render() {
let { handleConfirm, id, replyId, author } = this.props;
return (
<RepliesWrapper>
{localStorage.user ? (
<Fragment>
<RepliesTextarea
ref={textarea => {
this.content = textarea;
}}
placeholder={author ? "@" + author : "请输入回复内容"}
/>
<RepliesButton
onClick={() => {
handleConfirm(id, replyId, author, this.content);
}}
>
回复
</RepliesButton>
</Fragment>
) : (
<div className="login">
你丫的先<Link to={"/login"}> 登录</Link> 才能发评论
</div>
)}
</RepliesWrapper>
);
}
} const mapDispatch = dispatch => {
return {
handleConfirm(id, replyId, author, content) {
if (content.value.length) {
if (replyId !== "") {
dispatch(
actionCreators.sendReplies(
id,
replyId,
`[@${author}](/user/${author}) ${content.value}`
)
);
} else {
dispatch(actionCreators.sendReplies(id, replyId, content.value));
}
content.value = "";
} else {
T.notify("回复内容不能为空");
}
}
};
}; export default connect(
null,
mapDispatch
)(Replies);
//src/common/footer/index.js
import React from "react";
import { NavLink as Link } from "react-router-dom";
import { FooterWrapper, FooterItem } from "./style"; const Footer = () => {
return (
<FooterWrapper>
<FooterItem>
<Link to={"/"} exact>
<i className="iconfont"></i>
<p>首页</p>
</Link>
</FooterItem>
<FooterItem>
<Link to={"/create"}>
<i className="iconfont"></i>
<p>发表</p>
</Link>
</FooterItem>
<FooterItem>
<Link to={"/message"}>
<i className="iconfont"></i>
<p>消息</p>
</Link>
</FooterItem>
<FooterItem>
<Link to={"/mine"}>
<i className="iconfont"></i>
<p>我的</p>
</Link>
</FooterItem>
</FooterWrapper>
);
};
export default Footer;

//src/common/loading/index.js
import React from "react";
import { Spinner, BounceTop, BounceBottom } from "./style"; const Loading = () => {
return (
<Spinner>
<BounceTop />
<BounceBottom />
</Spinner>
);
};
export default Loading;
//src/common/login/index.js
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";
import { Redirect } from "react-router-dom";
import { LoginWrapper, Input, Button, LoginBack } from "./style";
import TopNav from "./../topnav"; class Login extends PureComponent {
render() {
let { isLogined, path } = this.props;
let from = path ? { pathname: path } : { pathname: "/" };
console.log('...from',from);
if (isLogined) return <Redirect to={from} />;
return (
<LoginBack>
<TopNav title={"登录"} />
<LoginWrapper>
<Input
placeholder="accessToken"
ref={input => {
this.username = input;
}}
/>
<Button
onClick={() => {
this.props.login(this.username);
}}
>
登录
</Button>
</LoginWrapper>
</LoginBack>
);
}
} const mapState = state => {
return {
isLogined: state.getIn(["login", "isLogined"]),
path: state.getIn(["login", "path"])
};
}; const mapDispatch = dispatch => {
return {
login(usernameElem) {
dispatch(actionCreators.login(usernameElem.value));
}
};
}; export default connect(
mapState,
mapDispatch
)(Login);
//src/common/topnav/index.js
import React, { PureComponent, Fragment } from "react";
import { TopNavWarpper, Back } from "./style";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { actionCreators as loginActionCreators } from "./../login/store"; class TopNav extends PureComponent {
render() {
let loginState = localStorage.user;
return (
<Fragment>
<TopNavWarpper>
<Back onClick={() => this.goBack()}>
<i className="iconfont"></i>
</Back>
{this.props.match.path === "/mine" ? (
<span>个人中心</span>
) : (
<span>{this.props.title}</span>
)}
{this.props.match.path === "/mine" && loginState ? (
<span onClick={() => this.quite()}>
<i className="iconfont"></i>
</span>
) : (
<span />
)}
</TopNavWarpper>
</Fragment>
);
} goBack() {
this.props.history.goBack();
}
quite() {
localStorage.user = "";
this.props.history.push("/");
this.props.logout();
}
} const mapDispatch = dispatch => {
return {
logout() {
dispatch(loginActionCreators.isLogined(false));
}
};
}; export default connect(
null,
mapDispatch
)(withRouter(TopNav));

//src/pages/header/store/reducer.js
//根据type值加载数据
import { actionTypes } from './index'
import { fromJS } from 'immutable' // '全部','精华','分享','问答','招聘'
const defaultState = fromJS({
navList: [
{
type: 'all',
text: '全部'
},
{
type: 'good',
text: '精华'
},
{
type: 'share',
text: '分享'
},
{
type: 'ask',
text: '问答'
},
{
type: 'job',
text: '招聘'
},
],
tab: 'all',
}) const reducer = (state = defaultState, action) => {
switch (action.type) {
case actionTypes.CHANGE_TAB:
return state.set('tab', action.data)
default:
return state
}
} export default reducer

//src/pages/detail/index.js
import React, { PureComponent, Fragment } from "react";
import { connect } from "react-redux";
import {
MianWrapper,
MianContent,
MianTitle,
MianInfo,
ReplyWrapper,
ReplyContent,
ReplyList,
ReplyItem
} from "./style";
import { actionCreators } from "./store";
import { formatDate } from "./../../utils";
import { Link } from "react-router-dom";
import TopNav from "./../../common/topnav";
import Replies from "./../replies"; class Detail extends PureComponent {
constructor(props) {
super(props);
this.state = {
currIndex: -1,
tab: {
good: "精华",
share: "分享",
ask: "问答",
job: "招聘"
}
};
}
render() {
let { topicDetailList } = this.props;
let newList = topicDetailList.toJS();
// 导步加载数据时,newList转为空,render的时候去读一个空对象的属性时会报错,现提供如下解决方案
// 方法一
// let {title="",create_at = "" ,author = "",visit_count = 0 ,replies = [],tab='good',content=''} = newList
// 方法二
// if (JSON.stringify(newList) === "{}") return null;
// 方法三
// 用 && 操作符
return (
<Fragment>
<TopNav title={"详情"} />
<MianWrapper>
<MianTitle>{newList && newList.title}</MianTitle>
<MianInfo>
<span>发布于 {formatDate(newList && newList.create_at)}</span>
<span>
作者 {newList && newList.author && newList.author.loginname}
</span>
<span>阅读 {newList && newList.visit_count}</span>
<span>来自 {this.state.tab[newList && newList.tab]}</span>
</MianInfo>
<MianContent
className="markdown-body"
dangerouslySetInnerHTML={{ __html: newList && newList.content }}
/>
</MianWrapper>
<ReplyWrapper>
<ReplyContent>
全部回复({newList && newList.replies && newList.replies.length})
</ReplyContent>
{newList && newList.replies && newList.replies.length ? (
<ReplyList>
{newList &&
newList.replies &&
newList.replies.map((it, index) => {
return (
<ReplyItem key={index}>
<div className="replyAvuthor">
<Link to={"/user/" + it.author.loginname}>
<img src={it.author.avatar_url} alt="avatar_url" />
</Link>
</div>
<div className="replyContent">
<div className="content-hd">
<p>
<span className="name">
<Link to={"/user/" + it.author.loginname}>
{it.author.loginname}
</Link>
</span>
{formatDate(it.create_at)}
</p>
<p className="r">
<span
className="replies"
onClick={() => this.openReplies(index)}
>
{" "}
<i className="iconfont"></i>{" "}
</span>
<span className="num"># {index + 1}</span>
</p>
</div>
<p
className="markdown-body"
dangerouslySetInnerHTML={{ __html: it.content }}
/>
{this.state.currIndex === index ? (
<Replies
author={it.author.loginname}
id={newList.id}
replyId={it.id}
/>
) : null}
</div>
</ReplyItem>
);
})}
</ReplyList>
) : (
<p className="noReply">暂无回复</p>
)}
</ReplyWrapper>
<Replies id={newList.id} replyId={""} />
</Fragment>
);
}
componentDidMount() {
window.scrollTo(0, 0);
this.props.getTopicDetail(this.props.match.params.id);
} openReplies(index) {
this.setState(() => {
return {
currIndex: index
};
});
}
} const mapState = state => {
return {
topicDetailList: state.getIn(["detail", "topicDetailList"])
};
}; const mapDispatch = dispatch => {
return {
getTopicDetail(id) {
dispatch(actionCreators.getTopicDetail(id));
}
};
}; export default connect(
mapState,
mapDispatch
)(Detail);

感谢作者无私开源的精神感恩~!

react-cnode的更多相关文章

  1. 【原】小写了一个cnode的小程序

    小程序刚出来的第一天,朋友圈被刷屏了,所以趁周末也小玩了一下小程序.其实发觉搭建一个小程序不难,只要给你一个demo,然后自己不断的查看文档,基本就可以入门了,不过对于这种刚出来的东西,还是挺多坑的, ...

  2. React Native专题-江清清

    本React Native讲解专题:主要讲解了React Native开发,由基础环境搭建配置入门,基础,进阶相关讲解. 刚创建的React Native交流8群:533435865  欢迎各位大牛, ...

  3. React Native开源项目案例

    (六).React Native开源项目: 1.Pober Wong_17童鞋为gank.io做的纯React Native项目,开源地址:https://github.com/Bob1993/Rea ...

  4. 基于vue的nuxt框架cnode社区服务端渲染

    nuxt-cnode 基于vue的nuxt框架仿的cnode社区服务端渲染,主要是为了seo优化以及首屏加载速度 线上地址 http://nuxt-cnode.foreversnsd.cngithub ...

  5. React Native 开源项目汇总

    最近闲来无事,学习了React Native开发Android APP,自我感觉RN APP的效果和Native APP比还是蛮不错,以下是找到的一些优秀源码,仅供学习参考... React Nati ...

  6. react组件的生命周期

    写在前面: 阅读了多遍文章之后,自己总结了一个.一遍加强记忆,和日后回顾. 一.实例化(初始化) var Button = React.createClass({ getInitialState: f ...

  7. 十分钟介绍mobx与react

    原文地址:https://mobxjs.github.io/mobx/getting-started.html 写在前面:本人英语水平有限,主要是写给自己看的,若有哪位同学看到了有问题的地方,请为我指 ...

  8. RxJS + Redux + React = Amazing!(译一)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: https:/ ...

  9. React 入门教程

    React 起源于Facebook内部项目,是一个用来构建用户界面的 javascript 库,相当于MVC架构中的V层框架,与市面上其他框架不同的是,React 把每一个组件当成了一个状态机,组件内 ...

  10. 通往全栈工程师的捷径 —— react

    腾讯Bugly特约作者: 左明 首先,我们来看看 React 在世界范围的热度趋势,下图是关键词“房价”和 “React” 在 Google Trends 上的搜索量对比,蓝色的是 React,红色的 ...

随机推荐

  1. 洛谷p1008 三连击

    https://www.luogu.org/problemnew/show/P1008 题目描述 将1,2,3,4,5,6,7,8,9共9个数分成3组,分别组成3个三位数,且使这3个三位数的值构成1: ...

  2. 008-python绘制五个五角星

    操纵海龟绘图有着许多的命令,这些命令可以划分为两种:一种为运动命令,一种为画笔控制命令 1. 运动命令: forward(degree)  #向前移动距离degree代表距离 backward(deg ...

  3. String、StringBuffer和StringBuilder源码解析

    1.String 1.1类的定义 public final class String implements java.io.Serializable, Comparable<String> ...

  4. hbase 利用rowkey设计进行多条件查询

    摘要 本文主要内容是通过合理Hbase 行键(rowkey)设计实现快速的多条件查询,所采用的方法将所有要用于查询中的列经过一些处理后存储在rowkey中,查询时通过rowkey进行查询,提高rowk ...

  5. 【vue移动端架子】vue-h5-template

    作者大大的地址:https://github.com/sunnie1992/vue-h5-template 我们运行项目,倒是可以看一看效果 虽然就是显示的UI,但是应该可以知道作者大大想要什么东西了 ...

  6. tyvj 1423 GF和猫咪的玩具

    传送门 解题思路 题目比较水,floyd求出最短路取个最小值即可.结果joyoi时限写错了..好像只有0ms才能过??突然发现加了快读就T不加就A,数据在10000以下的还是scanf快啊. 代码 # ...

  7. webstorm 2017.3.5之后 激活

    选择"license server" 输入:http://idea.codebeta.cn

  8. CSS min-height 属性

    实例 设置段落的最小高度: p { min-height:100px; } 浏览器支持 IE Firefox Chrome Safari Opera 所有主流浏览器都支持 min-height 属性. ...

  9. NFS挂载服务具体的实施方案

    1.服务器磁盘共享实施方案 第一步:安装NFS和rpc. 1. 安装nfs-utils:NFS主程序,rpcbind:PRC主程序 nfs-utils:NFS主程序,包含rpc.nfsd  rpc.m ...

  10. MySQL数据库起步 关于数据库的基本操作(更新中...)

    mysql的基本操作 连接指定的服务器(需要服务器开启3306端口) mysql -h ip地址 -P 端口号 -u 账号 -p 密码 删除游客模式 mysql -h ip地址 -P 端口号 -u 账 ...