├── .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 |
19 |
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 |
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 | [](https://github.com/Hancoson/react-redux-demo)
3 | [](https://github.com/Hancoson/react-redux-demo/blob/master/LICENSE)
4 | [](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";
--------------------------------------------------------------------------------