├── .gitignore ├── shot ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png ├── 10.png ├── 11.png ├── QR-code.png └── redux-state.jpg ├── src ├── Iconfont │ ├── iconfont.eot │ ├── iconfont.ttf │ ├── iconfont.woff │ ├── iconfont.css │ ├── demo.css │ ├── demo.html │ └── iconfont.svg ├── Config │ ├── Config.js │ ├── Store.jsx │ └── Route.jsx ├── Action │ └── Index.jsx ├── Template │ └── index.html ├── App.jsx ├── Component │ ├── common │ │ ├── getComponent.jsx │ │ ├── GetData.jsx │ │ ├── index.jsx │ │ └── GetNextPage.jsx │ ├── Signout.jsx │ ├── Signin.jsx │ ├── MyMessages.jsx │ ├── UserView.jsx │ ├── TopicCreate.jsx │ ├── IndexList.jsx │ └── Topic.jsx ├── Reducer │ └── Index.jsx ├── Tool.jsx └── Style │ └── style.less ├── postcss.config.js ├── server.js ├── index.html ├── package.json ├── README.md └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .vscode 4 | yarn.lock -------------------------------------------------------------------------------- /shot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/1.png -------------------------------------------------------------------------------- /shot/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/2.png -------------------------------------------------------------------------------- /shot/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/3.png -------------------------------------------------------------------------------- /shot/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/4.png -------------------------------------------------------------------------------- /shot/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/5.png -------------------------------------------------------------------------------- /shot/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/6.png -------------------------------------------------------------------------------- /shot/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/7.png -------------------------------------------------------------------------------- /shot/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/8.png -------------------------------------------------------------------------------- /shot/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/9.png -------------------------------------------------------------------------------- /shot/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/10.png -------------------------------------------------------------------------------- /shot/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/11.png -------------------------------------------------------------------------------- /shot/QR-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/QR-code.png -------------------------------------------------------------------------------- /shot/redux-state.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/shot/redux-state.jpg -------------------------------------------------------------------------------- /src/Iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/src/Iconfont/iconfont.eot -------------------------------------------------------------------------------- /src/Iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/src/Iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/Iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzxb/react-cnode/HEAD/src/Iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/Config/Config.js: -------------------------------------------------------------------------------- 1 | export const target = process.env.NODE_ENV !== 'production' ? '' : 'https://cnodejs.org'; //目标网站 -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by EX_WLJR_CHENYULUN on 2017/5/18. 3 | */ 4 | module.exports = { 5 | // parser: 'sugarss', 6 | // exec: true, 7 | plugins: { 8 | 'autoprefixer': {} 9 | } 10 | }; -------------------------------------------------------------------------------- /src/Config/Store.jsx: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 2 | import reducer from '../Reducer/Index'; 3 | import thunk from 'redux-thunk'; 4 | 5 | //创建一个 Redux store 来以存放应用中所有的 state,应用中应有且仅有一个 store。 6 | var store = createStore( 7 | combineReducers(reducer), 8 | applyMiddleware(thunk) 9 | ); 10 | 11 | export default store; -------------------------------------------------------------------------------- /src/Action/Index.jsx: -------------------------------------------------------------------------------- 1 | export default (_ID) => { 2 | var action = {}; 3 | var arr = [ 4 | 'signinSuccess', //登录成功 5 | 'signin', //退出登录 6 | 'setState' //设置状态 7 | ]; 8 | 9 | for (let i = 0; i < arr.length; i++) { 10 | action[arr[i]] = (target) => { 11 | return { _ID: _ID, target: target, type: arr[i] }; 12 | } 13 | } 14 | 15 | return action; 16 | } -------------------------------------------------------------------------------- /src/Template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | react-cnode 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | //启动服务 6 | var server = new WebpackDevServer(webpack(config), { 7 | publicPath: config.output.publicPath, 8 | // 相当于通过本地node服务代理请求到了http://cnodejs.org/api 9 | proxy: { 10 | "/api/*": { 11 | target: "https://cnodejs.org", 12 | secure: false 13 | } 14 | }, 15 | stats: { 16 | colors: true 17 | }, 18 | }); 19 | 20 | //将其他路由,全部返回index.html 21 | server.app.get('*', function (req, res) { 22 | res.sendFile(__dirname + '/index.html') 23 | }); 24 | 25 | server.listen(3000); 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | react-cnode 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import 'es6-promise/auto'; 2 | import React, { Component } from 'react'; 3 | import ReactDOM, { render } from 'react-dom'; 4 | import { Provider } from 'react-redux'; 5 | import route from './Config/Route'; //路由配置 6 | import store from './Config/Store'; 7 | 8 | 9 | import 'normalize.css'; //重置浏览器默认样式 10 | // import 'flex.css'; //flex布局 11 | import 'flex.css/dist/data-flex.css'; //flex布局 12 | import './Style/style.less'; //加载公共样式 13 | import './Iconfont/iconfont.css'; //字体图标 14 | import 'github-markdown-css'; //markdown css 15 | 16 | store.subscribe(function () { 17 | // console.log(store.getState()); 18 | }); 19 | 20 | 21 | render( 22 | 23 | {route} 24 | , 25 | document.body.appendChild(document.createElement('div')) 26 | ); -------------------------------------------------------------------------------- /src/Component/common/getComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Bundle extends Component { 4 | constructor (...args){ 5 | super(...args); 6 | this.state = { 7 | abc:"abc", 8 | mod: null 9 | }; 10 | } 11 | componentWillMount() { 12 | this.load(this.props) 13 | } 14 | 15 | componentWillReceiveProps(nextProps) { 16 | if (nextProps.load !== this.props.load) { 17 | this.load(nextProps) 18 | } 19 | } 20 | 21 | load(props) { 22 | this.setState({ 23 | mod: null 24 | }) 25 | props.load().then((mod) => { 26 | this.setState({ 27 | // handle both es imports and cjs 28 | mod: mod ? mod.default : mod 29 | }) 30 | }).catch(err => console.log('Failed to load module', err)); 31 | } 32 | 33 | render() { 34 | if (!this.state.mod) return false; 35 | return this.props.children(this.state.mod, this.props); 36 | } 37 | } 38 | 39 | 40 | export default function getComponent(props, ComponentFunc) { 41 | return ( 42 | 43 | {(Module, props) => } 44 | 45 | ) 46 | } -------------------------------------------------------------------------------- /src/Iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1467120074'); /* IE9*/ 4 | src: url('iconfont.eot?t=1467120074#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('iconfont.woff?t=1467120074') format('woff'), /* chrome, firefox */ 6 | url('iconfont.ttf?t=1467120074') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1467120074#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -webkit-text-stroke-width: 0.2px; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | .icon-huifu:before { content: "\e608"; } 19 | .icon-job:before { content: "\e602"; } 20 | .icon-fanhui:before { content: "\e607"; } 21 | .icon-dianzan:before { content: "\e609"; } 22 | .icon-shouye:before { content: "\e600"; } 23 | .icon-fabu:before { content: "\e60b"; } 24 | .icon-share:before { content: "\e603"; } 25 | .icon-wode:before { content: "\e601"; } 26 | .icon-good:before { content: "\e604"; } 27 | .icon-xiaoxi:before { content: "\e60a"; } 28 | .icon-top:before { content: "\e606"; } 29 | .icon-ask:before { content: "\e605"; } 30 | .icon-tuichu:before { content: "\e60c"; } 31 | -------------------------------------------------------------------------------- /src/Component/Signout.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {NavLink as Link } from 'react-router-dom'; 4 | import { connect } from 'react-redux'; 5 | import action from '../Action/Index'; 6 | import { Tool, merged } from '../Tool'; 7 | import { DataLoad, DataNull, Header, TipMsgSignin, Footer } from './common/index'; 8 | 9 | /** 10 | * 模块入口 11 | * 12 | * @class Main 13 | * @extends {Component} 14 | */ 15 | class Main extends Component { 16 | constructor(props) { 17 | super(props); 18 | this.signout = () => { 19 | this.props.signin(); 20 | this.context.router.history.replace({ pathname: '/' }); 21 | } 22 | 23 | } 24 | render() { 25 | return ( 26 |
27 |
28 |
29 |
30 | 31 |
32 |
33 |
34 | ); 35 | } 36 | } 37 | Main.contextTypes = { 38 | router: PropTypes.object.isRequired 39 | } 40 | 41 | 42 | export default connect((state) => { return { User: state.User }; }, action('User'))(Main); //连接redux -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-cnode", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "dev": "node server.js", 8 | "dist": "webpack --progress --colors -p" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/lzxb/react-cnode.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/lzxb/react-cnode/issues" 18 | }, 19 | "homepage": "https://github.com/lzxb/react-cnode#readme", 20 | "dependencies": { 21 | "es6-promise": "^4.1.0", 22 | "flex.css": "^1.1.6", 23 | "get-next-page": "1.0.0", 24 | "github-markdown-css": "^2.4.0", 25 | "normalize.css": "7.0.0", 26 | "obj-merged": "^1.0.5", 27 | "prop-types": "^15.5.10", 28 | "query-string": "^4.3.4", 29 | "react": "^15.0.1", 30 | "react-dom": "^15.0.1", 31 | "react-redux": "^5.0.4", 32 | "react-router": "^4.1.1", 33 | "react-router-dom": "^4.1.1", 34 | "redux": "3.6.0", 35 | "redux-thunk": "2.2.0" 36 | }, 37 | "devDependencies": { 38 | "autoprefixer": "^7.1.1", 39 | "autoprefixer-loader": "^3.2.0", 40 | "babel-core": "^6.18.0", 41 | "babel-loader": "^7.0.0", 42 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 43 | "babel-preset-es2015": "^6.6.0", 44 | "babel-preset-react": "^6.5.0", 45 | "babel-preset-stage-2": "^6.24.1", 46 | "body-parser": "^1.15.1", 47 | "css-loader": "^0.28.1", 48 | "extract-text-webpack-plugin": "2.1.0", 49 | "file-loader": "^0.11.1", 50 | "html-webpack-plugin": "^2.22.0", 51 | "jsx-loader": "^0.13.2", 52 | "less": "^2.6.1", 53 | "less-loader": "^4.0.3", 54 | "postcss-loader": "^2.0.5", 55 | "style-loader": "^0.17.0", 56 | "sugarss": "^1.0.0", 57 | "url-loader": "^0.5.7", 58 | "webpack": "2.5.1", 59 | "webpack-dev-server": "2.4.5" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Reducer/Index.jsx: -------------------------------------------------------------------------------- 1 | import { Tool, merged } from '../Tool'; 2 | /** 3 | * 存储登录的用户信息 4 | * 5 | * @param {string} [state=JSON.parse(Tool.localItem('User'))] 6 | * @param {Object} action 7 | * @returns Object 8 | */ 9 | const User = (state = JSON.parse(Tool.localItem('User')), action) => { 10 | 11 | switch (action.type) { 12 | case 'signinSuccess': //登录成功 13 | Tool.localItem('User', JSON.stringify(action.target)); 14 | return action.target; 15 | case 'signin': //退出 16 | Tool.removeLocalItem('User'); 17 | return null; 18 | default: 19 | return state; 20 | } 21 | 22 | } 23 | 24 | 25 | const DB = (_ID = '', setting = {}) => { 26 | const cb = { 27 | setDefault: () => { 28 | var defaults = merged({ 29 | path: '', //当前页面的href 30 | loadAnimation: true, //true显示加载动画,false 不显示加载动画 31 | loadMsg: '加载中', //加载提示 32 | data: null, //页面的数据 33 | scrollX: 0, //滚动条X 34 | scrollY: 0, //滚动条Y 35 | mdrender: true //当为 false 时,不渲染。默认为 true,渲染出现的所有 markdown 格式文本。 36 | }, setting); 37 | return { 38 | defaults, 39 | path: {} 40 | }; 41 | }, 42 | setState: (state, target) => { 43 | state.path[target.path] = target; 44 | return merged(state); 45 | } 46 | } 47 | return (state = {}, action = {}) => { 48 | 49 | if (action._ID && action._ID !== _ID) { 50 | return state; 51 | } else if (cb[action.type]) { 52 | return cb[action.type](state, action.target); 53 | } else { 54 | return cb.setDefault(); 55 | } 56 | } 57 | } 58 | const IndexList = DB('IndexList', { page: 1, nextBtn: true, limit: 10, mdrender: false, data: [] }); //首页 59 | const Topic = DB('Topic'); //主题详情 60 | const MyMessages = DB('MyMessages'); //消息 61 | const UserView = DB('UserView', { tabIndex: 0 }); //用户详情 62 | export default { IndexList, Topic, MyMessages, UserView, User } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 此项目除了正常的bug修复,不再进行功能更新 3 | 如果对状态管理感兴趣,可以看下 [Tms](https://github.com/FollowmeTech/tms),文档更齐全 4 | 5 | # react-cnode 6 | 基于webpack + react + react-router + redux + less + flex.css + ES6 的React版cnode社区 7 | 8 | ### demo 9 | [![demo](https://github.com/lzxb/react-cnode/raw/master/shot/QR-code.png)](http://lzxb.github.io/react-cnode/) 10 | 11 | ### 下载 12 | ``` 13 | git clone https://github.com/lzxb/react-cnode.git 14 | cd react-cnode 15 | npm install (安装依赖模块) 16 | npm install webpack -g (没有安装webpack的需要安装) 17 | ``` 18 | 19 | ### 运行(nodejs 6.0+) 20 | ``` 21 | npm run dev (开发版本访问:http://localhost:3000/) 22 | npm run dist (发布生产版本) 23 | 24 | ``` 25 | ### 功能 26 | ``` 27 | 1.登录退出 28 | 2.列表分页,查看帖子 29 | 3.发帖,回复帖子 30 | 4.我的消息 31 | 5.个人中心 32 | 6.查看别人的资料 33 | ``` 34 | 35 | ### 总结 36 | ``` 37 | 1.UI是自己设计的,虽然我并不会PS这些工具。 38 | 2.使用了flex.css模块布局,最大的感觉就是在写css不需要考虑在css中如何写布局,大大的提高了我的效率。 39 | 3.在移动端中,列表数据达到上百条之后,性能仍然是不容乐乎,有待于进一步的优化。 40 | 4.ES6中的箭头函数和变量解构赋值,最大的感受在开发效率上。提高很多。 41 | 5.使用高阶组件封装获取数据的流程,让页面组件专注于页面渲染,避免了每个页面都需要写一次获取数据的流程,提高开发效率 42 | 6.redux听起来很美好,在实际操作的过程中,大大的复杂了创建一个页面的难度,最后只能将其封装起来,简化这个过程(其实我对redux不怎么理解) 43 | 7.为了还原页面状态,比如后退时的滚动条位置,还是花费了不少功夫 44 | 8.开发移动到应用,还是使用字体图标爽。 45 | 9.借助webpack可以生成离线缓存清单,px转rem,ES6编译成ES5,模块化开发,代码压缩混淆...... 46 | 10.前端自动化,工程化,给前端的发展起到了很大的推动作用 47 | ``` 48 | ### 状态树 49 | ![Alt text](https://github.com/lzxb/react-cnode/raw/master/shot/redux-state.jpg) 50 | ### 小广告 51 | ``` 52 | 深圳html5开发者社群:170761660 53 | NodeJS前端分享群:133240225 54 | ``` 55 | ### 截图 56 | 57 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/1.png) 58 | 59 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/2.png) 60 | 61 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/3.png) 62 | 63 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/4.png) 64 | 65 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/5.png) 66 | 67 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/6.png) 68 | 69 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/7.png) 70 | 71 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/8.png) 72 | 73 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/9.png) 74 | 75 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/10.png) 76 | 77 | ![截图](https://github.com/lzxb/react-cnode/raw/master/shot/11.png) 78 | 79 | -------------------------------------------------------------------------------- /src/Component/Signin.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {NavLink as Link } from 'react-router-dom'; 4 | import { connect } from 'react-redux'; 5 | import action from '../Action/Index'; 6 | import { Tool, merged } from '../Tool'; 7 | import { DataLoad, DataNull, Header, TipMsgSignin, Footer } from './common/index'; 8 | 9 | /** 10 | * 模块入口 11 | * 12 | * @class Main 13 | * @extends {Component} 14 | */ 15 | class Main extends Component { 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | button: '登录' 20 | }; 21 | this.signin = () => { 22 | var accesstoken = this.refs.accesstoken.value; 23 | if (!accesstoken) return alert('不能为空!'); 24 | this.setState({ button: '登录中...' }); 25 | Tool.post('/api/v1/accesstoken', { accesstoken }, (res) => { 26 | if (res.success) { 27 | alert('登录成功'); 28 | res.accesstoken = accesstoken; 29 | this.props.signinSuccess(res); 30 | this.context.router.history.push({ 31 | pathname: '/user/' + res.loginname 32 | }); 33 | } else { 34 | alert('登录失败'); 35 | this.setState({ button: '登录' }); 36 | } 37 | 38 | }, () => { 39 | alert('登录失败!'); 40 | this.setState({ button: '登录' }); 41 | }); 42 | } 43 | 44 | } 45 | render() { 46 | return ( 47 |
48 |
49 |
50 |
51 |
52 | 53 |
54 |
55 |
56 | ); 57 | } 58 | } 59 | Main.contextTypes = { 60 | router: PropTypes.object.isRequired 61 | } 62 | 63 | 64 | export default connect((state) => { return { User: state.User }; }, action('User'))(Main); //连接redux -------------------------------------------------------------------------------- /src/Iconfont/demo.css: -------------------------------------------------------------------------------- 1 | *{margin: 0;padding: 0;list-style: none;} 2 | /* 3 | KISSY CSS Reset 4 | 理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。 5 | 2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。 6 | 3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。 7 | 特色:1. 适应中文;2. 基于最新主流浏览器。 8 | 维护:玉伯, 正淳 9 | */ 10 | 11 | /** 清除内外边距 **/ 12 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */ 13 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 14 | pre, /* text formatting elements 文本格式元素 */ 15 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */ 16 | th, td /* table elements 表格元素 */ { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | /** 设置默认字体 **/ 22 | body, 23 | button, input, select, textarea /* for ie */ { 24 | font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif; 25 | } 26 | h1, h2, h3, h4, h5, h6 { font-size: 100%; } 27 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 28 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */ 29 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 30 | 31 | /** 重置列表元素 **/ 32 | ul, ol { list-style: none; } 33 | 34 | /** 重置文本格式元素 **/ 35 | a { text-decoration: none; } 36 | a:hover { text-decoration: underline; } 37 | 38 | 39 | /** 重置表单元素 **/ 40 | legend { color: #000; } /* for ie6 */ 41 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */ 42 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */ 43 | /* 注:optgroup 无法扶正 */ 44 | 45 | /** 重置表格元素 **/ 46 | table { border-collapse: collapse; border-spacing: 0; } 47 | 48 | /* 清除浮动 */ 49 | .ks-clear:after, .clear:after { 50 | content: '\20'; 51 | display: block; 52 | height: 0; 53 | clear: both; 54 | } 55 | .ks-clear, .clear { 56 | *zoom: 1; 57 | } 58 | 59 | .main {padding: 30px 100px;} 60 | .main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;} 61 | 62 | .helps{margin-top:40px;} 63 | .helps pre{ 64 | padding:20px; 65 | margin:10px 0; 66 | border:solid 1px #e7e1cd; 67 | background-color: #fffdef; 68 | overflow: auto; 69 | } 70 | 71 | .icon_lists li{ 72 | float:left; 73 | width: 100px; 74 | height:180px; 75 | text-align: center; 76 | } 77 | .icon_lists .icon{ 78 | font-size: 42px; 79 | line-height: 100px; 80 | margin: 10px 0; 81 | color:#333; 82 | -webkit-transition: font-size 0.25s ease-out 0s; 83 | -moz-transition: font-size 0.25s ease-out 0s; 84 | transition: font-size 0.25s ease-out 0s; 85 | 86 | } 87 | .icon_lists .icon:hover{ 88 | font-size: 100px; 89 | } 90 | -------------------------------------------------------------------------------- /src/Config/Route.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter, HashRouter, Switch, Route, Redirect} from 'react-router-dom'; 3 | import createBrowserHistory from 'history/createBrowserHistory' 4 | 5 | const history = createBrowserHistory(); 6 | 7 | 8 | import IndexList from '../Component/IndexList'; //首页组件 9 | // import Topic from '../Component/Topic'; //主题详情 10 | // import TopicCreate from '../Component/TopicCreate'; //发布主题 11 | // import MyMessages from '../Component/MyMessages'; //我的消息 12 | // import UserView from '../Component/UserView'; //我的个人中心 13 | // import Signin from '../Component/Signin'; //登录 14 | // import Signout from '../Component/Signout'; //退出 15 | import getComponent from '../Component/common/getComponent'; 16 | const routes = [ 17 | { path: '/', 18 | exact: true, 19 | component: IndexList 20 | }, 21 | { path: '/topic/create', 22 | exact: false, 23 | component: (props) => getComponent(props, () => import('../Component/TopicCreate')) 24 | }, 25 | { path: '/topic/:id', 26 | exact: false, 27 | component: (props) => getComponent(props, () => import('../Component/Topic')) 28 | }, 29 | { path: '/my/messages', 30 | exact: false, 31 | component: (props) => getComponent(props, () => import('../Component/MyMessages')) 32 | }, 33 | { path: '/user/:loginname', 34 | exact: false, 35 | component: (props) => getComponent(props, () => import('../Component/UserView')) 36 | }, 37 | { path: '/signin', 38 | exact: false, 39 | component: (props) => getComponent(props, () => import('../Component/Signin')) 40 | }, 41 | { path: '/signout', 42 | exact: false, 43 | component: (props) => getComponent(props, () => import('../Component/Signout')) 44 | } 45 | ]; 46 | /** 47 | * (路由根目录组件,显示当前符合条件的组件) 48 | * 49 | * @class Roots 50 | * @extends {Component} 51 | */ 52 | class Roots extends Component { 53 | render() { 54 | return ( 55 |
{this.props.children}
56 | ); 57 | } 58 | } 59 | // var history = process.env.NODE_ENV !== 'production' ? browserHistory : hashHistory; 60 | const supportsHistory = 'pushState' in window.history; 61 | let Router = process.env.NODE_ENV !== 'production' ? BrowserRouter : HashRouter; 62 | const RouteConfig = ( 63 | 64 | 65 | {/**/} 66 | {routes.map((route, index) => ( 67 | 73 | ))} 74 | {/**/} 75 | {/**/} 76 | {/**/} 77 | {/**/} 78 | {/**/} 79 | {/**/} 80 | 81 | 82 | 83 | ); 84 | 85 | export default RouteConfig; -------------------------------------------------------------------------------- /src/Component/MyMessages.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | // import { Router, Route, IndexRoute, browserHistory, Link } from 'react-router'; 3 | import {NavLink as Link } from 'react-router-dom'; 4 | import { connect } from 'react-redux'; 5 | import action from '../Action/Index'; 6 | import { Tool, merged } from '../Tool'; 7 | import { DataLoad, DataNull, Header, TipMsgSignin, Footer, GetData, UserHeadImg } from './common/index'; 8 | 9 | /** 10 | * 模块入口 11 | * 12 | * @class Main 13 | * @extends {Component} 14 | */ 15 | class Main extends Component { 16 | render() { 17 | var {data, loadAnimation, loadMsg, id, tabIndex} = this.props.state; 18 | var {User, params} = this.props; 19 | var main = null; 20 | if (!User) { 21 | main = 22 | } else if (!data) { 23 | main = ; 24 | } else { 25 | let {hasnot_read_messages, has_read_messages} = data; 26 | Array.prototype.push.apply(hasnot_read_messages, has_read_messages); 27 | main = ; 28 | } 29 | 30 | return ( 31 |
32 |
33 | {main} 34 |
35 |
36 | ); 37 | } 38 | } 39 | 40 | /** 41 | * 消息内容 42 | * 43 | * @class Content 44 | * @extends {Component} 45 | */ 46 | class Content extends Component { 47 | render() { 48 | var list = this.props.list; 49 | return ( 50 |
51 |
    52 | { 53 | list.map((item, index) => { 54 | var {type, author, topic, reply, has_read} = item; 55 | var content = null; 56 | 57 | if (type == 'at') { 58 | content =
    在话题{topic.title}中 @了你
    ; 59 | } else { 60 | content =
    回复你了的话题{topic.title}
    61 | } 62 | return ( 63 |
  • 64 | 65 | 66 | 67 |
    68 |
    {author.loginname}
    69 |
    70 |
    71 | {content} 72 |
    73 |
    74 |
  • 75 | ); 76 | }) 77 | } 78 |
79 |
80 | ); 81 | } 82 | } 83 | export default GetData({ 84 | id: 'MyMessages', //应用关联使用的redux 85 | component: Main, //接收数据的组件入口 86 | url: '/api/v1/messages', //服务器请求的地址 87 | stop: (props, state) => { 88 | return !Boolean(props.User); //true 拦截请求,false不拦截请求 89 | }, 90 | data: (props, state) => { //发送给服务器的数据 91 | return { accesstoken: props.User.accesstoken } 92 | }, 93 | success: (state) => { return state; }, //请求成功后执行的方法 94 | error: (state) => { return state } //请求失败后执行的方法 95 | }); 96 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); //css单独打包 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); //生成html 4 | 5 | var publicPath = '/dist/'; //服务器路径 6 | var path = __dirname + '/dist/'; 7 | 8 | var plugins = []; 9 | 10 | if (process.argv.indexOf('-p') > -1) { //生产环境 11 | plugins.push(new webpack.DefinePlugin({ //编译成生产版本 12 | 'process.env': { 13 | NODE_ENV: JSON.stringify('production') 14 | } 15 | })); 16 | publicPath = '/react-cnode/dist/'; 17 | path = __dirname + '/react-cnode/dist/'; 18 | } 19 | plugins.push(new ExtractTextPlugin({ 20 | filename:'[name].css' 21 | })); //css单独打包 22 | 23 | plugins.push(new HtmlWebpackPlugin({ //根据模板插入css/js等生成最终HTML 24 | filename: '../index.html', //生成的html存放路径,相对于 path 25 | template: './src/template/index.html', //html模板路径 26 | hash: true,    //为静态资源生成hash值 27 | })); 28 | 29 | module.exports = { 30 | entry: { 31 | app: './src/App', //编译的入口文件 32 | }, 33 | output: { 34 | publicPath, //编译好的文件,在服务器的路径 35 | path, //编译到当前目录 36 | filename: '[name].js' //编译后的文件名字 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.js$/, 42 | exclude: /^node_modules$/, 43 | use: [ 44 | { 45 | loader: "babel-loader", 46 | options: { 47 | presets: ['es2015'], 48 | compact: 'false', 49 | plugins: ['syntax-dynamic-import'] 50 | } 51 | } 52 | ] 53 | }, { 54 | test: /\.css$/, 55 | exclude: /^node_modules$/, 56 | use: ExtractTextPlugin.extract({ 57 | fallback: "style-loader", 58 | use: [ 59 | { loader: 'css-loader', options: { importLoaders: 1 } }, 60 | 'postcss-loader' 61 | ] 62 | }) 63 | // loader: ExtractTextPlugin.extract('style-loader', 'css-loader!autoprefixer-loader') 64 | }, { 65 | test: /\.less/, 66 | exclude: /^node_modules$/, 67 | use: ExtractTextPlugin.extract({ 68 | fallback: "style-loader", 69 | use: [ 70 | { loader: 'css-loader', options: { importLoaders: 1 } }, 71 | 'postcss-loader', 72 | { 73 | loader: 'less-loader' 74 | } 75 | ] 76 | }) 77 | // loader: ExtractTextPlugin.extract('style-loader', 'css-loader!autoprefixer-loader!less-loader') 78 | }, { 79 | test: /\.(eot|woff|svg|ttf|woff2|gif|appcache)(\?|$)/, 80 | exclude: /^node_modules$/, 81 | use: [ 82 | { 83 | loader: "file-loader", 84 | options: { 85 | name: '[name].[ext]' 86 | } 87 | } 88 | ] 89 | // loader: 'file-loader?name=[name].[ext]' 90 | }, { 91 | test: /\.(png|jpg)$/, 92 | exclude: /^node_modules$/, 93 | use: [ 94 | { 95 | loader: "url-loader", 96 | options: { 97 | limit: 20000, 98 | name: '[name].[ext]' 99 | } 100 | } 101 | ] 102 | // loader: 'url?limit=20000&name=[name].[ext]' //注意后面那个limit的参数,当你图片大小小于这个限制的时候,会自动启用base64编码图片 103 | }, { 104 | test: /\.jsx$/, 105 | exclude: /^node_modules$/, 106 | use: [ 107 | { 108 | loader: "babel-loader", 109 | options: { 110 | presets: [ 111 | "es2015", 112 | "react" 113 | ], 114 | plugins: ['syntax-dynamic-import'] 115 | } 116 | } 117 | ] 118 | // loaders: ['jsx', 'babel?presets[]=es2015,presets[]=react'] 119 | } 120 | ] 121 | }, 122 | plugins, 123 | resolve: { 124 | extensions: ['.js', '.jsx'], //后缀名自动补全 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /src/Component/UserView.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import {NavLink as Link } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import action from '../Action/Index'; 5 | import { Tool, merged } from '../Tool'; 6 | import { DataLoad, DataNull, Header, TipMsgSignin, Footer, UserHeadImg, GetData } from './common/index'; 7 | 8 | /** 9 | * 模块入口 10 | * 11 | * @class Main 12 | * @extends {Component} 13 | */ 14 | class Main extends Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = this.props.state; 18 | this.tab = (tabIndex) => { 19 | this.state.tabIndex = tabIndex; 20 | this.props.setState(this.state); 21 | } 22 | } 23 | render() { 24 | var {data, loadAnimation, loadMsg, id, tabIndex} = this.props.state; 25 | let { User, match} = this.props; 26 | let params = match.params; 27 | User = User ? User : {}; 28 | var main = data ? : ; 29 | var title = params.loginname === User.loginname ? '个人中心' : params.loginname + '的个人中心'; 30 | var footer = params.loginname === User.loginname ?