• 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);
    

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

  • 相关阅读:
    为何要对URL进行编码
    关于GreenPlum的一些整理
    Greenplum入门——基础知识、安装、常用函数
    PyGreSQL入门,pg模块,pgdb模块
    MySQL Test Suite使用
    MySQL到Greenplum迁移分析
    Method overrides should not change parameter defaults
    Why use a public method in an internal class?
    Git Submodules vs Git Subtrees
    To Allow App through Windows Defender Firewall in Command Prompt
  • 原文地址:https://www.cnblogs.com/smart-girl/p/10855805.html
Copyright © 2020-2023  润新知