react-cnode

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

//根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">&#xe651;</i>
          <p>首页</p>
        </Link>
      </FooterItem>
      <FooterItem>
        <Link to={"/create"}>
          <i className="iconfont">&#xe67b;</i>
          <p>发表</p>
        </Link>
      </FooterItem>
      <FooterItem>
        <Link to={"/message"}>
          <i className="iconfont">&#xe60c;</i>
          <p>消息</p>
        </Link>
      </FooterItem>
      <FooterItem>
        <Link to={"/mine"}>
          <i className="iconfont">&#xe608;</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">&#xe664;</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">&#xe61b;</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">&#xe609;</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);

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

猜你喜欢

转载自www.cnblogs.com/smart-girl/p/10855805.html
今日推荐