├── .babelrc ├── app ├── assets │ ├── img │ │ └── loading.gif │ └── scss │ │ ├── layout.scss │ │ ├── about.scss │ │ ├── items.scss │ │ ├── variables.scss │ │ └── main.scss ├── constants │ ├── actionType.js │ └── config.js ├── utils │ ├── objectEmpty.js │ └── getDate.js ├── compontens │ ├── index.js │ ├── footer.js │ ├── app.js │ ├── detail.js │ ├── navBar.js │ ├── about.js │ └── itemsList.js ├── reducers │ ├── detail.js │ ├── index.js │ └── items.js ├── containers │ ├── detail.js │ └── items.js ├── routers │ └── index.js ├── store │ └── configureStore.js ├── app.js └── actions │ └── index.js ├── .travis.yml ├── .gitignore ├── __test__ ├── reducers │ └── index.test.js └── compontent │ └── list.test.js ├── index.html ├── LICENSE ├── webpack.config.js ├── webpack.pdct.config.js ├── README.md └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react","env"], 3 | "plugins": [["antd"]] 4 | } -------------------------------------------------------------------------------- /app/assets/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hancoson/react-redux-demo/HEAD/app/assets/img/loading.gif -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | after_success: 5 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #OS 2 | .DS_Store 3 | Thumbs.db 4 | .history 5 | 6 | #page 7 | index.html 8 | 9 | #node 10 | /node_modules 11 | npm-debug.log* 12 | .log 13 | 14 | #IDE 15 | .idea 16 | 17 | #www -------------------------------------------------------------------------------- /app/constants/actionType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * actionTypes 3 | * @author Guoxing.Han(hancoson#163.com) 4 | * @time 2016/12/29. 5 | */ 6 | import keyMirror from 'keymirror'; 7 | 8 | export default keyMirror({ 9 | 'EMPTYDATA': null, 10 | 'GETSUCCESS': null, 11 | 'DETAILDATA': null 12 | }); -------------------------------------------------------------------------------- /app/utils/objectEmpty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/11. 4 | */ 5 | 6 | export const isEmptyObject = obj => { 7 | for (var key in obj) { 8 | if (obj.hasOwnProperty(key)) { 9 | return false; 10 | } 11 | } 12 | return true; 13 | } -------------------------------------------------------------------------------- /app/compontens/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | 6 | import React, { Component } from 'react' 7 | 8 | export default class Index extends Component { 9 | 10 | render() { 11 | return ( 12 |
Hello Word!
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/compontens/footer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/11. 4 | */ 5 | import React, { Component } from 'react' 6 | export default class Footer extends Component { 7 | 8 | render() { 9 | return ( 10 | 14 | ) 15 | } 16 | } -------------------------------------------------------------------------------- /__test__/reducers/index.test.js: -------------------------------------------------------------------------------- 1 | import * as actions from '../../app/actions/index' 2 | import types from '../../app/constants/actionType' 3 | 4 | console.log(types) 5 | describe('actions', () => { 6 | it('should create an action to add a todo', () => { 7 | const text = 'Finish docs' 8 | const expectedAction = { 9 | type: types.GETSUCCESS, 10 | text 11 | } 12 | expect(actions.fetchPosts(text)).toEqual(expectedAction) 13 | }) 14 | }) -------------------------------------------------------------------------------- /app/constants/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/10. 4 | */ 5 | export default { 6 | URL_PREFIX : 'http://daily.zhihu.com/story/', 7 | API : 'http://news-at.zhihu.com/api/4/news/before/',//消息列表 8 | NEWS : 'http://news-at.zhihu.com/api/4/news/',//消息内容获取 9 | YAHOO : 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20json%20where%20url=%22', 10 | YAHOO_SUFFIX: '%22&format=json' 11 | }; -------------------------------------------------------------------------------- /app/reducers/detail.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/5. 4 | */ 5 | import actionType from '../constants/actionType'; 6 | 7 | const initialState = {} 8 | 9 | const detailReducer = (state = initialState, action) => { 10 | 11 | switch (action.type) { 12 | case actionType.DETAILDATA: 13 | return action.data; 14 | case actionType.EMPTYDATA: 15 | return {}; 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default detailReducer; -------------------------------------------------------------------------------- /app/reducers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/3. 4 | */ 5 | 6 | import { combineReducers } from 'redux' // 利用combineReducers 合并reducers 7 | //import { combineReducers } from 'redux-immutablejs' 8 | import { routerReducer } from 'react-router-redux' // 将routerReducer一起合并管理 9 | import itemsReducer from './items' 10 | import detailReducer from './detail' 11 | 12 | export default combineReducers({ 13 | itemsReducer, 14 | detailReducer, 15 | routing: routerReducer 16 | }) -------------------------------------------------------------------------------- /app/containers/detail.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | 6 | import {connect} from 'react-redux' 7 | import {fetchDetailPosts, emptyData} from '../actions/index' 8 | import Detail from '../compontens/detail' 9 | 10 | const fetchDetailData = id => { 11 | return (dispatch) => { 12 | return dispatch(fetchDetailPosts(id)) 13 | } 14 | } 15 | const getDetail = state => { 16 | return {detail: state.detailReducer} 17 | } 18 | 19 | export default connect(getDetail, {fetchDetailData, emptyData})(Detail) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-redux|知乎日报 6 | 7 | 8 | react-redux 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/compontens/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | import React, { Component } from 'react' 6 | 7 | import NavBar from '../compontens/navBar' 8 | import Footer from '../compontens/footer' 9 | 10 | export default class App extends Component { 11 | render() { 12 | return ( 13 |
14 | 15 |
16 | {this.props.children} 17 |
18 |
20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /app/reducers/items.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/5. 4 | */ 5 | import actionType from '../constants/actionType'; 6 | 7 | const initialState = { 8 | data: '', 9 | stories: [] 10 | } 11 | 12 | const itemsReducer = (state = initialState, action) => { 13 | if (action) { 14 | switch (action.type) { 15 | case actionType.GETSUCCESS: 16 | return action.data; 17 | 18 | case actionType.EMPTYDATA: 19 | return initialState; 20 | default: 21 | return state; 22 | } 23 | } 24 | }; 25 | 26 | export default itemsReducer; 27 | -------------------------------------------------------------------------------- /app/routers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | import React from 'react' // 引入react 6 | import { Route, IndexRoute } from 'react-router' // 引入react路由 7 | 8 | import App from './../compontens/app' 9 | import Index from './../compontens/index' 10 | import About from './../compontens/about' 11 | import Items from './../containers/items' 12 | import Detail from './../containers/detail' 13 | 14 | export default ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | ) -------------------------------------------------------------------------------- /app/assets/scss/layout.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @author Guoxing.Han(hancoson#163.com) 4 | * @time 2016/11/14. 5 | */ 6 | @import 'variables'; 7 | body{ 8 | } 9 | .main{ 10 | width: 1200px; 11 | position: relative; 12 | margin: 0 auto; 13 | } 14 | section{ 15 | @extend .border-box; 16 | width: 100%; 17 | margin-top: 40px; 18 | } 19 | 20 | 21 | .index-warp{ 22 | text-align: center; 23 | padding: 100px 0 24 | } 25 | 26 | 27 | footer{ 28 | width: 100%; 29 | text-align: center; 30 | padding: 20px 0; 31 | color: #999; 32 | font-size: 13px; 33 | background: #fff; 34 | border-top:#f0f0f0 1px solid; 35 | margin-top: 20px; 36 | } 37 | 38 | .loading{ 39 | margin: 100px auto; 40 | text-align: center; 41 | } 42 | -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/3. 4 | */ 5 | import thunk from 'redux-thunk' // redux-thunk 支持 dispatch function,并且可以异步调用它 6 | import createLogger from 'redux-logger' // 利用redux-logger打印日志 7 | import { createStore, applyMiddleware, compose } from 'redux' // 引入redux createStore、中间件及compose 8 | 9 | // 调用日志打印方法 10 | const loggerMiddleware = createLogger() 11 | 12 | // 创建一个中间件集合 13 | const middleware = [thunk, loggerMiddleware] 14 | 15 | // 利用compose增强store,这个 store 与 applyMiddleware 和 redux-devtools 一起使用 16 | const finalCreateStore = compose( 17 | applyMiddleware(...middleware), 18 | window.devToolsExtension ? window.devToolsExtension() : f => f 19 | )(createStore) 20 | 21 | export default finalCreateStore -------------------------------------------------------------------------------- /app/utils/getDate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @author Guoxing.Han(hancoson#163.com) 4 | * @time 2017/1/10. 5 | */ 6 | //获取当天时间并格式化 7 | export const getDate = () => { 8 | let d = new Date(); 9 | let m = d.getMonth() + 1; 10 | let dd = d.getDate(); 11 | if (m < 10) { 12 | m = '0' + m.toString() 13 | } 14 | if (dd < 10) { 15 | dd = '0' + dd.toString() 16 | } 17 | let str = d 18 | .getFullYear() 19 | .toString() + m + dd.toString(); 20 | return str 21 | } 22 | 23 | //格式化时间 24 | export const subString = str => { 25 | let _y = '', 26 | _m = '', 27 | _d = ''; 28 | _y = str.substring(0, 4); 29 | _m = str.substring(4, 6); 30 | _d = str.substring(6, 8); 31 | return (_y + '/' + _m + '/' + _d) 32 | 33 | } 34 | //格式化时间 35 | export const timeClear = str => { 36 | let _t = ''; 37 | str 38 | .split('-') 39 | .forEach((i) => { 40 | _t = _t + i.toString() 41 | }) 42 | return _t 43 | 44 | } -------------------------------------------------------------------------------- /app/containers/items.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | import React, { Component } from 'react' 6 | import { connect } from 'react-redux' 7 | import { fetchPosts, emptyData } from '../actions/index' 8 | import Items from '../compontens/itemsList' 9 | import { getDate } from './../utils/getDate' 10 | 11 | //通过日期选择获取数据 12 | // mapDispatchToProps()方法接收 dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法 13 | const fetchData = (t) => { 14 | return (dispatch) => { 15 | return dispatch(fetchPosts(t)) 16 | } 17 | } 18 | // 默认获取数据 19 | const defaultFetchData = () => { 20 | return (dispatch) => { 21 | return dispatch(fetchPosts(getDate())) 22 | } 23 | } 24 | 25 | const getItems = state => { 26 | return { items: state.itemsReducer } 27 | } 28 | 29 | /** 30 | * @getItems (state) 31 | * @defaultFetchData, @fetchData, @emptyData (dispatch) 32 | */ 33 | export default connect(getItems, { defaultFetchData, fetchData, emptyData })(Items) -------------------------------------------------------------------------------- /app/assets/scss/about.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/13. 4 | */ 5 | .about-warp { 6 | h1 { 7 | padding-bottom: 5px; 8 | border-bottom: 1px #f0f0f0 solid; 9 | font-size: 32px; 10 | margin-bottom: 20px; 11 | } 12 | ul { 13 | padding-left: 20px; 14 | li { 15 | line-height: 1.6; 16 | list-style: circle; 17 | } 18 | } 19 | h2 { 20 | font-size: 26px; 21 | } 22 | code{ 23 | padding: 2px 4px; 24 | font-size: 90%; 25 | color: #c7254e; 26 | background-color: #f9f2f4; 27 | border-radius: 4px; 28 | font-family: Menlo,Monaco,Consolas,"Courier New",monospace; 29 | } 30 | pre{ 31 | display: block; 32 | padding: 9.5px; 33 | margin: 0 0 10px; 34 | font-size: 13px; 35 | line-height: 1.42857143; 36 | color: #333; 37 | word-break: break-all; 38 | word-wrap: break-word; 39 | background-color: #f5f5f5; 40 | border: 1px solid #ccc; 41 | border-radius: 4px; 42 | } 43 | } -------------------------------------------------------------------------------- /__test__/compontent/list.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Items from '../../app/compontens/itemsList' 3 | import { render } from 'enzyme' 4 | 5 | const setup = () => { 6 | const props = { 7 | items: { 8 | stories: [ 9 | { 10 | images: 'images-1', 11 | title: 'title-1', 12 | date: '2018-05-29 10:00:00' 13 | }, 14 | { 15 | images: 'images-2', 16 | title: 'title-2', 17 | date: '2018-05-29 10:00:00' 18 | }, 19 | { 20 | images: 'images-3', 21 | title: 'title-3', 22 | date: '2018-05-29 10:00:00' 23 | }, 24 | ], 25 | }, 26 | emptyData: jest.fn() 27 | } 28 | const wrapper = render() 29 | return { 30 | props, 31 | wrapper 32 | } 33 | } 34 | 35 | describe('itemList', () => { 36 | const { props, wrapper } = setup() 37 | it('itemList length should be 3', () => { 38 | expect(wrapper.find('a').length).toBe(3) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /app/compontens/detail.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/1/11. 4 | */ 5 | import React, { Component } from 'react' 6 | import { Spin } from 'antd'; 7 | import { isEmptyObject } from './../utils/objectEmpty' 8 | 9 | export default class Detail extends Component { 10 | componentWillMount() { 11 | const { fetchDetailData, emptyData } = this.props; 12 | //调取数据 13 | fetchDetailData(this.props.params.id) 14 | //清空原有的数据 15 | emptyData() 16 | } 17 | 18 | createMarkup() { 19 | var _fonts = this.props.detail.body; 20 | return { __html: _fonts }; 21 | } 22 | 23 | render() { 24 | const _o = this.props.detail; 25 | 26 | console.log(isEmptyObject({})) 27 | return ( 28 |
29 | { 30 | isEmptyObject(_o) ? 31 |
32 | 33 |
: 34 |
35 | } 36 |
37 | 38 | ) 39 | } 40 | } -------------------------------------------------------------------------------- /app/assets/scss/items.scss: -------------------------------------------------------------------------------- 1 | .items-warp { 2 | width: 100%; 3 | margin-top: 10px; 4 | .bar { 5 | width: 100%; 6 | background: #f7f7f7; 7 | padding: 10px; 8 | } 9 | .gutter-example .ant-row > div { 10 | border: 0; 11 | } 12 | .gutter-box { 13 | background: #00A0E9; 14 | padding: 5px 0; 15 | } 16 | .item { 17 | display: block; 18 | padding: 10px; 19 | height: 100px; 20 | position: relative; 21 | background: #f5f5f5; 22 | border-radius: 10px; 23 | margin: 10px 0 10px; 24 | transition: all .2s linear 0s; 25 | &:hover{ 26 | box-shadow: 0 5px 10px rgba(0,0,0,.1); 27 | } 28 | img{ 29 | display: block; 30 | width: 80px; 31 | height: 80px; 32 | float: left; 33 | margin-right: 10px; 34 | } 35 | p{ 36 | font-size: 14px; 37 | line-height: 1.4; 38 | color: #31b9ff; 39 | } 40 | sub{ 41 | font-size: 13px; 42 | position: absolute; 43 | bottom: 13px; 44 | right: 13px; 45 | color: #999; 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Hancoson(hancoson#163.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/compontens/navBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | import React, {Component} from 'react' 6 | import {IndexLink, Link} from 'react-router' 7 | 8 | import {Menu, Icon} from 'antd'; 9 | const SubMenu = Menu.SubMenu; 10 | const MenuItemGroup = Menu.ItemGroup; 11 | export default class NavBar extends Component { 12 | constructor(porps) { 13 | super(porps); 14 | this.state = { 15 | current: this.props.pathname == '/' 16 | ? '/index' 17 | : this.props.pathname 18 | } 19 | } 20 | 21 | handleClick(e) { 22 | console.log('click ', e); 23 | this.setState({current: e.key}); 24 | } 25 | 26 | render() { 27 | return ( 28 | 34 | 35 | 首页 36 | 37 | 38 | 文章 39 | 40 | 41 | 关于 42 | 43 | 44 | ) 45 | } 46 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | var path = require("path"); 6 | var webpack = require('webpack'); 7 | var ExtractTextPlugins = require('extract-text-webpack-plugin'); 8 | 9 | module.exports = { 10 | entry: { 11 | main: [ 12 | './app/app.js', 13 | 'webpack-dev-server/client?http://localhost:8094', 14 | 'webpack/hot/only-dev-server' 15 | ] 16 | }, 17 | output: { 18 | publicPath: 'http://localhost:8094/', 19 | filename: './dist/js/bundle.js' 20 | /*path : path.resolve(__dirname, 'dist'), 21 | publicPath: "./dist/js/", 22 | filename : "bundles.js"*/ 23 | }, 24 | module: { 25 | loaders: [ 26 | { 27 | test: /\.js$/, 28 | exclude: /node_modules/, 29 | loaders: ['react-hot', 'babel?' + JSON.stringify({ presets: ['react', 'es2015'] })] 30 | }, 31 | { 32 | test: /\.scss$/, 33 | loader: 'style!css!sass' 34 | } 35 | ] 36 | }, 37 | plugins: [ 38 | new ExtractTextPlugins('./dist/style/main.css', { 39 | allChunks: true 40 | }) 41 | ], 42 | resolve: { 43 | extension: ['', '.js', '.es6'] 44 | }, 45 | devServer: { 46 | inline: true 47 | /*host: '0.0.0.0', 48 | proxy: [{ 49 | //path: '/admin/!*', 50 | // target: 'http://10.2.82.209:8899' //开发1 51 | }]*/ 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | import Promise from 'promise-polyfill' //支持IE 6 | import React from 'react' // 引入React 7 | import { render } from 'react-dom' // 引入render方法 8 | import { Provider } from 'react-redux' // 利用Provider可以使我们的 store 能为下面的组件所用 9 | import { Router, hashHistory } from 'react-router' // Browser history 是由 React Router 创建浏览器应用推荐的 history 10 | //使用 hashHistory,浏览器上看到的 url 会是这样的: /#/a/a1?_k=adseis 11 | //使用 browserHistory,需要服务端的支持,浏览器上看到的 url 会是这样的:/a/a1 12 | import { syncHistoryWithStore } from 'react-router-redux' // 利用react-router-redux提供的syncHistoryWithStore我们可以结合store同步导航事件 13 | 14 | import finalCreateStore from './store/configureStore' //引入store配置 15 | import reducer from './reducers' // 引入reducers集合 16 | import routes from './routers' // 引入路由配置 17 | 18 | import './assets/scss/main.scss' // 引入样式文件 19 | 20 | // To add to window 21 | if (!window.Promise) window.Promise = Promise; 22 | 23 | // 给增强后的store传入reducer 24 | const store = finalCreateStore(reducer); 25 | 26 | // 创建一个增强版的history来结合store同步导航事件 27 | const history = syncHistoryWithStore(hashHistory, store) 28 | 29 | render( 30 | 31 | 32 | , 33 | document.getElementById('app') 34 | ) 35 | -------------------------------------------------------------------------------- /app/assets/scss/variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 变量 3 | * @author 墨萧 4 | * @time 2016/06/06 5 | * @version 0.1.0 6 | */ 7 | 8 | 9 | /** 10 | * 继承代码 11 | **/ 12 | 13 | //border-box 14 | .border-box { 15 | -moz-box-sizing: border-box; 16 | -webkit-box-sizing: border-box; 17 | -o-box-sizing: border-box; 18 | -ms-box-sizing: border-box; 19 | box-sizing: border-box; 20 | } 21 | 22 | //旋转360 23 | .transition { 24 | -moz-transition: -moz-transform 0.3s ease; 25 | -webkit-transition: -webkit-transform 0.3s ease; 26 | transition: transform 0.3s ease; 27 | } 28 | 29 | //截取长度显示省略号 30 | .ellipsis { 31 | overflow: hidden; 32 | text-overflow: ellipsis; 33 | white-space: nowrap; 34 | } 35 | 36 | //滚动条 37 | .scrollbar::-webkit-scrollbar-track { 38 | -webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.2); 39 | border-radius: 6px; 40 | background-color: #F5F5F5; 41 | } 42 | 43 | .scrollbar::-webkit-scrollbar { 44 | width: 3px; 45 | background-color: #fff; 46 | } 47 | 48 | .scrollbar::-webkit-scrollbar-thumb { 49 | border-radius: 6px; 50 | -webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, .2); 51 | background-color: #07a1d6; 52 | } 53 | 54 | 55 | /** 56 | * flex 57 | **/ 58 | 59 | .flex { 60 | display: -webkit-box; 61 | display: -webkit-flex; 62 | display: -ms-flexbox; 63 | display: flex; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /webpack.pdct.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | var webpack = require('webpack'); 6 | var WebpackStripLoader = require('strip-loader'); 7 | var ExtractTextPlugins = require('extract-text-webpack-plugin'); 8 | 9 | module.exports = { 10 | entry: './app/app.js', 11 | output: { 12 | filename: './dist/js/bundle.js' 13 | }, 14 | module: { 15 | loaders: [ 16 | { 17 | test: /\.js$/, 18 | exclude: /node_modules/, 19 | loaders: ['react-hot', 'babel?' + JSON.stringify({ presets: ['react', 'es2015'] })] 20 | }, 21 | { 22 | test: /\.scss$/, 23 | loader: ExtractTextPlugins.extract(['css', 'sass']) 24 | }, 25 | { 26 | test: [/\.js$/, /\.es6$/], 27 | exclude: /node_modules/, 28 | loader: WebpackStripLoader.loader('console.log') 29 | } 30 | ] 31 | }, 32 | plugins: [ 33 | new ExtractTextPlugins('./dist/style/main.css', { 34 | allChunks: true 35 | }), 36 | new webpack.optimize.UglifyJsPlugin({ 37 | compress: { 38 | warnings: false 39 | } 40 | }), 41 | new webpack.DefinePlugin({ 42 | 'process.env.NODE_ENV': JSON.stringify('production') 43 | }), 44 | new webpack.optimize.DedupePlugin(), 45 | new webpack.optimize.OccurenceOrderPlugin() 46 | ], 47 | resolve: { 48 | extension: ['', '.js', '.es6'] 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /app/actions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 动作生成器 3 | * @author Guoxing.Han(hancoson#163.com) 4 | * @time 2016/12/29. 5 | */ 6 | import fetch from 'isomorphic-fetch'; 7 | import assign from 'object-assign'; 8 | import Config from './../constants/config' 9 | 10 | export const emptyData = () => { 11 | return { 12 | type: 'EMPTYDATA' 13 | } 14 | } 15 | 16 | /** 17 | * 获取列表数据 18 | * 异步 action 返回函数 参数为 dispatch 19 | * @param {*} time 20 | */ 21 | export const fetchPosts = (time) => { 22 | return dispatch => { 23 | return fetch(Config.YAHOO + Config.API + time + Config.YAHOO_SUFFIX) 24 | .then(res => { 25 | return res.json() 26 | }) 27 | .then(data => { 28 | data = data.query.results.json; 29 | dispatch({ 30 | type: 'GETSUCCESS', 31 | data: assign({}, data) 32 | }) 33 | }) 34 | .catch(e => { 35 | console.log(e.message) 36 | }) 37 | } 38 | } 39 | 40 | //获取单条数据 41 | export const fetchDetailPosts = (id) => { 42 | return dispatch => { 43 | return fetch(Config.YAHOO + Config.NEWS + id + Config.YAHOO_SUFFIX) 44 | .then(res => { 45 | return res.json() 46 | }) 47 | .then(data => { 48 | data = data.query.results.json; 49 | console.log(data) 50 | dispatch({ 51 | type: 'DETAILDATA', 52 | data: assign({}, data) 53 | }) 54 | }) 55 | .catch(e => { 56 | console.log(e.message) 57 | }) 58 | } 59 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-redux Zhihu Daily 2 | [![GitHub package version](https://img.shields.io/github/package-json/v/Hancoson/react-redux-demo.svg)](https://github.com/Hancoson/react-redux-demo) 3 | [![GitHub license](https://img.shields.io/github/license/Hancoson/react-redux-demo.svg)](https://github.com/Hancoson/react-redux-demo/blob/master/LICENSE) 4 | [![Travis](https://img.shields.io/travis/Hancoson/react-redux-demo/master.svg)](https://travis-ci.org/Hancoson/react-redux-demo) 5 | 6 | > 一个React+Redux版知乎日报 所有API均来自网络(若涉及侵权,请及时联系我删除) 7 | 8 | > _你觉得好就给个 Star 或者 Watch 吧,不要 Fork._ 9 | 10 | ### [DEOM](https://hancoson.github.io/react-redux-demo) 11 | ### [Mobx 版本](https://github.com/Hancoson/react-mobx-demo) 12 | 13 | ## 技术实现 14 | - [x] React 15 | - [x] Redux 16 | - [x] React-router 17 | - [x] fetch 18 | - [x] ES6 19 | - [x] webpack 20 | - [x] antd 21 | - [x] babel 22 | - [x] react-hot-loader 23 | - [x] jest 24 | 25 | ## 运行 26 | - 安装 `npm install(yarn)` 27 | - 启动 `npm start` 28 | - 构建 `npm run build` 29 | 30 | ## 目录结构 31 | ```$xslt 32 | ├─app 33 | │ ├─actions //redux动作生成器 34 | │ ├─assets //静态资源 35 | │ │ ├─img 36 | │ │ └─scss 37 | │ ├─compontens //UI组建 38 | │ ├─constants //常量 39 | │ ├─containers //包装器 40 | │ ├─reducers //reducers 41 | │ ├─routes //路由 42 | │ ├─store //store 43 | │ └─utils //工具函数 44 | └─dist //发布目录 45 | ├─js 46 | └─style 47 | ``` 48 | 49 | ## API说明 50 | 51 | 本应用调用了开源的「知乎日报 API」,具体可参考官方文档,[传送门>>](https://github.com/izzyleung/ZhihuDailyPurify/wiki/%E7%9F%A5%E4%B9%8E%E6%97%A5%E6%8A%A5-API-%E5%88%86%E6%9E%90) 52 | 53 | ## 数据获取 54 | - 采用Yahoo跨域访问代理 55 | ```$xslt 56 | YAHOO:'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20json%20where%20url=%22', 57 | YAHOO_SUFFIX:'%22&format=json' 58 | ``` 59 | -------------------------------------------------------------------------------- /app/compontens/about.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2016/12/29. 4 | */ 5 | import React, { Component } from 'react' 6 | 7 | export default class About extends Component { 8 | 9 | render() { 10 | 11 | return ( 12 |
13 |
14 |

React-redux Zhihu Daily

15 |
    16 |
  • 一个React+Redux版知乎日报 所有API均来自网络(若涉及侵权,请及时联系我删除)
  • 17 |
  • Github[https://github.com/Hancoson/react-redux-demo]
  • 18 |
19 |
20 |
21 |

技术实现

22 |
    23 |
  • 采用React+Redux+ES6+webpack+antd实现
  • 24 |
  • React-hot-load 热替换
  • 25 |
26 |
27 |
28 |

运行

29 |
    30 |
  • 安装 npm install(yarn)
  • 31 |
  • 启动 npm start
  • 32 |
  • 构建 npm build
  • 33 |
34 |
35 |
36 |

目录结构

37 |
38 |             ├─app
39 | │ ├─actions //redux动作生成器
40 | │ ├─assets //静态资源
41 | │ │ ├─img
42 | │ │ └─scss
43 | │ ├─compontens //UI组建
44 | │ ├─constants //常量
45 | │ ├─containers //包装器
46 | │ ├─reducers //reducers
47 | │ ├─routes //路由
48 | │ ├─store //store
49 | │ └─utils //工具函数
50 | └─dist //发布目录
51 |   ├─js
52 |   └─style 53 |
54 |
55 |
56 | ) 57 | } 58 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux", 3 | "version": "1.1.2", 4 | "description": "A react-redux ZhihuDaily demo", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/Hancoson/react-redux-demo.git" 8 | }, 9 | "license": "MIT", 10 | "scripts": { 11 | "start": "webpack-dev-server --progress --colors --port 8094 --hot --content-base --config ./webpack.config.js --open --history-api-fallback", 12 | "build": "webpack --progress --config ./webpack.pdct.config.js", 13 | "test": "jest" 14 | }, 15 | "author": "hancoson@163.com", 16 | "devEngines": { 17 | "node": ">= 6.x", 18 | "npm": ">= 3.x" 19 | }, 20 | "dependencies": { 21 | "antd": "^2.0.0", 22 | "isomorphic-fetch": "^2.2.1", 23 | "keymirror": "^0.1.1", 24 | "object-assign": "^4.1.0", 25 | "promise-polyfill": "^6.0.2", 26 | "react": "^15.4.1", 27 | "react-dom": "^15.4.1", 28 | "react-redux": "^5.0.1", 29 | "react-router": "^2.0.0", 30 | "react-router-redux": "^4.0.7", 31 | "redux": "^3.2.1", 32 | "redux-logger": "^2.7.4", 33 | "redux-thunk": "^2.1.0" 34 | }, 35 | "devDependencies": { 36 | "babel-core": "^6.4.5", 37 | "babel-eslint": "^7.2.0", 38 | "babel-loader": "^6.2.2", 39 | "babel-plugin-antd": "^0.4.0", 40 | "babel-plugin-transform-object-assign": "^6.8.0", 41 | "babel-preset-env": "^1.2.0", 42 | "babel-preset-es2015": "^6.3.13", 43 | "babel-preset-react": "^6.3.13", 44 | "babel-preset-stage-1": "^6.3.13", 45 | "css-loader": "^0.26.1", 46 | "enzyme": "^3.3.0", 47 | "eslint": "^3.18.0", 48 | "eslint-config-rackt": "^1.1.1", 49 | "eslint-plugin-react": "^3.16.1", 50 | "extract-text-webpack-plugin": "^1.0.1", 51 | "file-loader": "^0.9.0", 52 | "jest": "^23.0.1", 53 | "node-sass": "^3.13.0", 54 | "react-hot-loader": "^1.3.1", 55 | "redux-devtools": "^3.3.1", 56 | "redux-devtools-dock-monitor": "^1.1.1", 57 | "redux-devtools-log-monitor": "^1.0.11", 58 | "sass-loader": "^4.0.2", 59 | "strip-loader": "^0.1.2", 60 | "style-loader": "^0.13.1", 61 | "url-loader": "^0.5.7", 62 | "webpack": "^1.12.13", 63 | "webpack-dev-server": "^1.14.1" 64 | } 65 | } -------------------------------------------------------------------------------- /app/compontens/itemsList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Guoxing.Han(hancoson#163.com) 3 | * @time 2017/01/06. 4 | */ 5 | import React, {Component} from 'react' 6 | import {Link} from 'react-router' 7 | import {Row, Col, Spin, Button, DatePicker} from 'antd'; 8 | import {subString, timeClear} from './../utils/getDate' 9 | 10 | export default class Items extends Component { 11 | constructor(porps) { 12 | super(porps); 13 | } 14 | 15 | disabledDate(current) { 16 | // can not select days before today and today 17 | return current && current.valueOf() > Date.now(); 18 | } 19 | 20 | handleClick(value, dateString) { 21 | const {fetchData, emptyData} = this.props; 22 | emptyData() 23 | fetchData(timeClear(dateString)); 24 | } 25 | componentWillMount() { 26 | const {emptyData} = this.props; 27 | //清空原有的数据 28 | emptyData() 29 | } 30 | 31 | render() { 32 | const {items, emptyData} = this.props 33 | const list = items 34 | return ( 35 |
36 |
37 | 42 | 43 |
44 |
45 |
46 | {list.stories.length > 0 47 | ? 48 | {list 49 | .stories 50 | .map((e, index) => 51 | 52 | 53 |

{e.title}

54 | {subString(list.date)} 55 | 56 | )} 57 |
58 | :
59 | 60 |
61 | } 62 | 63 |
64 | {/**/} 65 |
66 |
67 | 68 | ) 69 | } 70 | 71 | componentDidMount() { 72 | const {defaultFetchData} = this.props; 73 | //默认调取数据 74 | defaultFetchData() 75 | } 76 | } -------------------------------------------------------------------------------- /app/assets/scss/main.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * reset 3 | * @author 墨萧 4 | * @time 2016/06/06 5 | * @version 0.1.0 6 | */ 7 | 8 | html, 9 | body, 10 | div, 11 | span, 12 | applet, 13 | object, 14 | iframe, 15 | h1, 16 | h2, 17 | h3, 18 | h4, 19 | h5, 20 | h6, 21 | p, 22 | blockquote, 23 | pre, 24 | a, 25 | abbr, 26 | acronym, 27 | address, 28 | big, 29 | cite, 30 | code, 31 | del, 32 | dfn, 33 | em, 34 | img, 35 | ins, 36 | kbd, 37 | q, 38 | s, 39 | samp, 40 | small, 41 | strike, 42 | strong, 43 | sub, 44 | sup, 45 | tt, 46 | var, 47 | b, 48 | u, 49 | i, 50 | center, 51 | dl, 52 | dt, 53 | dd, 54 | ol, 55 | ul, 56 | li, 57 | fieldset, 58 | form, 59 | label, 60 | legend, 61 | table, 62 | caption, 63 | tbody, 64 | tfoot, 65 | thead, 66 | tr, 67 | th, 68 | td, 69 | article, 70 | aside, 71 | canvas, 72 | details, 73 | embed, 74 | figure, 75 | figcaption, 76 | footer, 77 | header, 78 | menu, 79 | nav, 80 | output, 81 | ruby, 82 | section, 83 | summary, 84 | time, 85 | mark, 86 | audio, 87 | video { 88 | margin: 0; 89 | padding: 0; 90 | border: 0; 91 | font: inherit; 92 | } 93 | 94 | * { 95 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 96 | } 97 | 98 | /* HTML5 display-role reset for older browsers */ 99 | 100 | article, 101 | aside, 102 | details, 103 | figcaption, 104 | figure, 105 | footer, 106 | header, 107 | menu, 108 | nav, 109 | section { 110 | display: block; 111 | } 112 | 113 | body { 114 | line-height: 1; 115 | font-family: sans-serif; 116 | } 117 | 118 | ol, 119 | ul, 120 | ul li { 121 | list-style: none; 122 | } 123 | 124 | blockquote, 125 | q { 126 | quotes: none; 127 | } 128 | 129 | blockquote:before, 130 | blockquote:after, 131 | q:before, 132 | q:after { 133 | content: ''; 134 | content: none; 135 | } 136 | 137 | table { 138 | border-collapse: collapse; 139 | border-spacing: 0; 140 | } 141 | 142 | input { 143 | outline: 0; 144 | } 145 | 146 | input, 147 | textarea, 148 | keygen, 149 | select, 150 | button { 151 | font: inherit; 152 | &:focus { 153 | outline: none; 154 | } 155 | } 156 | 157 | button, 158 | a { 159 | border: 0; 160 | cursor: pointer; 161 | } 162 | 163 | a, 164 | a:hover { 165 | text-decoration: none; 166 | } 167 | 168 | .clearfix:before, 169 | .clearfix:after { 170 | display: table; 171 | content: " "; 172 | } 173 | 174 | .clearfix:after { 175 | clear: both; 176 | } 177 | 178 | /* End hide from IE-mac */ 179 | 180 | .no-scroll { 181 | overflow: hidden; 182 | position: relative; 183 | } 184 | 185 | .fl { 186 | float: left; 187 | } 188 | 189 | .fr { 190 | float: right; 191 | } 192 | 193 | audio, 194 | canvas, 195 | video { 196 | display: inline-block; 197 | } 198 | 199 | audio:not([controls]) { 200 | display: none; 201 | height: 0; 202 | } 203 | 204 | html { 205 | font-family: sans-serif; 206 | -webkit-text-size-adjust: 100%; 207 | } 208 | 209 | body { 210 | font-family: 'Helvetica Neue', Helvetica, Arial, Sans-serif; 211 | background: #fff; 212 | padding-top: 0; 213 | margin: 0; 214 | } 215 | 216 | a:focus { 217 | outline: thin dotted; 218 | } 219 | 220 | a:active, 221 | a:hover { 222 | outline: 0; 223 | } 224 | 225 | h1 { 226 | margin: .67em 0; 227 | } 228 | 229 | h1, 230 | h2, 231 | h3, 232 | h4, 233 | h5, 234 | h6 { 235 | font-size: 16px; 236 | } 237 | 238 | abbr[title] { 239 | border-bottom: 1px dotted; 240 | } 241 | 242 | hr { 243 | box-sizing: content-box; 244 | height: 0; 245 | } 246 | 247 | mark { 248 | background: #ff0; 249 | color: #000; 250 | } 251 | 252 | code, 253 | kbd, 254 | pre, 255 | samp { 256 | font-family: monospace, serif; 257 | font-size: 1em; 258 | } 259 | 260 | pre { 261 | white-space: pre-wrap; 262 | } 263 | 264 | q { 265 | quotes: \201C\201D\2018\2019; 266 | } 267 | 268 | small { 269 | font-size: 80%; 270 | } 271 | 272 | sub, 273 | sup { 274 | font-size: 75%; 275 | line-height: 0; 276 | position: relative; 277 | vertical-align: baseline; 278 | } 279 | 280 | sup { 281 | top: -0.5em; 282 | } 283 | 284 | sub { 285 | bottom: -0.25em; 286 | } 287 | 288 | img { 289 | border: 0; 290 | vertical-align: middle; 291 | color: transparent; 292 | font-size: 0; 293 | } 294 | 295 | svg:not(:root) { 296 | overflow: hidden; 297 | } 298 | 299 | figure { 300 | margin: 0; 301 | } 302 | 303 | fieldset { 304 | border: 1px solid silver; 305 | margin: 0 2px; 306 | padding: .35em .625em .75em; 307 | } 308 | 309 | legend { 310 | border: 0; 311 | padding: 0; 312 | } 313 | 314 | table { 315 | border-collapse: collapse; 316 | border-spacing: 0; 317 | overflow: hidden; 318 | } 319 | 320 | a { 321 | text-decoration: none; 322 | } 323 | 324 | blockquote { 325 | border-left: 3px solid #D0E5F2; 326 | font-style: normal; 327 | display: block; 328 | vertical-align: baseline; 329 | font-size: 100%; 330 | margin: .5em 0; 331 | padding: 0 0 0 1em; 332 | } 333 | 334 | ul, 335 | ol { 336 | padding-left: 20px; 337 | } 338 | 339 | .main-wrap { 340 | max-width: 100%; 341 | min-width: 300px; 342 | margin: 0 auto; 343 | } 344 | 345 | .content-wrap { 346 | overflow: hidden; 347 | background-color: #f9f9f9; 348 | } 349 | 350 | .content-wrap a { 351 | word-break: break-all; 352 | } 353 | 354 | .headline { 355 | border-bottom: 4px solid #f6f6f6; 356 | } 357 | 358 | .headline-title.onlyheading { 359 | margin: 20px 0; 360 | } 361 | 362 | .headline img { 363 | max-width: 100%; 364 | vertical-align: top; 365 | } 366 | 367 | .headline-background-link { 368 | line-height: 2em; 369 | position: relative; 370 | display: block; 371 | padding: 20px 45px 20px 20px !important; 372 | } 373 | 374 | .icon-arrow-right { 375 | position: absolute; 376 | top: 50%; 377 | right: 20px; 378 | background-image: url(http://static.daily.zhihu.com/img/share-icons.png); 379 | background-repeat: no-repeat; 380 | display: inline-block; 381 | vertical-align: middle; 382 | background-position: -70px -20px; 383 | width: 10px; 384 | height: 15px; 385 | margin-top: -7.5px; 386 | } 387 | 388 | .headline-background .heading { 389 | color: #999; 390 | font-size: 15px !important; 391 | margin-bottom: 8px; 392 | line-height: 1em; 393 | } 394 | 395 | .headline-background .heading-content { 396 | color: #444; 397 | font-size: 17px !important; 398 | line-height: 1.2em; 399 | } 400 | 401 | .headline-title { 402 | line-height: 1.2em; 403 | color: #000; 404 | font-size: 22px; 405 | margin: 20px 0 10px; 406 | padding: 0 20px !important; 407 | font-weight: bold; 408 | } 409 | 410 | .meta { 411 | white-space: nowrap; 412 | text-overflow: ellipsis; 413 | overflow: hidden; 414 | font-size: 16px; 415 | color: #b8b8b8; 416 | } 417 | 418 | .meta .source-icon { 419 | width: 20px; 420 | height: 20px; 421 | margin-right: 4px; 422 | } 423 | 424 | .meta .time { 425 | float: right; 426 | margin-top: 2px; 427 | } 428 | 429 | .content { 430 | color: #444; 431 | line-height: 1.6em; 432 | font-size: 17px; 433 | margin: 10px 0 20px; 434 | } 435 | 436 | .content img { 437 | max-width: 100%; 438 | display: block; 439 | margin: 30px auto; 440 | } 441 | 442 | .content img+img { 443 | margin-top: 15px; 444 | } 445 | 446 | .content img[src*="zhihu.com/equation"] { 447 | display: inline-block; 448 | margin: 0 3px; 449 | } 450 | 451 | .content a { 452 | color: #259; 453 | } 454 | 455 | .content a:hover { 456 | text-decoration: underline; 457 | } 458 | 459 | .view-more { 460 | margin-bottom: 25px; 461 | text-align: center; 462 | } 463 | 464 | .view-more a { 465 | font-size: 16px; 466 | display: inline-block; 467 | width: 125px; 468 | height: 30px; 469 | line-height: 30px; 470 | background: #f0f0f0; 471 | color: #B8B8B8; 472 | } 473 | 474 | .question { 475 | overflow: hidden; 476 | padding: 0 20px !important; 477 | } 478 | 479 | .question+.question { 480 | border-top: 5px solid #f6f6f6; 481 | } 482 | 483 | .question-title { 484 | line-height: 1.4em; 485 | color: #000; 486 | font-weight: 700; 487 | font-size: 18px; 488 | margin: 20px 0; 489 | } 490 | 491 | .meta .author { 492 | color: #444; 493 | font-weight: 700; 494 | } 495 | 496 | .answer+.answer { 497 | border-top: 2px solid #f6f6f6; 498 | padding-top: 20px; 499 | } 500 | 501 | .footer { 502 | text-align: center; 503 | color: #b8b8b8; 504 | font-size: 13px; 505 | padding: 20px 0; 506 | } 507 | 508 | .footer a { 509 | color: #b8b8b8; 510 | } 511 | 512 | .question .view-more a { 513 | width: 100%; 514 | display: block; 515 | } 516 | 517 | .hot-comment { 518 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 519 | } 520 | 521 | .comment-label { 522 | font-size: 16px; 523 | color: #333; 524 | line-height: 1.5em; 525 | font-weight: 700; 526 | border-top: 1px solid #eee; 527 | border-bottom: 1px solid #eee; 528 | margin: 0; 529 | padding: 9px 20px; 530 | } 531 | 532 | .comment-list { 533 | margin-bottom: 20px; 534 | } 535 | 536 | .comment-item { 537 | font-size: 15px; 538 | color: #666; 539 | border-bottom: 1px solid #eee; 540 | padding: 15px 20px; 541 | } 542 | 543 | .comment-meta { 544 | position: relative; 545 | margin-bottom: 10px; 546 | } 547 | 548 | .comment-meta .author { 549 | vertical-align: middle; 550 | color: #444; 551 | } 552 | 553 | .comment-meta .vote { 554 | position: absolute; 555 | color: #b8b8b8; 556 | font-size: 12px; 557 | right: 0; 558 | } 559 | 560 | .night .comment-label { 561 | color: #b8b8b8; 562 | border-top: 1px solid #303030; 563 | border-bottom: 1px solid #303030; 564 | } 565 | 566 | .night .comment-item { 567 | color: #7f7f7f; 568 | border-bottom: 1px solid #303030; 569 | } 570 | 571 | .icon-vote, 572 | .icon-voted { 573 | background-repeat: no-repeat; 574 | display: inline-block; 575 | vertical-align: 0; 576 | width: 11px; 577 | height: 12px; 578 | margin-right: 4px; 579 | background-image: url(http://static.daily.zhihu.com/img/app/Comment_Vote.png) !important; 580 | } 581 | 582 | .icon-voted { 583 | background-image: url(http://static.daily.zhihu.com/img/app/Comment_Voted.png) !important; 584 | } 585 | 586 | .night .icon-vote { 587 | background-image: url(http://static.daily.zhihu.com/img/app/Dark_Comment_Vote.png) !important; 588 | } 589 | 590 | .img-wrap .headline-title { 591 | bottom: 5px; 592 | } 593 | 594 | .img-wrap .img-source { 595 | right: 10px !important; 596 | font-size: 9px; 597 | } 598 | 599 | .global-header { 600 | position: static; 601 | } 602 | 603 | .button { 604 | width: 60px; 605 | } 606 | 607 | .button i { 608 | margin-right: 0; 609 | } 610 | 611 | .headline .img-place-holder { 612 | height: 200px; 613 | } 614 | 615 | .from-column { 616 | width: 280px; 617 | line-height: 30px; 618 | height: 30px; 619 | padding-left: 90px; 620 | color: #2aacec; 621 | background-image: url(http://static.daily.zhihu.com/img/News_Column_Entrance.png); 622 | box-sizing: border-box; 623 | margin: 0 20px 20px; 624 | } 625 | 626 | .from-column:active { 627 | background-image: url(http://static.daily.zhihu.com/img/News_Column_Entrance_Highlight.png); 628 | } 629 | 630 | .night .headline { 631 | border-bottom: 4px solid #303030; 632 | } 633 | 634 | .night img { 635 | -webkit-mask-image: -webkit-gradient(linear, 0 0, 0 100%, from(rgba(0, 0, 0, 0.7)), to(rgba(0, 0, 0, 0.7))); 636 | } 637 | 638 | body.night, 639 | .night .content-wrap { 640 | background: #343434; 641 | } 642 | 643 | .night .answer+.answer { 644 | border-top: 2px solid #303030; 645 | } 646 | 647 | .night .question+.question { 648 | border-top: 4px solid #303030; 649 | } 650 | 651 | .night .view-more a { 652 | background: #292929; 653 | color: #666; 654 | } 655 | 656 | .night .icon-arrow-right { 657 | background-image: url(http://static.daily.zhihu.com/img/share-icons.png); 658 | background-repeat: no-repeat; 659 | display: inline-block; 660 | vertical-align: middle; 661 | background-position: -70px -35px; 662 | width: 10px; 663 | height: 15px; 664 | } 665 | 666 | .night blockquote, 667 | .night sup { 668 | border-left: 3px solid #666; 669 | } 670 | 671 | .night .content a { 672 | color: #698ebf; 673 | } 674 | 675 | .night .from-column { 676 | color: #2b82ac; 677 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance.png); 678 | } 679 | 680 | .night .from-column:active { 681 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance_Highlight.png); 682 | } 683 | 684 | .large .question-title { 685 | font-size: 24px; 686 | } 687 | 688 | .large .meta { 689 | font-size: 18px; 690 | } 691 | 692 | .large .content { 693 | font-size: 20px; 694 | } 695 | 696 | .large blockquote, 697 | .large sup { 698 | line-height: 1.6; 699 | } 700 | 701 | .meta .meta-item { 702 | -o-text-overflow: ellipsis; 703 | width: 39%; 704 | overflow: hidden; 705 | white-space: nowrap; 706 | text-overflow: ellipsis; 707 | display: inline-block; 708 | color: #929292; 709 | margin-right: 7px; 710 | } 711 | 712 | .headline .meta { 713 | white-space: nowrap; 714 | text-overflow: ellipsis; 715 | overflow: hidden; 716 | font-size: 11px; 717 | color: #b8b8b8; 718 | margin: 15px 0; 719 | padding: 0 20px; 720 | } 721 | 722 | .headline .meta a, 723 | .headline .meta a:hover { 724 | padding-left: 1em; 725 | margin-top: 2px; 726 | float: right; 727 | font-size: 11px; 728 | color: #0066cf; 729 | text-decoration: none; 730 | } 731 | 732 | .highlight { 733 | width: auto; 734 | overflow: auto; 735 | word-wrap: normal; 736 | } 737 | 738 | .highlight::-webkit-scrollbar { 739 | width: 6px; 740 | height: 6px; 741 | } 742 | 743 | .highlight code { 744 | overflow: auto; 745 | } 746 | 747 | .highlight::-webkit-scrollbar-thumb:horizontal { 748 | border-radius: 6px; 749 | background-color: rgba(0, 0, 0, .5); 750 | } 751 | 752 | .highlight::-webkit-scrollbar-thumb:horizontal:hover { 753 | background-color: rgba(0, 0, 0, .6); 754 | } 755 | 756 | .highlight pre { 757 | margin: 0; 758 | white-space: pre; 759 | } 760 | 761 | .highlight .hll { 762 | background-color: #ffc; 763 | } 764 | 765 | .highlight .err { 766 | color: #a61717; 767 | background-color: #e3d2d2; 768 | } 769 | 770 | .highlight .cp { 771 | color: #999; 772 | font-weight: 700; 773 | } 774 | 775 | .highlight .cs { 776 | color: #999; 777 | font-weight: 700; 778 | font-style: italic; 779 | } 780 | 781 | .highlight .gd { 782 | color: #000; 783 | background-color: #fdd; 784 | } 785 | 786 | .highlight .gi { 787 | color: #000; 788 | background-color: #dfd; 789 | } 790 | 791 | .highlight .gu { 792 | color: #aaa; 793 | } 794 | 795 | .highlight .ni { 796 | color: purple; 797 | } 798 | 799 | .highlight .nt { 800 | color: navy; 801 | } 802 | 803 | .highlight .w { 804 | color: #bbb; 805 | } 806 | 807 | .highlight .sr { 808 | color: olive; 809 | } 810 | 811 | [hidden], 812 | .button span { 813 | display: none; 814 | } 815 | 816 | b, 817 | strong, 818 | .highlight .k, 819 | .highlight .o, 820 | .highlight .gs, 821 | .highlight .kc, 822 | .highlight .kd, 823 | .highlight .kn, 824 | .highlight .kp, 825 | .highlight .kr, 826 | .highlight .ow { 827 | font-weight: 700; 828 | } 829 | 830 | dfn, 831 | .highlight .ge { 832 | font-style: italic; 833 | } 834 | 835 | .meta span, 836 | .meta .source { 837 | vertical-align: middle; 838 | } 839 | 840 | .meta .avatar, 841 | .comment-meta .avatar { 842 | width: 20px; 843 | height: 20px; 844 | border-radius: 2px; 845 | margin-right: 5px; 846 | } 847 | 848 | .meta .bio, 849 | .highlight .gh, 850 | .highlight .bp { 851 | color: #999; 852 | } 853 | 854 | .night .comment-meta .author, 855 | .night .content, 856 | .night .meta .author, 857 | .highlight .go { 858 | color: #888; 859 | } 860 | 861 | .night .headline-title, 862 | .night .headline-background .heading-content, 863 | .night .question-title { 864 | color: #B8B8B8; 865 | } 866 | 867 | .highlight .c, 868 | .highlight .cm, 869 | .highlight .c1 { 870 | color: #998; 871 | font-style: italic; 872 | } 873 | 874 | .highlight .gr, 875 | .highlight .gt { 876 | color: #a00; 877 | } 878 | 879 | .highlight .gp, 880 | .highlight .nn { 881 | color: #555; 882 | } 883 | 884 | .highlight .kt, 885 | .highlight .nc { 886 | color: #458; 887 | font-weight: 700; 888 | } 889 | 890 | .highlight .m, 891 | .highlight .mf, 892 | .highlight .mh, 893 | .highlight .mi, 894 | .highlight .mo, 895 | .highlight .il { 896 | color: #099; 897 | } 898 | 899 | .highlight .s, 900 | .highlight .sb, 901 | .highlight .sc, 902 | .highlight .sd, 903 | .highlight .s2, 904 | .highlight .se, 905 | .highlight .sh, 906 | .highlight .si, 907 | .highlight .sx, 908 | .highlight .s1, 909 | .highlight .ss { 910 | color: #d32; 911 | } 912 | 913 | .highlight .na, 914 | .highlight .nb, 915 | .highlight .no, 916 | .highlight .nv, 917 | .highlight .vc, 918 | .highlight .vg, 919 | .highlight .vi { 920 | color: teal; 921 | } 922 | 923 | .highlight .ne, 924 | .highlight .nf { 925 | color: #900; 926 | font-weight: 700; 927 | } 928 | 929 | .answer h1, 930 | .answer h2, 931 | .answer h3, 932 | .answer h4, 933 | .answer h5 { 934 | font-size: 19px; 935 | } 936 | 937 | @media only screen and (-webkit-min-device-pixel-ratio2), 938 | only screen and (min-device-pixel-ratio2) { 939 | .icon-arrow-right { 940 | background-image: url(http://static.daily.zhihu.com/img/share-icons@2x.png); 941 | -webkit-background-size: 82px 55px; 942 | background-size: 82px 55px; 943 | } 944 | .icon-vote, 945 | .icon-voted { 946 | background-image: url(http://static.daily.zhihu.com/img/app/Comment_Vote@2x.png) !important; 947 | background-size: 11px 12px; 948 | } 949 | .icon-voted { 950 | background-image: url(http://static.daily.zhihu.com/img/app/Comment_Voted@2x.png) !important; 951 | } 952 | .night .icon-vote { 953 | background-image: url(http://static.daily.zhihu.com/img/app/Dark_Comment_Vote@2x.png) !important; 954 | } 955 | .from-column { 956 | background-image: url(http://static.daily.zhihu.com/img/News_Column_Entrance@2x.png) !important; 957 | background-size: 280px 30px; 958 | } 959 | .from-column:active { 960 | background-image: url(http://static.daily.zhihu.com/img/News_Column_Entrance_Highlight@2x.png) !important; 961 | } 962 | .night .from-column { 963 | color: #2b82ac; 964 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance@2x.png) !important; 965 | } 966 | .night .from-column:active { 967 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance_Highlight@2x.png) !important; 968 | } 969 | } 970 | 971 | .meta .meta-item { 972 | width: 39%; 973 | overflow: hidden; 974 | white-space: nowrap; 975 | text-overflow: ellipsis; 976 | display: inline-block; 977 | color: #929292; 978 | margin-right: 7px; 979 | } 980 | 981 | .headline .meta { 982 | white-space: nowrap; 983 | text-overflow: ellipsis; 984 | overflow: hidden; 985 | font-size: 11px; 986 | color: #b8b8b8; 987 | margin: 20px 0; 988 | padding: 0 20px; 989 | } 990 | 991 | .headline .meta a, 992 | .headline .meta a:hover { 993 | margin-top: 2px; 994 | float: right; 995 | font-size: 11px; 996 | color: #0066cf; 997 | text-decoration: none; 998 | } 999 | 1000 | .answer h1, 1001 | .answer h2, 1002 | .answer h3, 1003 | .answer h4, 1004 | .answer h5 { 1005 | font-size: 19px; 1006 | } 1007 | 1008 | .origin-source, 1009 | a.origin-source:link { 1010 | display: block; 1011 | margin: 25px 0; 1012 | height: 50px; 1013 | overflow: hidden; 1014 | background: #f0f0f0; 1015 | color: #888; 1016 | position: relative; 1017 | -webkit-touch-callout: none; 1018 | } 1019 | 1020 | .origin-source .source-logo, 1021 | a.origin-source:link .source-logo { 1022 | float: left; 1023 | width: 50px; 1024 | height: 50px; 1025 | margin-right: 10px; 1026 | } 1027 | 1028 | .origin-source .text, 1029 | a.origin-source:link .text { 1030 | line-height: 50px; 1031 | height: 50px; 1032 | font-size: 13px; 1033 | } 1034 | 1035 | .origin-source.with-link .text { 1036 | color: #333; 1037 | } 1038 | 1039 | .origin-source.with-link:after { 1040 | display: block; 1041 | position: absolute; 1042 | border-color: transparent transparent transparent #f0f0f0; 1043 | border-width: 7px; 1044 | border-style: solid; 1045 | height: 0; 1046 | width: 0; 1047 | top: 18px; 1048 | right: 4px; 1049 | line-height: 0; 1050 | content: ""; 1051 | } 1052 | 1053 | .origin-source.with-link:before { 1054 | display: block; 1055 | height: 0; 1056 | width: 0; 1057 | position: absolute; 1058 | top: 18px; 1059 | right: 3px; 1060 | border-color: transparent transparent transparent #000; 1061 | border-width: 7px; 1062 | border-style: solid; 1063 | line-height: 0; 1064 | content: ""; 1065 | } 1066 | 1067 | .origin-source-wrap { 1068 | position: relative; 1069 | background: #f0f0f0; 1070 | } 1071 | 1072 | .origin-source-wrap .focus-link { 1073 | position: absolute; 1074 | right: 0; 1075 | top: 0; 1076 | width: 45px; 1077 | color: #00a2ed; 1078 | height: 50px; 1079 | display: none; 1080 | text-align: center; 1081 | font-size: 12px; 1082 | -webkit-touch-callout: none; 1083 | } 1084 | 1085 | .origin-source-wrap .focus-link .btn-label { 1086 | text-align: center; 1087 | display: block; 1088 | margin-top: 8px; 1089 | border-left: solid 1px #ccc; 1090 | height: 34px; 1091 | line-height: 34px; 1092 | } 1093 | 1094 | .origin-source-wrap.unfocused .focus-link { 1095 | display: block; 1096 | } 1097 | 1098 | .origin-source-wrap.unfocused .origin-source:before, 1099 | .origin-source-wrap.unfocused .origin-source:after { 1100 | display: none; 1101 | } 1102 | 1103 | .night .origin-source-wrap { 1104 | background: #292929; 1105 | } 1106 | 1107 | .night .origin-source-wrap .focus-link { 1108 | color: #116f9e; 1109 | } 1110 | 1111 | .night .origin-source-wrap .btn-label { 1112 | border-left: solid 1px #3f3f3f; 1113 | } 1114 | 1115 | .night .origin-source, 1116 | .night .origin-source.with-link { 1117 | background: #292929; 1118 | color: #666; 1119 | } 1120 | 1121 | .night .origin-source .text, 1122 | .night .origin-source.with-link .text { 1123 | color: #666; 1124 | } 1125 | 1126 | .night .origin-source.with-link:after { 1127 | border-color: transparent transparent transparent #292929; 1128 | } 1129 | 1130 | .night .origin-source.with-link:before { 1131 | border-color: transparent transparent transparent #666; 1132 | } 1133 | 1134 | /* ==== */ 1135 | 1136 | .question-title { 1137 | color: #494b4d; 1138 | } 1139 | 1140 | blockquote { 1141 | color: #9da3a6; 1142 | border-left: 3px solid #Dfe3e6; 1143 | } 1144 | 1145 | .content a { 1146 | color: #4786b3; 1147 | } 1148 | 1149 | .content { 1150 | font-size: 17px; 1151 | color: #616466; 1152 | } 1153 | 1154 | .content-wrap { 1155 | background: #fff; 1156 | } 1157 | 1158 | hr { 1159 | margin: 30px 0; 1160 | border-top-width: 0; 1161 | } 1162 | 1163 | p { 1164 | margin: 20px 0 !important; 1165 | } 1166 | 1167 | .dudu-night .content { 1168 | color: #797b80; 1169 | } 1170 | 1171 | .dudu-night hr { 1172 | color: #27282b; 1173 | border-color: #27282b; 1174 | } 1175 | 1176 | .dudu-night .meta .author, 1177 | .dudu-night .meta .bio { 1178 | color: #555659; 1179 | } 1180 | 1181 | .dudu-night .headline-title, 1182 | .dudu-night .headline-background .heading-content, 1183 | .dudu-night .question-title { 1184 | color: #919499; 1185 | } 1186 | 1187 | .dudu-night .headline { 1188 | border-bottom: none; 1189 | } 1190 | 1191 | .dudu-night img { 1192 | -webkit-mask-image: -webkit-gradient(linear, 0 0, 0 100%, from(rgba(0, 0, 0, 0.7)), to(rgba(0, 0, 0, 0.7))); 1193 | } 1194 | 1195 | body.dudu-night, 1196 | .dudu-night .content-wrap { 1197 | background: #1d1e1f; 1198 | } 1199 | 1200 | .dudu-night .answer+.answer { 1201 | border-top: 2px solid #27282b; 1202 | } 1203 | 1204 | .dudu-night .question+.question { 1205 | border-top: 4px solid #27282b; 1206 | } 1207 | 1208 | .dudu-night .view-more a { 1209 | background: #1d1e1f; 1210 | color: #396280; 1211 | } 1212 | 1213 | .dudu-night .icon-arrow-right { 1214 | background-image: url(http://static.daily.zhihu.com/img/share-icons.png); 1215 | background-repeat: no-repeat; 1216 | display: inline-block; 1217 | vertical-align: middle; 1218 | background-position: -70px -35px; 1219 | width: 10px; 1220 | height: 15px; 1221 | } 1222 | 1223 | .dudu-night blockquote, 1224 | .dudu-night sup { 1225 | border-left: 3px solid #2e3033; 1226 | color: #555659; 1227 | } 1228 | 1229 | .dudu-night .content a { 1230 | color: #396280; 1231 | } 1232 | 1233 | .dudu-night img { 1234 | opacity: 0.7; 1235 | } 1236 | 1237 | .dudu-night .from-column { 1238 | color: #2b82ac; 1239 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance.png); 1240 | } 1241 | 1242 | .dudu-night .from-column:active { 1243 | background-image: url(http://static.daily.zhihu.com/img/Dark_News_Column_Entrance_Highlight.png); 1244 | } 1245 | 1246 | //禁用头部下面的分隔线 1247 | .dudu .headline { 1248 | border-bottom: none; 1249 | } 1250 | 1251 | .dudu-night .origin-source, 1252 | .dudu-night a.origin-source:link { 1253 | background: #222324; 1254 | } 1255 | 1256 | .dudu-night .origin-source.with-link .text { 1257 | color: #797b80; 1258 | } 1259 | 1260 | .dudu-night .origin-source.with-link:after { 1261 | border-color: transparent transparent transparent #797b80; 1262 | } 1263 | 1264 | @import "../../../node_modules/antd/dist/antd.css"; 1265 | @import "layout"; 1266 | @import "items"; 1267 | @import "about"; --------------------------------------------------------------------------------