├── .babelrc
├── .gitignore
├── README.md
├── app
├── Router.js
├── actions
│ └── index.js
├── assets
│ └── cnode_icon_32-1.png
├── components
│ ├── Content.js
│ ├── Header.js
│ ├── Init.js
│ ├── ListItem.js
│ ├── Pagination.js
│ ├── Time.js
│ ├── TopicDetail.js
│ └── UserDetail.js
├── containers
│ ├── HeaderCon.js
│ ├── List.js
│ ├── SwitchPage.js
│ ├── TopicDetailCon.js
│ └── UserDetailCon.js
├── css
│ └── Nprogress.css
├── index.html
├── reducers
│ ├── index.js
│ ├── progressBar.js
│ ├── switchPage.js
│ ├── topicDetail.js
│ └── userDetail.js
└── routes
│ ├── Detail.js
│ ├── Index.js
│ └── User.js
├── dist
├── assets
│ └── cnode_icon_32-1.png
├── bundle.js
└── index.html
├── package.json
├── screen
├── 1.png
├── 2.png
└── 3.png
├── webpack.config.js
└── webpack.config.pro.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015", "stage-0"],
3 | "plugins": [
4 | ["import", {
5 | "libraryName": "antd",
6 | "style": true
7 | }]
8 | ],
9 | "env": {
10 | "development": {
11 | "plugins": [
12 | ["react-transform", {
13 | "transforms": [{
14 | "transform": "react-transform-hmr",
15 | "imports": ["react"],
16 | "locals": ["module"]
17 | }]
18 | }]
19 | ]
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cnode-react
2 | 第一个react练手项目,react框架写的cnode社区,数据来源于[cnode api](http://cnodejs.org/api),感谢cnode提供的api。
3 |
4 | [线上预览地址](http://chuuup.applinzi.com/cnode)
5 |
6 | ## npm
7 | - redux
8 | - react-redux
9 | - react-route
10 | - redux-thunk
11 | - redux-logger
12 | - antd
13 |
14 | ## 语法
15 | - es6
16 |
17 | ## 运行
18 | npm install
19 | npm start
20 |
21 | ## 生产
22 | npm run build
23 |
24 | ## Screen Shot
25 | 
26 |
27 | 
28 |
29 | 
30 |
31 |
32 | ## todo
33 | - ~~浏览器显示title(切换页面时,title应该改变~~
34 | - ~~添加user页面~~
35 | - react-router 后退会重新加载
36 | - ~~添加webpack生产配置 npm run build~~
37 | - ~~添加热加载HMR~~
38 | - ~~压缩bundle.js文件~~
39 |
40 | ## 坑
41 | - @user 由于无法改变href,所以点击会跳转到错误界面
--------------------------------------------------------------------------------
/app/Router.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Router, Route, hashHistory, IndexRoute, browserHistory } from 'react-router'
4 | import { createStore, applyMiddleware } from 'redux'
5 | import thunk from 'redux-thunk'
6 | import createLogger from 'redux-logger'
7 | import { Provider } from 'react-redux'
8 | import reducer from './reducers'
9 | import HeaderCon from './containers/HeaderCon'
10 | import Index from './routes/Index'
11 | import Detail from './routes/Detail'
12 | import User from './routes/User'
13 | import Init from './components/Init'
14 |
15 | const body = document.getElementsByTagName('body')[0]
16 | body.style.backgroundColor = '#f2f3f5'
17 |
18 |
19 | const middleware = [ thunk ]
20 | if (process.env.NODE_ENV !== 'production') {
21 | console.log(process.env.NODE_ENV)
22 | middleware.push(createLogger())
23 | }
24 |
25 | const store = createStore(
26 | reducer,
27 | applyMiddleware(...middleware)
28 | )
29 |
30 | render((
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ), document.getElementById('content'))
46 |
47 |
--------------------------------------------------------------------------------
/app/actions/index.js:
--------------------------------------------------------------------------------
1 | // export const switchPage = (index) => ({
2 | // type: 'SWITCH_PAGE',
3 | // index
4 | // })
5 |
6 | export const getThenShow = (index = 1, tab = "") => (dispatch, getState) => {
7 |
8 | dispatch({
9 | type: 'SHOW_PROGRESS'
10 | })
11 |
12 | dispatch({
13 | type: 'SWITCH_PAGINATION',
14 | pageIndex: index
15 | })
16 |
17 | let url = `https://cnodejs.org/api/v1/topics?limit=20&mdrender=false&page=${index}&tab=${tab}`
18 | fetch(url)
19 | .then(response => {
20 | return response.json()
21 | }, e => {
22 | dispatch({
23 | type: 'FETCH_DATA_FAIL',
24 | message: e,
25 | })
26 | })
27 | .then(data => {
28 | dispatch({
29 | type: 'FETCH_DATA_SUCCESS',
30 | message: data,
31 | })
32 | dispatch({
33 | type: 'HIDE_PROGRESS'
34 | })
35 | })
36 | }
37 |
38 | export const getTopicDetail = (topicId) => (dispatch, getState) => {
39 |
40 | dispatch({
41 | type: 'SHOW_PROGRESS'
42 | })
43 |
44 | let url = `https://cnodejs.org/api/v1/topic/${topicId}`
45 | fetch(url)
46 | .then(response => {
47 | return response.json()
48 | }, e => {
49 | dispatch({
50 | type: 'FETCH_DATA_FAIL',
51 | message: e,
52 | })
53 | })
54 | .then(data => {
55 | dispatch({
56 | type: 'TOPIC_DETAIL_FETCH_DATA_SUCCESS',
57 | message: data,
58 | })
59 | dispatch({
60 | type: 'HIDE_PROGRESS'
61 | })
62 | })
63 | }
64 |
65 | export const changeRepliesPage = (index) => (dispatch, getState)=> {
66 | dispatch({
67 | type: 'CHANGE_REPLIES_PAGE',
68 | index: index
69 | })
70 | }
71 |
72 | export const getUserDetail = (username) => (dispatch, getState) => {
73 |
74 | dispatch({
75 | type: 'SHOW_PROGRESS'
76 | })
77 |
78 | let url = `https://cnodejs.org/api/v1/user/${username}`
79 | fetch(url)
80 | .then(response => {
81 | return response.json()
82 | }, e => {
83 | dispatch({
84 | type: 'FETCH_DATA_FAIL',
85 | message: e,
86 | })
87 | })
88 | .then(data => {
89 | dispatch({
90 | type: 'USER_DETAIL_FETCH_DATA_SUCCESS',
91 | message: data,
92 | })
93 | dispatch({
94 | type: 'HIDE_PROGRESS'
95 | })
96 | })
97 | }
--------------------------------------------------------------------------------
/app/assets/cnode_icon_32-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/believeitcould/cnode-react/87a897be0d8db8d390cdc31dbf4df99b01d48d98/app/assets/cnode_icon_32-1.png
--------------------------------------------------------------------------------
/app/components/Content.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 |
4 | export default class extends React.Component {
5 | render() {
6 | return (
7 |
8 | {this.props.children}
9 |
10 | )
11 | }
12 | }
13 |
14 | const styles = {
15 | box: {
16 | backgroundColor: '#fff',
17 | borderRadius: '4px',
18 | margin: '40px auto 30px',
19 | width: '800px'
20 | }
21 | }
--------------------------------------------------------------------------------
/app/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Link } from 'react-router'
4 |
5 | class Inner extends React.Component {
6 |
7 | render () {
8 | return (
9 |
10 | CNode
11 | 全部
12 | 精华
13 | 分享
14 | 工作
15 | Built with React
16 |
17 | )
18 | }
19 | }
20 |
21 | export default class Header extends React.Component {
22 | render() {
23 | return (
24 |
25 |
28 | {this.props.children}
29 |
30 |
31 | )
32 | }
33 | }
34 |
35 | const styles = {
36 | header: {
37 | backgroundColor: '#f60',
38 |
39 | },
40 | inner: {
41 | maxWidth: '800px',
42 | boxSizing: 'borderBox',
43 | margin: '0 auto',
44 | padding: '15px 5px'
45 | },
46 | logo: {
47 | color: '#fff',
48 | fontSize: '16px',
49 | marginRight: '1.8em',
50 | border: '2px solid #fff',
51 | borderRadius: '5px',
52 | padding: '5px'
53 | },
54 | link: {
55 | color: '#fff',
56 | fontSize: '16px',
57 | marginRight: '1.8em',
58 | cursor: 'pointer'
59 | },
60 | txt: {
61 | color: '#fff',
62 | fontSize: '16px',
63 | float: 'right'
64 | }
65 | }
--------------------------------------------------------------------------------
/app/components/Init.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 |
4 | export default class Init extends React.Component {
5 | render() {
6 | return (
7 |
8 | )
9 | }
10 | }
11 |
12 | const styles = {
13 |
14 | }
--------------------------------------------------------------------------------
/app/components/ListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Link } from 'react-router'
4 | import Time from './Time'
5 |
6 |
7 | class ListItem extends React.Component {
8 |
9 | render() {
10 | return (
11 |
12 |

13 |
{this.props.item.title}
14 |
{Time(this.props.item.last_reply_at)}
15 |
16 | )
17 | }
18 | }
19 |
20 | export default class List extends React.Component {
21 |
22 | componentDidMount() {
23 | this.props.getIndexData()
24 | }
25 |
26 | componentDidUpdate(prevProps) {
27 | if (prevProps.tab !== this.props.tab) {
28 | this.props.getIndexData()
29 | }
30 | }
31 |
32 | render() {
33 | let item = this.props.item
34 | if (!item) return ( )
35 |
36 | return (
37 |
38 | {
39 | item.map((ele, index)=>{
40 | return
41 | })
42 | }
43 |
44 | )
45 | }
46 | }
47 |
48 | const styles = {
49 | box: {
50 | display: 'flex',
51 | flexDirection: 'row',
52 | alignItems: 'center',
53 | padding: '10px',
54 | },
55 | avatar: {
56 | width: '40px',
57 | height: '40px',
58 | borderRadius: '3px',
59 | marginRight: '30px'
60 | },
61 | title: {
62 | flex: 1
63 | },
64 | lastReply: {
65 |
66 | }
67 | }
--------------------------------------------------------------------------------
/app/components/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Pagination } from 'antd'
3 | import { render } from 'react-dom'
4 |
5 | class Page extends React.Component {
6 | render() {
7 | return (
8 |
9 |
this.props.onChange(index)} />
10 |
11 | )
12 | }
13 | }
14 |
15 | const styles = {
16 | box: {
17 | backgroundColor: '#fff',
18 | borderRadius: '2px',
19 | padding: '15px 30px',
20 | boxShadow: '0 1px 2px rgba(0,0,0,.1)',
21 | display: 'flex',
22 | justifyContent: 'center'
23 | }
24 | }
25 | export default Page
--------------------------------------------------------------------------------
/app/components/Time.js:
--------------------------------------------------------------------------------
1 | let now = new Date().getTime()
2 |
3 | const Time = (date) => {
4 | let t = Date.parse(date)
5 | var s = (now - t) / 1000
6 | let timeStr = ''
7 | if (s < 60) {
8 | timeStr = parseInt(s) + '秒前'
9 | }else if ((s/60) < 60) {
10 | timeStr = parseInt(s/60) + '分钟前'
11 | }else if ((s/60/60) < 24) {
12 | timeStr = parseInt(s/60/60) + '小时前'
13 | }else if ((s/60/60/24) < 30) {
14 | timeStr = parseInt(s/60/60/24) + '天前'
15 | }else if ((s/60/60/24/30) < 12) {
16 | timeStr = parseInt(s/60/60/24/30) + '月前'
17 | }else {
18 | timeStr = parseInt(s/60/60/24/30/12) + '年前'
19 | }
20 | return timeStr
21 | }
22 |
23 | export default Time
--------------------------------------------------------------------------------
/app/components/TopicDetail.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Pagination } from 'antd'
4 | import 'github-markdown-css'
5 | import { Link } from 'react-router'
6 | import Time from './Time'
7 |
8 | const Author = ({ title, avatar, loginname, createAt, content}) => {
9 |
10 | return (
11 |
12 |
{title}
13 |
14 |
15 |

16 |
17 |
{loginname}
18 |
{Time(createAt)}
19 |
20 |
21 |
22 |
23 | )
24 | }
25 |
26 | const Replies = ({ replies, onChangeRepliesPageIndex, repliesPageIndex }) => {
27 | // 每页评论数 当前页数
28 | let pageSize = 20
29 |
30 | let onPageChange = (index) => {
31 | {/* dispatch action 重新render replies分页数据 */}
32 | onChangeRepliesPageIndex(index)
33 | }
34 | return (
35 |
44 | )
45 | }
46 |
47 | const RepliesCount = ({ count }) => {
48 | return (
49 |
50 | {count} 回复
51 |
52 | )
53 | }
54 |
55 | const RepliesList = ({ replies }) => {
56 | return (
57 |
58 | {
59 | replies.map((ele, index)=>{
60 | return (
61 |
62 |
63 |
64 |

65 |
66 |
{ele.author.loginname}
67 |
{Time(ele.create_at)}
68 |
69 |
70 |
72 |
73 | )
74 | })
75 | }
76 |
77 | )
78 | }
79 |
80 | export default class extends React.Component {
81 |
82 | constructor(props) {
83 | super(props)
84 | }
85 |
86 | componentDidMount() {
87 | let topicId = this.props.topicId
88 | this.props.getTopicDetail(topicId)
89 | }
90 |
91 | render() {
92 | let data = this.props.details.data
93 | // 1.state不存在时显示空 2.当前id与state.id不同时显示空
94 | if (!data || data.id != this.props.topicId) {
95 | return ()
96 | }
97 |
98 | // 页面title
99 | document.title = data.title
100 |
101 | return (
102 |
103 |
104 |
109 | {this.props.children}
110 |
111 | {/* 无回复的帖子 不显示Replies */}
112 | {data.replies.length != 0
113 | ?
114 |
117 | :
118 | ''
119 | }
120 |
121 |
122 | )
123 | }
124 | }
125 |
126 | const styles = {
127 | box: {
128 | backgroundColor: '#fff',
129 | borderRadius: '4px',
130 | margin: '40px auto 30px',
131 | width: '800px',
132 | padding: '10px',
133 | },
134 | avatar: {
135 | width: '40px',
136 | height: '40px',
137 | borderRadius: '3px'
138 | },
139 | repliesBox: {
140 | borderRadius: '4px',
141 | margin: '40px auto 30px',
142 | width: '800px',
143 | },
144 | repliesItem: {
145 | backgroundColor: '#fff',
146 | padding: '10px',
147 | borderTop: '1px solid #f0f0f0',
148 | },
149 | repliesAvatar: {
150 | width: '30px',
151 | height: '30px',
152 | borderRadius: '3px'
153 | }
154 | }
--------------------------------------------------------------------------------
/app/components/UserDetail.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Link } from 'react-router'
4 | import Time from './Time'
5 |
6 | const Section = ({ section, children }) => {
7 | return (
8 |
9 | {section}
10 | {children}
11 |
12 | )
13 | }
14 |
15 | const SectionHeader = ({ children }) => {
16 |
17 | return (
18 |
19 |
20 | {children}
21 |
22 |
23 | )
24 | }
25 |
26 | const SectionIndex = ({ avatar, loginname, score, createAt }) => {
27 | return (
28 |
29 |
30 |

31 |
{loginname}
32 |
33 |
34 |
35 | {score} 积分
36 |
37 |
38 |
39 |
40 | 注册时间 {Time(createAt)}
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | const SectionTopic = ({ list }) => {
48 | return (
49 |
50 | {list.map(
51 | (ele,index) => {
52 | return
53 | })
54 | }
55 |
56 | )
57 | }
58 |
59 | const SectionReply = ({ list }) => {
60 | return (
61 |
62 | {list.map(
63 | (ele,index) => {
64 | return
65 | })
66 | }
67 |
68 | )
69 | }
70 |
71 | const List = ({ item }) => {
72 | return (
73 |
74 |

75 |
{item.title}
76 |
{Time(item.last_reply_at)}
77 |
78 | )
79 | }
80 |
81 | export default class extends React.Component {
82 |
83 | constructor(props) {
84 | super(props)
85 | }
86 |
87 | componentDidMount() {
88 |
89 | this.props.getUserDetail()
90 | }
91 |
92 | render() {
93 | let data = this.props.details.data
94 | if (!data) {
95 | return ()
96 | }
97 |
98 | return (
99 |
100 |
106 |
109 |
112 |
113 |
114 | )
115 | }
116 | }
117 |
118 | const styles = {
119 | box: {
120 | margin: '40px auto 30px',
121 | width: '800px',
122 | },
123 | section: {
124 | padding: '10px',
125 | marginBottom: '20px',
126 | backgroundColor: '#fff',
127 | borderRadius: '4px'
128 | },
129 | avatar: {
130 | width: '40px',
131 | height: '40px',
132 | borderRadius: '3px',
133 | marginRight: '10px'
134 | },
135 | listAvatar: {
136 | width: '30px',
137 | height: '30px',
138 | borderRadius: '3px',
139 | marginRight: '10px'
140 | },
141 | listBox: {
142 | display: 'flex',
143 | flexDirection: 'row',
144 | alignItems: 'center',
145 | padding: '5px 0'
146 | },
147 | listTitle: {
148 | flex: 1
149 | },
150 | }
--------------------------------------------------------------------------------
/app/containers/HeaderCon.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Header from '../components/Header'
3 | import { getThenShow } from '../actions'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 |
7 | })
8 |
9 | const mapDispatchToProps = (dispatch, ownProps) => ({
10 | // onClick: (index, tab) => {
11 | // dispatch(getThenShow(index, tab))
12 | // }
13 | onClick: (index, tab) => {
14 | dispatch(getThenShow(index, tab))
15 | }
16 | })
17 |
18 | const HeaderCon = connect(
19 | mapStateToProps,
20 | mapDispatchToProps
21 | )(Header)
22 |
23 | export default HeaderCon
24 |
--------------------------------------------------------------------------------
/app/containers/List.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import ListItem from '../components/ListItem'
3 | import { getThenShow } from '../actions'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | loaded: state.switchPage.tab == ownProps.tab,
7 | item: state.switchPage.data
8 | })
9 |
10 | const mapDispatchToProps = (dispatch, ownProps) => ({
11 | getIndexData: () => {
12 | dispatch(getThenShow(ownProps.pageIndex, ownProps.tab))
13 | }
14 | })
15 |
16 | const List = connect(
17 | mapStateToProps,
18 | mapDispatchToProps
19 | )(ListItem)
20 |
21 | export default List
22 |
--------------------------------------------------------------------------------
/app/containers/SwitchPage.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { getThenShow } from '../actions'
3 | import Pagination from '../components/Pagination'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | pageIndex: state.switchPage.pageIndex,
7 | tab: ownProps.tab
8 | })
9 |
10 | const mapDispatchToProps = (dispatch, ownProps) => ({
11 | onChange: (index) => {
12 | dispatch(getThenShow(index, ownProps.tab))
13 | }
14 | })
15 |
16 | const SwitchPage = connect(
17 | mapStateToProps,
18 | mapDispatchToProps
19 | )(Pagination)
20 |
21 | export default SwitchPage
22 |
--------------------------------------------------------------------------------
/app/containers/TopicDetailCon.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { getTopicDetail, changeRepliesPage } from '../actions'
3 | import TopicDetail from '../components/TopicDetail'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | details: state.topicDetail.details,
7 | repliesPageIndex: state.topicDetail.repliesPageIndex
8 | })
9 |
10 | const mapDispatchToProps = (dispatch, ownProps) => ({
11 | getTopicDetail: (topicId) => {
12 | dispatch(getTopicDetail(topicId))
13 | },
14 | changeRepliesPage: (index) => {
15 | dispatch(changeRepliesPage(index))
16 | }
17 | })
18 |
19 | const TopicDetailCon = connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(TopicDetail)
23 |
24 | export default TopicDetailCon
25 |
--------------------------------------------------------------------------------
/app/containers/UserDetailCon.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { getUserDetail } from '../actions'
3 | import UserDetail from '../components/UserDetail'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | details: state.userDetail
7 | })
8 |
9 | const mapDispatchToProps = (dispatch, ownProps) => ({
10 | getUserDetail: () => {
11 | dispatch(getUserDetail(ownProps.username))
12 | }
13 | })
14 |
15 | const UserDetailCon = connect(
16 | mapStateToProps,
17 | mapDispatchToProps
18 | )(UserDetail)
19 |
20 | export default UserDetailCon
21 |
--------------------------------------------------------------------------------
/app/css/Nprogress.css:
--------------------------------------------------------------------------------
1 | /* Make clicks pass-through */
2 |
3 | #nprogress {
4 | pointer-events: none;
5 | }
6 |
7 | #nprogress .bar {
8 | background: #29d;
9 | position: fixed;
10 | z-index: 1031;
11 | top: 0;
12 | left: 0;
13 | width: 100%;
14 | height: 3px;
15 | }
16 |
17 |
18 | /* Fancy blur effect */
19 |
20 | #nprogress .peg {
21 | display: block;
22 | position: absolute;
23 | right: 0px;
24 | width: 100px;
25 | height: 100%;
26 | box-shadow: 0 0 10px #29d, 0 0 5px #29d;
27 | opacity: 1.0;
28 | -webkit-transform: rotate(3deg) translate(0px, -4px);
29 | -ms-transform: rotate(3deg) translate(0px, -4px);
30 | transform: rotate(3deg) translate(0px, -4px);
31 | }
32 |
33 |
34 | /* Remove these to get rid of the spinner */
35 |
36 | #nprogress .spinner {
37 | display: block;
38 | position: fixed;
39 | z-index: 1031;
40 | top: 15px;
41 | right: 15px;
42 | }
43 |
44 | #nprogress .spinner-icon {
45 | width: 18px;
46 | height: 18px;
47 | box-sizing: border-box;
48 | border: solid 2px transparent;
49 | border-top-color: #29d;
50 | border-left-color: #29d;
51 | border-radius: 50%;
52 | -webkit-animation: nprogress-spinner 400ms linear infinite;
53 | animation: nprogress-spinner 400ms linear infinite;
54 | }
55 |
56 | .nprogress-custom-parent {
57 | overflow: hidden;
58 | position: relative;
59 | }
60 |
61 | .nprogress-custom-parent #nprogress .spinner,
62 | .nprogress-custom-parent #nprogress .bar {
63 | position: absolute;
64 | }
65 |
66 | @-webkit-keyframes nprogress-spinner {
67 | 0% {
68 | -webkit-transform: rotate(0deg);
69 | }
70 | 100% {
71 | -webkit-transform: rotate(360deg);
72 | }
73 | }
74 |
75 | @keyframes nprogress-spinner {
76 | 0% {
77 | transform: rotate(0deg);
78 | }
79 | 100% {
80 | transform: rotate(360deg);
81 | }
82 | }
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | CNode
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import switchPage from './switchPage'
3 | import progressBar from './progressBar'
4 | import topicDetail from './topicDetail'
5 | import userDetail from './userDetail'
6 | const reducer = combineReducers({
7 | switchPage,
8 | progressBar,
9 | topicDetail,
10 | userDetail
11 | })
12 |
13 | export default reducer
14 |
--------------------------------------------------------------------------------
/app/reducers/progressBar.js:
--------------------------------------------------------------------------------
1 | import NProgress from 'nprogress'
2 | import '../css/Nprogress.css'
3 |
4 | const progressBar = (state = NProgress, action) => {
5 | switch (action.type) {
6 | case "SHOW_PROGRESS":
7 |
8 | return state.start()
9 | case "HIDE_PROGRESS":
10 |
11 | return state.done()
12 | default:
13 | return state
14 | }
15 | }
16 |
17 | export default progressBar
--------------------------------------------------------------------------------
/app/reducers/switchPage.js:
--------------------------------------------------------------------------------
1 | let initialState = {
2 | tab: '',
3 | pageIndex: 1
4 | }
5 |
6 | const switchPage = (state = initialState, action) => {
7 | if (typeof state == 'undefined') {
8 | return []
9 | }
10 | switch(action.type) {
11 | case 'FETCH_DATA_SUCCESS':
12 | return {
13 | pageIndex: state.pageIndex,
14 | data: action.message.data
15 | }
16 | case 'SWITCH_PAGINATION':
17 | return {
18 | pageIndex: action.pageIndex,
19 | data: state.data
20 | }
21 |
22 | default:
23 | return state
24 | }
25 | }
26 |
27 | export default switchPage
--------------------------------------------------------------------------------
/app/reducers/topicDetail.js:
--------------------------------------------------------------------------------
1 | let initialState = {
2 | details: '',
3 | repliesPageIndex: 1
4 | }
5 |
6 | const topicDetail = (state = initialState, action) => {
7 | if (typeof state == 'undefined') {
8 | return []
9 | }
10 | switch(action.type) {
11 | case 'TOPIC_DETAIL_FETCH_DATA_SUCCESS':
12 | return {
13 | details: action.message,
14 | repliesPageIndex: state.repliesPageIndex
15 | }
16 | case 'CHANGE_REPLIES_PAGE':
17 | return {
18 | details: state.details,
19 | repliesPageIndex: action.index
20 | }
21 | default:
22 | return state
23 | }
24 | }
25 |
26 | export default topicDetail
--------------------------------------------------------------------------------
/app/reducers/userDetail.js:
--------------------------------------------------------------------------------
1 | let initialState = {
2 |
3 | }
4 |
5 | const topicDetail = (state = initialState, action) => {
6 | if (typeof state == 'undefined') {
7 | return []
8 | }
9 | switch(action.type) {
10 | case 'USER_DETAIL_FETCH_DATA_SUCCESS':
11 | return action.message
12 | default:
13 | return state
14 | }
15 | }
16 |
17 | export default topicDetail
--------------------------------------------------------------------------------
/app/routes/Detail.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { browserHistory } from 'react-router'
4 | import HeaderCon from '../containers/HeaderCon'
5 | import Content from '../components/Content'
6 | import TopicDetailCon from '../containers/TopicDetailCon'
7 |
8 | export default class Detail extends React.Component {
9 | constructor(props) {
10 | super(props)
11 | }
12 |
13 | componentDidMount() {
14 | this.props.router.setRouteLeaveHook(
15 | this.props.route,
16 | this.routerWillLeave
17 | )
18 | }
19 |
20 | routerWillLeave(nextLocation) {
21 |
22 | return true
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
29 |
30 | )
31 | }
32 | }
33 |
34 |
35 | const styles = {
36 |
37 | }
--------------------------------------------------------------------------------
/app/routes/Index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import SwitchPage from '../containers/SwitchPage'
4 | import Content from '../components/Content'
5 | import List from '../containers/List'
6 |
7 | export default class Index extends React.Component {
8 | constructor(props) {
9 | super(props)
10 | }
11 |
12 | componentDidMount() {
13 | document.title = 'CNode'
14 | }
15 |
16 | componentDidUpdate() {
17 |
18 | }
19 |
20 |
21 | render() {
22 | let path = this.props.location.pathname
23 | let tab = path.substr(1)
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 | }
34 |
35 |
36 | const styles = {
37 |
38 | }
--------------------------------------------------------------------------------
/app/routes/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import UserDetailCon from '../containers/UserDetailCon'
4 | import Content from '../components/Content'
5 |
6 | export default class Detail extends React.Component {
7 | constructor(props) {
8 | super(props)
9 | }
10 |
11 | componentDidMount() {
12 | document.title = `@${this.props.params.name}`
13 | }
14 |
15 | render() {
16 | let username = this.props.params.name
17 | return (
18 |
19 |
20 |
21 | )
22 | }
23 | }
24 |
25 |
26 | const styles = {
27 |
28 | }
--------------------------------------------------------------------------------
/dist/assets/cnode_icon_32-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/believeitcould/cnode-react/87a897be0d8db8d390cdc31dbf4df99b01d48d98/dist/assets/cnode_icon_32-1.png
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | CNode
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cnode-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/believeitcould/cnode-react.git"
8 | },
9 | "scripts": {
10 | "start": "webpack-dev-server --progress",
11 | "build": "webpack --progress --colors --config webpack.config.pro.js"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "antd": "^2.5.2",
18 | "github-markdown-css": "^2.4.1",
19 | "nprogress": "^0.2.0",
20 | "react": "^15.4.1",
21 | "react-dom": "^15.4.1",
22 | "react-redux": "^5.0.2",
23 | "react-router": "^3.0.1",
24 | "redux": "^3.6.0",
25 | "redux-thunk": "^2.1.0"
26 | },
27 | "devDependencies": {
28 | "babel-core": "^6.20.0",
29 | "babel-loader": "^6.2.9",
30 | "babel-plugin-import": "^1.1.0",
31 | "babel-plugin-react-transform": "^2.0.2",
32 | "babel-preset-stage-0": "^6.16.0",
33 | "babel-runtime": "^6.20.0",
34 | "babel-preset-es2015": "^6.18.0",
35 | "babel-preset-react": "^6.16.0",
36 | "compression-webpack-plugin": "^0.3.2",
37 | "css-loader": "^0.26.1",
38 | "file-loader": "^0.9.0",
39 | "html-webpack-plugin": "^2.26.0",
40 | "less": "^2.7.2",
41 | "less-loader": "^2.2.3",
42 | "open-browser-webpack-plugin": "0.0.3",
43 | "react-transform-hmr": "^1.0.4",
44 | "redux-logger": "^2.7.4",
45 | "style-loader": "^0.13.1",
46 | "url-loader": "^0.5.7",
47 | "webpack": "^1.14.0",
48 | "webpack-dashboard": "^0.2.1",
49 | "webpack-dev-server": "^1.16.2"
50 | }
51 | }
--------------------------------------------------------------------------------
/screen/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/believeitcould/cnode-react/87a897be0d8db8d390cdc31dbf4df99b01d48d98/screen/1.png
--------------------------------------------------------------------------------
/screen/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/believeitcould/cnode-react/87a897be0d8db8d390cdc31dbf4df99b01d48d98/screen/2.png
--------------------------------------------------------------------------------
/screen/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/believeitcould/cnode-react/87a897be0d8db8d390cdc31dbf4df99b01d48d98/screen/3.png
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 | var HtmlWebpackPlugin = require('html-webpack-plugin')
4 | var OpenBrowserPlugin = require('open-browser-webpack-plugin') //自动打开浏览器插件
5 |
6 | process.env.NODE_ENV = 'development' // 这个要写 .babel env 坑!
7 |
8 | module.exports = {
9 |
10 | entry: ['webpack/hot/dev-server', path.resolve(__dirname, 'app/Router.js')],
11 |
12 | output: {
13 | path: path.resolve(__dirname, 'build'),
14 | filename: 'bundle.js'
15 | },
16 |
17 | devServer: {
18 | contentBase: './app',
19 | port: 3000,
20 | color: true,
21 | inline: true,
22 | hot: true,
23 | historyApiFallback: true
24 | },// progress 不起作用
25 |
26 | module: {
27 | loaders: [
28 | {
29 | test: /\.jsx?$/,
30 | exclude: /node_modules/,
31 | loader: 'babel'
32 | },
33 | {
34 | test: /\.(png|jpg|gif)$/,
35 | loader: 'url-loader?limit=8192' // 这里的 limit=8192 表示用 base64 编码 <= 8K 的图像
36 | },
37 | {
38 | test: /\.css$/,
39 | loader: 'style-loader!css-loader'
40 | },
41 | {
42 | test: /\.less$/,
43 | loader: 'style-loader!css-loader!less-loader'
44 | }
45 | ]
46 | },
47 |
48 | plugins: [
49 | new HtmlWebpackPlugin({
50 | template: './app/index.html'
51 | }),
52 | new webpack.DefinePlugin({
53 | 'process.env': {
54 | NODE_ENV: JSON.stringify("development")
55 | }
56 | }),
57 | new webpack.HotModuleReplacementPlugin(), //热加载插件
58 |
59 | new OpenBrowserPlugin({ url: 'http://localhost:3000' })
60 | ]
61 | }
62 | // new webpack.optimize.CommonsChunkPlugin('vendors','js/vendors.js'),
63 | // vendors:['react','react-dom','react-router','redux','react-redux'] //第三方库和框架
64 | // new webpack.optimize.OccurenceOrderPlugin(),
65 | // new webpack.optimize.UglifyJsPlugin({
66 | // compressor: {
67 | // warnings: false,
68 | // },
69 | // }),
--------------------------------------------------------------------------------
/webpack.config.pro.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 | var HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | process.env.NODE_ENV = 'production' // 这个要写 .babel env 坑!
6 |
7 | module.exports = {
8 | entry: path.resolve(__dirname, 'app/Router.js'),
9 | output: {
10 | path: path.resolve(__dirname, 'dist'),
11 | filename: 'bundle.js'
12 | },
13 | module: {
14 | loaders: [
15 | {
16 | test: /\.jsx?$/,
17 | exclude: /node_modules/,
18 | loader: 'babel'
19 | },
20 | {
21 | test: /\.(png|jpg|gif)$/,
22 | loader: 'url-loader?limit=8192' // 这里的 limit=8192 表示用 base64 编码 <= 8K 的图像
23 | },
24 | {
25 | test: /\.css$/,
26 | loader: 'style-loader!css-loader'
27 | },
28 | {
29 | test: /\.less$/,
30 | loader: 'style-loader!css-loader!less-loader'
31 | }
32 | ]
33 | },
34 | plugins: [
35 | new webpack.DefinePlugin({
36 | 'process.env': {
37 | NODE_ENV: JSON.stringify("production"),
38 | BABEL_ENV: JSON.stringify("production")
39 | }
40 | }),
41 | new webpack.optimize.UglifyJsPlugin({
42 | compressor: {
43 | warnings: false,
44 | },
45 | }),
46 | new webpack.optimize.OccurrenceOrderPlugin(),
47 | new HtmlWebpackPlugin({
48 | template: './app/index.html'
49 | })
50 | ]
51 | }
--------------------------------------------------------------------------------