Repository: jixianu/EasyFun Branch: master Commit: 027f79c3a43d Files: 68 Total size: 77.2 KB Directory structure: gitextract_as64i4d8/ ├── .babelrc ├── .gitattributes ├── .gitignore ├── README.md ├── docs/ │ └── EasyFun.xmind ├── package.json ├── src/ │ ├── common/ │ │ ├── fetch.js │ │ └── mock.js │ ├── components/ │ │ ├── Book/ │ │ │ ├── BookColumn.js │ │ │ └── BookList.js │ │ ├── ColumnHeader.js │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── ListLoadMore.js │ │ ├── Loading.js │ │ ├── Login/ │ │ │ ├── Login.js │ │ │ ├── LoginForm.js │ │ │ └── Register.js │ │ ├── Movie/ │ │ │ ├── MovieAbout.js │ │ │ ├── MovieActor.js │ │ │ ├── MovieBanner.js │ │ │ ├── MovieColumn.js │ │ │ ├── MovieIntro.js │ │ │ ├── MovieItem.js │ │ │ ├── MovieList.js │ │ │ └── MovieMenu.js │ │ ├── Music/ │ │ │ ├── MusicColumn.js │ │ │ └── MusicList.js │ │ ├── Pages.js │ │ ├── Spot/ │ │ │ ├── NewsList.js │ │ │ ├── NewsListBlock.js │ │ │ ├── SpotColumn.js │ │ │ └── TopListBlock.js │ │ └── TopList.js │ ├── config/ │ │ ├── Route-Config.js │ │ └── index.js │ ├── containers/ │ │ ├── AppContainer.js │ │ ├── BookContainer.js │ │ ├── BookDetailContainer.js │ │ ├── HomeContainer.js │ │ ├── MovieContainer.js │ │ ├── MovieDetailContainer.js │ │ ├── MusicContainer.js │ │ ├── MusicDetailContainer.js │ │ ├── NotFoundPage.js │ │ ├── SpotContainer.js │ │ └── SpotDetailContainer.js │ ├── index.html │ ├── index.js │ ├── style/ │ │ ├── App.less │ │ ├── BookList.less │ │ ├── ColumnHeader.less │ │ ├── Home.less │ │ ├── ListLoadMore.less │ │ ├── Login.less │ │ ├── MovieColumn.less │ │ ├── MovieDetail.less │ │ ├── MovieItem.less │ │ ├── MovieMenu.less │ │ ├── Music.less │ │ ├── NewsList.less │ │ ├── Pages.less │ │ ├── Spot.less │ │ ├── TopList.less │ │ └── base.less │ └── template/ │ └── template.html ├── webpack.dev.config.js └── webpack.pub.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "es2015", "react", "stage-0" ], "plugins": [["import", { "libraryName": "antd", "style": true }]] } ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ node_modules .idea dist npm-debug.log yarn-error.log ================================================ FILE: README.md ================================================ ## 简趣 一个简单趣味的pc-react站 ###技术栈 - React - React-Router - ES6 - Less - Antd - Mock.js - Webpack ###运行 在目录中,运行指令 npm install 开发版 yarn run dev 发布版 yarn run pro ###历程(各种偷懒啊) 2.24 配置webpack与包管理 2.25 配置webpack开发热更新 4.6 使用stage-0编程,这样可以省去bind(this) 使用聚合数据API与localStorage做用户登录,接口只能接受username-aaa 4.21 完成电影详情页,首页 5.21 router的异步加载 ###挖坑埋坑 1. 公共的base,其他css引用不每次都要引用 2. 豆瓣的API访问次数,30次/min 3. 异步读取数据时,返回数据之前切换router会使页面报waring,setState nothing 用给路由一个状态this.mouted 4. fetch时mock的数据需要放在服务器环境才能拦截http请求,未解决--需要配置node后台环境 5. 组件样式问题,需要在渲染组件上引入,不应在容器组件上那应用样式,会导致再次使用时还需引样式 6. fetch错误的catch怎么能统一返回一个数据组合?给一个state做判断,能不能统一返回一个DOM节点--不能 7. 组件的大小样式是px像素,而当二次使用时不可适配,应使用百分比 8. webpack打包无法处理jsx中的img标签引用,使用require()则会被编译 9. react-router@4.x.x渲染是需要按照react-router-dom,没有hashHistory方法,推荐使用browserHistory,只有react-router@3.x.x才有hashHistory ###预览 ![](http://i.imgur.com/6aPjIX4.jpg) ![](http://i.imgur.com/7FQl7Q4.jpg) ================================================ FILE: package.json ================================================ { "name": "EasyFun", "version": "1.0.0", "description": "A project using react es6 webpack antd..", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "webpack-dev-server --config webpack.dev.config.js --devtool eval --progress --colors --hot --inline --content-base src", "pub": " rimraf dist/* && webpack --config webpack.pub.config.js -p" }, "author": "xiaomeng", "license": "MIT", "devDependencies": { "autoprefixer-loader": "^3.2.0", "babel-core": "^6.21.0", "babel-loader": "^6.2.10", "babel-plugin-import": "^1.1.1", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.16.0", "babel-preset-stage-0": "^6.22.0", "css-loader": "^0.26.1", "es6-promise": "^4.1.0", "extract-text-webpack-plugin": "^1.0.1", "fetch-jsonp": "^1.0.6", "file-loader": "^0.9.0", "html-webpack-plugin": "^2.28.0", "less": "^2.7.2", "less-loader": "^2.2.3", "mockjs": "^1.0.1-beta3", "open-browser-webpack-plugin": "0.0.3", "react-hot-loader": "^1.3.1", "rimraf": "^2.6.1", "style-loader": "^0.13.1", "url-loader": "^0.5.7", "webpack": "^1.14.0", "webpack-dev-server": "^1.16.2", "yarn": "^0.21.3" }, "dependencies": { "antd": "^2.7.2", "react": "^15.4.2", "react-dom": "^15.4.2", "react-router": "^3.0.2" } } ================================================ FILE: src/common/fetch.js ================================================ import fetchJsonp from 'fetch-jsonp' import * as config from '../config' require('es6-promise').polyfill(); // 获取电影列表 export function fetch_movie(opt) { if (!opt) { return false; } let REQUEST_PATH = `${config.SERVER_PATH}movie/${opt.type}`; if (opt.type !== 'us_box') { REQUEST_PATH += `?start=${opt.start}&count=${opt.count}` } const result = fetchJsonp(REQUEST_PATH, { timeout: 3000, }); return result.then(response=> { return response.json(); }).catch(err=> console.log('parsing failed', err) ); } // 获取电影详情 export function fetch_movieDetail(opt) { if (!opt) { return false; } const REQUEST_PATH = `${config.SERVER_PATH}movie/subject/${opt.id}`; const result = fetchJsonp(REQUEST_PATH, { timeout: 3000, }); return result.then(response=> { return response.json(); }).catch(err=> console.log('parsing failed', err) ); } // 登录注册 export function fetch_login(opt) { if (!opt) { return false; } let params = ''; for (let [key, value] of Object.entries(opt)) { params += `&${key}=${value}`; localStorage.setItem(key, value); } params = params.substring(1); let LOGIN_PATH = `${config.LOGIN_PATH}?${params}`; return fetch(LOGIN_PATH, {method: 'GET'}) .then(response => { return response.json() }) .catch(err=> console.log('parsing failed', err) ) } // 获取新闻热点 export function fetch_spot(opt) { return fetch(`http://newsapi.gugujiankong.com/Handler.ashx?action=getnews&type=${opt.type}&count=${opt.count}`, {method: 'GET'}) .then(response=> { return response.json(); }) } ================================================ FILE: src/common/mock.js ================================================ import Mock from 'mockjs' const {Random} = Mock export const comments = Mock.mock({ 'commentList|5': [{ 'id': '@natural', 'time': '@datetime(16-MM-dd HH:mm:ss)', 'name': '@cname', 'content': '@cparagraph', 'url': Random.image('50x50', Random.color(), '#FFF', Random.word(3, 5)) }] }); export const commentsMore = Mock.mock({ 'commentList|5': [{ 'id': '@natural', 'time': '@datetime(16-MM-dd HH:mm:ss)', 'name': '@cname', 'content': '@cparagraph', 'url': Random.image('50x50', Random.color(), '#FFF', Random.word(3, 5)) }] }); export const messages = Mock.mock({ 'messageList|5-8': [{ 'id': '@natural', 'title': '@ctitle(5,10)', 'name': '@name', 'url': Random.image('40x40', Random.color(), '#FFF', Random.word(3, 5)) }] }); export const correlations = Mock.mock({ 'correlationList|5-8': [{ 'id': '@natural', 'title': '@ctitle(6,10)', 'name': '@name', 'url': Random.image('40x40', Random.color(), '#FFF', Random.word(3, 5)) }] }); export const musicTop = Mock.mock({ 'musicList|10':[{ 'uniquekey' : '@natural', 'title': '@ctitle(3,6)' }] }) ================================================ FILE: src/components/Book/BookColumn.js ================================================ import React, {Component} from 'react' import BookList from './BookList' import ColumnHeader from '../ColumnHeader' export default class MusicColumn extends Component { render() { return (
) } } ================================================ FILE: src/components/Book/BookList.js ================================================ import React from 'react' const BookList = () => { return (
) } export default BookList ================================================ FILE: src/components/ColumnHeader.js ================================================ import React from 'react' import {Link} from 'react-router' import Pages from './Pages' const ColumnHeader = ({title, id , target, total, onChange, current, isMore})=> { return (
{title} {!isMore && }
) } export default ColumnHeader ================================================ FILE: src/components/Footer.js ================================================ import React from 'react' import {Layout} from 'antd' const {Footer} = Layout const Foot = () => { return ( ) } export default Foot ================================================ FILE: src/components/Header.js ================================================ import React from 'react' import {Link, IndexLink} from 'react-router' import {Layout, Row, Col} from 'antd' import Login from './Login/Login' const {Header} = Layout const Head = () => { return (
  • 首页
  • 电影
  • 热点
  • 音乐
  • 书籍
) } export default Head ================================================ FILE: src/components/ListLoadMore.js ================================================ import React, {Component} from 'react' import {Button} from 'antd' const ListLoadMore = ({isLoading, handleClick, count}) => { return (
{count > 0 ? :

没有更多了

}
) } export default ListLoadMore ================================================ FILE: src/components/Loading.js ================================================ import React, {Component} from 'react' import {Spin} from 'antd' const Loading = ()=> { return (
) } export default Loading ================================================ FILE: src/components/Login/Login.js ================================================ import React, {Component} from 'react' import {Modal, Icon, Button, message} from 'antd' import LoginForm from './LoginForm' import Register from './Register' export default class Login extends Component { state = { action: 'login', visible: false, confirmLoading: false, isLogin: false } componentDidMount() { if (localStorage.string) { this.setState({ isLogin: true }) } } showModal = () => { this.setState({ visible: true }); } handleCancel = () => { this.setState({ visible: false }); } handleRegiste = () => { this.setState({ action: 'register' }); } handleRegisterDone = () => { this.setState({ visible: false, isLogin: true }) } handleBack = () => { this.setState({ action: 'login' }); } logout = () => { this.setState({ isLogin: false, action: 'login' }) } loginDone = () => { this.setState({ isLogin: true, visible: false }); message.success("登录成功!"); } render() { return (
{this.state.isLogin ? : }
注册
: '登录' } visible={this.state.visible} footer={null} onCancel={this.handleCancel} > { this.state.action === 'register' ? : } ); } } ================================================ FILE: src/components/Login/LoginForm.js ================================================ import React, {Component} from 'react' import {Form, Icon, Input, Button, message} from 'antd'; const FormItem = Form.Item; class LoginForm extends Component { handleSubmit = (e) => { e.preventDefault(); // 获取表单的值 this.props.form.validateFields((err, values) => { if (!err && values.userName === localStorage.getItem('string') && values.password === localStorage.getItem('password')) { this.props.loginDone(); // API接收登录,但不会校验 // fetch_login(Object.assign({}, {action: 'login'}, formData), this.callback); } else { message.error('登录失败!'); } }); } render() { const {getFieldDecorator} = this.props.form; return (
{getFieldDecorator('userName', { rules: [{required: true, message: '请输入用户名!'}], })( } placeholder="用户名"/> )} {getFieldDecorator('password', { rules: [{required: true, message: '请输入密码!'}], })( } type="password" placeholder="密码"/> )} 现在注册!
); } } export default LoginForm = Form.create({})(LoginForm) ================================================ FILE: src/components/Login/Register.js ================================================ import React, {Component} from 'react' import {Form, Input, Button, Radio, InputNumber, message} from 'antd'; import {fetch_login} from '../../common/fetch' const FormItem = Form.Item; const RadioGroup = Radio.Group; class RegistrationForm extends Component { state = { confirmDirty: false, isSubmitting: false }; // 表单提交动作 handleSubmit = (e) => { e.preventDefault(); this.setState({ isSubmitting: true }); // 但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 this.props.form.validateFieldsAndScroll((err, values) => { if (!err) { fetch_login(Object.assign({}, {action: this.props.action}, values)) .then(() => this.callback()); } }); } callback = () => { setTimeout(()=> { message.success("注册成功!"); this.setState({ isSubmitting: false }); this.props.handleRegisterDone(); }, 1500); } handleConfirmBlur = (e) => { const value = e.target.value; this.setState({confirmDirty: this.state.confirmDirty || !!value}); } checkPassword = (rule, value, callback) => { const form = this.props.form; if (value && value !== form.getFieldValue('password')) { callback('两次密码输入不一致!'); } else { callback(); } } checkConfirm = (rule, value, callback) => { const form = this.props.form; if (value && this.state.confirmDirty) { form.validateFields(['confirm'], {force: true}); } callback(); } render() { // 用于和表单进行双向绑定 const {getFieldDecorator} = this.props.form; const formItemLayout = { labelCol: { xs: {span: 24}, sm: {span: 6}, }, wrapperCol: { xs: {span: 24}, sm: {span: 14}, }, }; const tailFormItemLayout = { wrapperCol: { xs: { span: 24, offset: 0, }, sm: { span: 14, offset: 6, }, }, }; return (
{getFieldDecorator('string', { rules: [{ type: 'string', message: '请输入正确的用户名!', }, { required: true, message: '请输入你的用户名!', }], })( )} {getFieldDecorator('password', { rules: [{ required: true, message: '请输入密码!', }, { validator: this.checkConfirm, }], })( )} {getFieldDecorator('confirm', { rules: [{ required: true, message: '请再次确认密码!', }, { validator: this.checkPassword, }] })( )} {getFieldDecorator('sex', { initialValue: 1 })( 保密 )} {getFieldDecorator('age', { initialValue: 18 })( )}
); } } export default RegistrationForm = Form.create({})(RegistrationForm) ================================================ FILE: src/components/Movie/MovieAbout.js ================================================ import React from 'react' import {Card, Row, Col} from 'antd' import {messages, correlations} from '../../common/mock' const MovieAbout = () => { const messageList = messages.messageList.map(item=> (

{item.title}

{item.name}

)); const correlationList = correlations.correlationList.map(item=> (

{item.title}

{item.name}

)); return ( {correlationList} {messageList} ) } export default MovieAbout ================================================ FILE: src/components/Movie/MovieActor.js ================================================ import React from 'react' import {Card, Icon} from 'antd' import Loading from '../Loading' const MovieActors = ({directors, casts, isLoading}) => { let actorList; if (!isLoading) { actorList = casts.map((item, index)=>(
  • {index == 0 ?

    主演

    :

    }

    {item.name}

  • ) ); } return ( 更多}> {isLoading ? :
    • 导演

      {directors[0].name}

    • {actorList}
    }
    ) } export default MovieActors ================================================ FILE: src/components/Movie/MovieBanner.js ================================================ import React, {Component} from 'react' import {Rate} from 'antd' import Loading from '../Loading' const MovieBanner = ({isLoading, data}) => { if (isLoading) { return ( ) } const directorList = data.directors.map((item, index) => { if (index === data.directors.length - 1) { return item.name; } else { return item.name + '、'; } }); const actorList = data.casts.map((item, index) => { if (index === data.casts.length - 1) { return item.name; } else { return item.name + '、'; } }); return (

    {data.title}

    原名: {data.original_title}

    导演: {directorList}

    主演:{actorList}

    国家:{data.countries.join('、')}

    系列:{data.genres.join('、')}

    年代:{data.year}

    短评数:{data.comments_count}

    评分人数:{data.ratings_count}

    {data.rating.average.toFixed(1)}
    ) } export default MovieBanner ================================================ FILE: src/components/Movie/MovieColumn.js ================================================ import React, {Component} from 'react' import ColumnHeader from '../ColumnHeader' import MovieList from './MovieList' import Loading from '../Loading' import Pages from '../Pages' import {fetch_movie} from '../../common/fetch' import * as config from '../../config' export default class MovieColumn extends Component { state = { isLoading: false, MoviesData: null, current: 1 } componentDidMount() { this.setState({ isLoading: true }); fetch_movie({ start: config.DEFAULT_START, count: this.props.count || 4, type: this.props.type }).then(data=> { this.resolve(data); }).catch(err=> { console.log('parsing failed', err); }) } componentWillUnmount() { // 这里使用组件属性 this.unmount = true; } resolve = data => { if (data && !this.unmount) { this.setState({ MoviesData: data.subjects, isLoading: false }) } } pageChange = page => { this.setState({ current: page }) fetch_movie({ start: (page - 1) * config.DEFAULT_COUNT, count: this.props.count || 4, type: this.props.type }).then(data=> { this.resolve(data) }).catch(err=> { console.log('parsing failed', err); }) } render() { const {type, isMore, total, title, id} = this.props; const {MoviesData, isLoading, current} = this.state; return (
    { isLoading ? :
    }
    ) } } ================================================ FILE: src/components/Movie/MovieIntro.js ================================================ import React, {Component} from 'react' import {Card, Col} from 'antd' import {comments, commentsMore} from '../../common/mock' import MovieActor from './MovieActor' import ListLoadMore from '../ListLoadMore' import Loading from '../Loading' export default class MovieIntro extends Component { state = { commentList: comments.commentList, count: 1, iconLoading: false } handleClick = ()=> { this.setState({ iconLoading: true }); setTimeout(()=> { this.setState({ count: this.state.count > 0 ? this.state.count - 1 : 0, iconLoading: false, commentList: [...this.state.commentList, ...commentsMore.commentList] }); }, 1000); } render() { let commentList = this.state.commentList.map((item)=>(
    {item.name} {item.time}

    {item.content}

    ) ); return ( {this.props.isLoading ? : this.props.data.summary} {commentList} ) } } ================================================ FILE: src/components/Movie/MovieItem.js ================================================ import React, {Component} from 'react' import {Link} from 'react-router' import {Col} from 'antd' import * as config from '../../config' const MovieItem = ({imgUrl, title, rating, genre, id}) => { // 返回JSX结构 return (
    {title}
    {rating == '0' ?
    {genre}
    :
    {rating}
    }
    ); } export default MovieItem ================================================ FILE: src/components/Movie/MovieList.js ================================================ import React, {Component, PropTypes} from 'react' import MovieItem from './MovieItem' import {Row} from 'antd' export default class MovieList extends Component { render() { let itemList = null; const {MoviesData, type, current} = this.props; // 判断是否有数据 if (MoviesData) { // 北美榜数据格式不同,进行判断 if (type === 'us_box') { itemList = MoviesData.map(item => ( )); let endlength = current * 4 < itemList.length ? current * 4 : itemList.length; itemList = itemList.slice((current - 1) * 4, endlength); } else { itemList = MoviesData.map(item => ( )); } } return ( // Item间距 {itemList} ); } } ================================================ FILE: src/components/Movie/MovieMenu.js ================================================ import React from 'react' import {Anchor} from 'antd' const {Link} = Anchor const MovieMenu = () => { return ( ); } export default MovieMenu ================================================ FILE: src/components/Music/MusicColumn.js ================================================ import React, {Component} from 'react' import {Row, Col} from 'antd' import MusicList from './MusicList' import TopList from '../TopList' import {musicTop} from '../../common/mock' import ColumnHeader from '../ColumnHeader' export default class MusicColumn extends Component { render() { return (
    ) } } ================================================ FILE: src/components/Music/MusicList.js ================================================ import React, {Component} from 'react' const MusicList = ()=> { return ( ); } export default MusicList ================================================ FILE: src/components/Pages.js ================================================ import React, { Component } from 'react' import { Pagination } from 'antd' const Pages = ({current, total, onChange, defaultPageSize, id}) => { return (
    ) } export default Pages ================================================ FILE: src/components/Spot/NewsList.js ================================================ import React from 'react'; import {Link} from 'react-router' const NewsList = ({newsData}) => { if (!newsData) { return

    没有数据

    ; } const newsList = newsData.map((newsItem, index) => (
  • {newsItem.title}/ {newsItem.title}
  • )); return (
      {newsList}
    ); } export default NewsList ================================================ FILE: src/components/Spot/NewsListBlock.js ================================================ import React, {Component} from 'react' import {fetch_spot} from '../../common/fetch' import NewsList from './NewsList' import Loading from '../Loading' export default class NewsListBlock extends Component { state = { newsData: null, isLoading: false } componentDidMount() { this.setState({ isLoading: true }); fetch_spot({ type: this.props.type, count: this.props.count }) .then( data=> { this.setState({ newsData: data, isLoading: false }) } ) .catch(err=>console.log('parsing failed', err)) } render() { const {isLoading} = this.state; return (
    {isLoading ? : }
    ) } } ================================================ FILE: src/components/Spot/SpotColumn.js ================================================ import React, {Component} from 'react' import {Row, Col, Carousel, Tabs} from 'antd' import NewsListBlock from './NewsListBlock' import TopListBlock from './TopListBlock' import ColumnHeader from '../ColumnHeader' const {TabPane} = Tabs const SpotColumn = ()=> { return (
    平壤居民罕见用韩国品牌相机拍照
    土耳其公投成功海外公民有喜有悲
    镜头记录雄安新区街头即景
    长沙现微型古籍疑是科举作弊用书
    ); } export default SpotColumn ================================================ FILE: src/components/Spot/TopListBlock.js ================================================ import React, {Component} from 'react'; import {Link} from 'react-router' import TopList from '../TopList' import Loading from '../Loading' import {fetch_spot} from '../../common/fetch' export default class TopListBlock extends Component { state = { newsData: null, isLoading: false } componentDidMount() { this.setState({ isLoading: true }); fetch_spot({ type: 'top', count: 10 }) .then( data=> { this.setState({ newsData: data, isLoading: false }) } ) .catch(err=>console.log('parsing failed', err)) } render() { const {isLoading} = this.state; return (
    {isLoading ? : }
    ) } } ================================================ FILE: src/components/TopList.js ================================================ import React from 'react'; import {Link} from 'react-router' const TopList = ({data, title, link}) => { if (!data) { return

    没有数据

    ; } const topList = data.map((item, index) => (
  • {index+1} {item.title}
  • )); return (
    • {title}

    • {topList}
    ); } export default TopList ================================================ FILE: src/config/Route-Config.js ================================================ import React from 'react' import {Router, Route, IndexRoute, Redirect, hashHistory} from 'react-router' import AppContainer from '../containers/AppContainer' import HomeContainer from '../containers/HomeContainer' const BookContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/BookContainer').default) },'BookContainer') } const BookDetailContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/BookDetailContainer').default) },'BookDetailContainer') } const MovieContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/MovieContainer').default) },'MovieContainer') } const MovieDetailContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/MovieDetailContainer').default) },'MovieDetailContainer') } const MusicContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/MusicContainer').default) },'MusicContainer') } const MusicDetailContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/MusicDetailContainer').default) },'MusicDetailContainer') } const SpotContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/SpotContainer').default) },'SpotContainer') } const SpotDetailContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/SpotDetailContainer').default) },'SpotDetailContainer') } const NotFoundPage = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/NotFoundPage').default) },'NotFoundPage') } /*const HomeContainer = (location, cb) => { require.ensure([], require => { cb(null, require('../containers/HomeContainer').default) },'HomeContainer') }*/ const RootRoter = ( console.log('离开了music页面') } onEnter={ ()=>console.log('进入了music页面') }/> ) export default RootRoter ================================================ FILE: src/config/index.js ================================================ export const SERVER_PATH = 'https://api.douban.com/v2/'; export const LOGIN_PATH = 'http://newsapi.gugujiankong.com/Handler.ashx'; export const DEFAULT_COUNT = 4; export const DEFAULT_START = 0; ================================================ FILE: src/containers/AppContainer.js ================================================ import React, {Component} from 'react' import Header from '../components/Header' import Footer from '../components/Footer' export default class AppContainer extends Component { render() { return (
    {this.props.children}
    ) } } ================================================ FILE: src/containers/BookContainer.js ================================================ import React, { Component } from 'react' export default class BookContainer extends Component { constructor(props){ super(props); this.state = {}; } componentDidMount() { } render() { return (
    敬请期待...
    ) } } ================================================ FILE: src/containers/BookDetailContainer.js ================================================ import React, { Component } from 'react' export default class BookDetailContainer extends Component { constructor(props){ super(props); this.state = {}; } componentDidMount() { } render() { return (
    BookDE
    ) } } ================================================ FILE: src/containers/HomeContainer.js ================================================ import React, {Component} from 'react' import {Carousel, Card, BackTop, Icon} from 'antd' import MovieColumn from '../components/Movie/MovieColumn' import SpotColumn from '../components/Spot/SpotColumn' import MusicColumn from '../components/Music/MusicColumn' import BookColumn from '../components/Book/BookColumn' export default class HomeContainer extends Component { render() { return (
    ) } } ================================================ FILE: src/containers/MovieContainer.js ================================================ import React, { Component } from 'react' import {Layout} from 'antd' import MovieMenu from '../components/Movie/MovieMenu' import MovieColumn from '../components/Movie/MovieColumn' const {Content, Sider} = Layout export default class MovieContainer extends Component { render() { return (
    ) } } ================================================ FILE: src/containers/MovieDetailContainer.js ================================================ import React, {Component} from 'react' import {Layout, Rate, Card, Icon, Row, BackTop} from 'antd' import MovieBanner from '../components/Movie/MovieBanner' import MovieIntro from '../components/Movie/MovieIntro' import MovieAbout from '../components/Movie/MovieAbout' import {fetch_movieDetail} from '../common/fetch' export default class MovieDetailContainer extends Component { state = { data: null, isLoading: true } componentDidMount() { fetch_movieDetail({ id: this.props.params.id }) .then(data=>{ if (data && !this.unmount) { this.setState({ data: data, isLoading: false }) } }) .catch(err=> console.log('parsing failed', err) ) } componentWillUnmount() { this.unmount = true; } render() { return (
    ) } } ================================================ FILE: src/containers/MusicContainer.js ================================================ import React, { Component } from 'react' export default class MusicContainer extends Component { constructor(props){ super(props); this.state = {}; } componentDidMount() { } render() { return (
    敬请期待...
    ) } } ================================================ FILE: src/containers/MusicDetailContainer.js ================================================ import React, { Component } from 'react' export default class MusicDetailContainer extends Component { constructor(props){ super(props); this.state = {}; } componentDidMount() { } render() { return (
    MusicDE页面暂时不开发
    ) } } ================================================ FILE: src/containers/NotFoundPage.js ================================================ import React, {Component} from 'react' export default class SportContainer extends Component { render() { return (
    ) } } ================================================ FILE: src/containers/SpotContainer.js ================================================ import React, { Component } from 'react' export default class SpotContainer extends Component { constructor(props){ super(props); this.state = {}; } componentDidMount() { } render() { return (
    敬请期待...
    ) } } ================================================ FILE: src/containers/SpotDetailContainer.js ================================================ import React, { Component } from 'react' export default class SpotDetailContainer extends Component { constructor(props){ super(props); this.state = {}; } componentDidMount() { } render() { return (
    SportDE页面暂时不开发
    ) } } ================================================ FILE: src/index.html ================================================ 简趣
    ================================================ FILE: src/index.js ================================================ import React from 'react' import ReactDOM from 'react-dom' import RootRouter from './config/Route-Config' import 'antd/dist/antd.less' import './style/App' import './style/base' import './style/BookList' import './style/ColumnHeader' import './style/Home' import './style/ListLoadMore' import './style/Login' import './style/MovieDetail' import './style/MovieMenu' import './style/Music' import './style/NewsList' import './style/Pages' import './style/Spot' import './style/TopList' import './style/MovieItem' import './style/MovieColumn' ReactDOM.render(
    {RootRouter}
    , document.getElementById('app') ) ================================================ FILE: src/style/App.less ================================================ // 首页样式 .logo { display: block; line-height: 64px; height: 64px; background: url('../image/logo.png') left center no-repeat; background-position: 0 0; } .header_tab { float: right; line-height: 62px; li { a { display: block; font-size: 18px; text-align: center; height: 64px; &:hover { color: #f2bd00 } } width: 80px; float: left; height: 62px; } } .header_curPage { color: #f2bd00; width: 80px; line-height: 64px; display: block; text-align: center; border-bottom: 2px solid #108ee9; } /*覆盖样式*/ .ant-layout-header.wrap { background-color: #fcfcfc; height: 64px; line-height: 64px; .ant-row { border-bottom: 1px solid #eee; } } .center { padding: 10px 50px 0px 50px; .ant-layout-content { padding: 0px 8px; border: 1px solid #eee; border-radius: 10px; background-color: #fff; } .ant-layout, .ant-layout-sider { overflow-x: hidden; overflow-y: hidden; background-color: #fcfcfc; } .ant-menu { text-align: center; margin-right: 10px; background-color: #fff; } .ant-menu-inline, .ant-menu-vertical { border: 1px solid #eee; border-radius: 10px; background-color: #fff; } //右侧中心显示区域 .content { padding-left: 10px; border: 1px solid #eee; border-radius: 10px; background-color: #fff; overflow-x: hidden; } } .ant-modal-title{ text-align: center; font-weight: normal; } // 卡片样式 .ant-card { margin: 20px 0px; } // backTop样式 .ant-back-top-inner { height: 40px; width: 40px; line-height: 40px; border-radius: 4px; background-color: #1088e9; color: #fff; text-align: center; font-size: 20px; } // 星星样式 .ant-rate { padding-left: 10px; } // 加载中样式 .loading { padding: 30px 50px; text-align: center; } .not_found { height: 300px; background-color: #FFF; text-align: center; img { padding-top: 50px; width: 300px; } } ================================================ FILE: src/style/BookList.less ================================================ .list-summary { font-size: 0; margin-bottom: -30px; clear: both; li { width: 340px; height: 180px; float: left; margin-bottom: 30px; display: inline-block; } .cover { float: left; margin-right: 18px; } .title { font-size: 14px; height: auto; margin: 0; background: none; } .info { font-size: 12px; margin-right: 20px; height: 180px; p { margin: 0; word-wrap: break-word; .meta-label { display: inline-block; line-height: 1; color: #fff; padding: 2px 3px; background-color: #a1a1a1; } } .reviews { color: #666; clear: both; padding-top: 15px; } .star-img { color: #4dd197 } } &:after { content: ""; display: block; clear: both; } } ================================================ FILE: src/style/ColumnHeader.less ================================================ //栏目 .column_header { border-bottom: 1px solid #eee; height: 36px; line-height: 30px; padding: 5px 10px 5px 0px; margin-bottom: 8px; .column_more { float: right; padding-right: 10px; color: #9DC; } .column_title { float: left; font-size: 18px; display: inline-block; a { color: #E8570C; } } } ================================================ FILE: src/style/Home.less ================================================ .home { .ant-card-body { padding: 10px 20px; } .movie_item { width: 20%; } .carousel{ padding-bottom: 10px; .slick-slide{ img { width: 100%; } } } .column_header { margin-top:10px; } .ant-tabs-bar { margin-bottom: 8px; } } ================================================ FILE: src/style/ListLoadMore.less ================================================ .list_load { padding-top: 10px; .ant-btn { width: 100%; text-align: center; } p { text-align: center; color:#e1e1e1; } } ================================================ FILE: src/style/Login.less ================================================ .login { text-align: center; line-height: 64px; font-size: 14px; color: #0c0c0c; cursor: pointer; } .login_icon { font-size: 18px; color: #108ee9; margin-right: 5px; } .login-form { max-width: 300px; margin: 0 auto; } .login-form-forgot { float: right; } .login-form-button { width: 100%; } .modal_back { display: block; position: absolute; left: 0; top: -2px; cursor: pointer; color: #aaa; transition: color 0.3s; &:hover { color: #000 } } ================================================ FILE: src/style/MovieColumn.less ================================================ .movie_column { .ant-row { border-bottom: 1px solid #eee; padding-bottom: 8px; } } ================================================ FILE: src/style/MovieDetail.less ================================================ .movie_detail { .movie_intro { font-size: 14px; } .movie_banner { height: 350px; background-image: url("../image/banner.jpg"); background-repeat: no-repeat; background-size: cover; padding: 26px 10px 0px 100px; } .movie_introduce { height: 260px; } .movie_poster { width: 220px; height: 300px; float: left; img { width: 100%; height: 100%; } } .movie_info { width: 400px; padding-left: 50px; color: #FFF; float: left; h4 { margin-bottom: 15px; } p { padding: 8px 0px; } } .movie_content { width: 200px; padding-left: 50px; padding-top: 50px; text-align: center; color: #FFF; float: left; } .movie_gate { font-size: 50px; font-family: sans-serif; color: #108ee9; padding: 20px; text-align: center; } .actor { :last-child { margin-right: 0px; } li { float: left; margin-right: 20px; p { text-align: center; height: 20px; } a { display: inline-block; width: 128px; height: 170px; img { width: 100%; height: 100%; } } } } .movie_comment { border-bottom: 1px solid #eee; padding: 10px 0px; .movie_commentator { width: 100%; float: left; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 5px; line-height: 36px; img { width: 30px; height: 30px; border-radius: 15px; margin-right: 10px; } } .movie_commentdate { float: right; } p { padding-left: 5px; padding-top: 5px; } } .movie_about { .ant-card-body { div { cursor: pointer; height: 50px; p:last-child { font-size: 10px; color: #4fd2be; } } } } } ================================================ FILE: src/style/MovieItem.less ================================================ .movie_item { &> div{ margin: 0 5px; } .movie_img { height: 280px; img { width: 100%; height: 100%; } } .movie_info { height: 20px; margin-top: 8px; .movie_title { height: 20px; width: 85%; font-size: 16px; line-height: 20px; font-weight: bold; float: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .movie_score { height: 20px; line-height: 20px; font-size: 18px; float: right; text-align: right; color: #108ee9; width: 15%; } .movie_genre { .movie_score; font-size: 14px; color: #66CD00; } } } ================================================ FILE: src/style/MovieMenu.less ================================================ .movie_menu { border: 1px solid #eee; margin-right: 10px; border-radius: 10px; .ant-anchor{ text-align: center; .ant-anchor-ink{ display: none; } .ant-anchor-link { line-height: 20px; font-size: 14px; padding:8px; } } } ================================================ FILE: src/style/Music.less ================================================ .m-cvrlst { margin: 20px 0 0 -42px; li { width: 180px; height: 204px; float: left; display: inline-block; overflow: hidden; padding: 0 0 30px 42px; line-height: 1.4; div.u-cover { width: 140px; height: 140px; position: relative; display: block; img { display: block; width: 100%; height: 100%; } .msk { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-position: 0 0; background: url(../image/coverall.png) no-repeat; } .icon-play { display: inline-block; position: absolute; right: 10px; bottom: 5px; width: 16px; height: 17px; background: url(../image/iconall.png) no-repeat; background-position: 0 0; float: right; } .icon-headset { display: inline-block; float: left; width: 16px; height: 16px; margin: 7px 7px 7px 5px; background: url(../image/iconall.png) no-repeat; background-position: 0 -24px; } .nb { float: left; margin: 7px 0 0 0; } .bottom { position: absolute; bottom: 0; left: 0; width: 100%; height: 27px; background: url(../image/coverall.png) no-repeat; background-position: 0 -537px; color: #ccc; } p { margin: 8px 0 3px; width: 100%; } } .dec { margin: 8px 0 3px; } .tit { display: inline-block; max-width: 100%; vertical-align: middle; color: #000; } } } ================================================ FILE: src/style/NewsList.less ================================================ .newsList { padding-left: 10px; ul > li { padding-top: 5px; border-bottom: 1px solid #eee; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; height: 70px; a { color: #595959; } img { width: 80px; height: 60px; margin-right: 5px; } } } ================================================ FILE: src/style/Pages.less ================================================ .Pages { height: 24px; line-height: 24px; ul.ant-pagination { float: right; } } ================================================ FILE: src/style/Spot.less ================================================ .spot_news { .carousel { width: 300px; height: 400px; padding-left: 20px; .slick-slide { img { width: 100%; } } } } ================================================ FILE: src/style/TopList.less ================================================ .topList { padding-left: 10px; ul > li { border-bottom: 1px solid #eee; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; height: 36px; line-height: 36px; a { color: #595959; } span { display: inline-block; font-size: 20px; vertical-align: middle; margin-right: 8px; } h3 { text-align: center; color: #108ee9; } } ul > li:nth-child(4), li:nth-child(2), li:nth-child(3) { span { color: #ED1C24; } } } ================================================ FILE: src/style/base.less ================================================ /* CSS Document */ /* css reset */ body, h1, h2, h3, h4, h5, h6, p, ul, ol, dl, dd, form, pre, input, textarea, select, th, td { margin: 0; padding: 0; font-family: "Microsoft Yahei"; } h1, h2, h3, h4, h5, h6, tr, th, td, textarea { word-wrap: break-word; word-break: break-all; } body { font-size: 12px; background-color: @bgColor; } em { font-style: normal; } li { list-style: none; } a { text-decoration: none; } img { border: none; vertical-align: middle; } table { border-collapse: collapse; } th, td { padding: 0; } textarea { overflow: auto; resize: none; } /* end css reset */ /*public.css*/ .wrap { width: 1170px; margin: 0 auto; } .auto { margin: 0 auto; } .clear { zoom: 1; } .clear:after { content: ""; display: block; clear: both; } .fl { float: left; } .fr { float: right; } // 间距 .pl5 { padding-left: 5px; } .pl10 { padding-left: 10px; } .pl15 { padding-left: 15px; } .pl20 { padding-left: 20px; } .pl30 { padding-left: 30px; } .pl40 { padding-left: 40px; } .pr5 { padding-right: 5px; } .pr10 { padding-right: 10px; } .pr20 { padding-right: 20px; } .pt5 { padding-top: 5px; } .pt10 { padding-top: 10px; } .pt20 { padding-top: 20px; } .pt30 { padding-top: 30px; } .pb5 { padding-bottom: 5px; } .pb10 { padding-bottom: 10px; } .pb20 { padding-bottom: 20px; } .pb30 { padding-bottom: 30px; } .ml5 { margin-left: 5px; } .ml10 { margin-left: 10px; } .ml15 { margin-left: 15px; } .ml20 { margin-left: 20px; } .ml30 { margin-left: 30px; } .ml40 { margin-left: 40px; } .mr5 { margin-right: 5px; } .mr10 { margin-right: 10px; } .mr15 { margin-right: 15px; } .mr20 { margin-right: 20px; } .mr30 { margin-right: 30px; } .mt3 { margin-top: 3px; } .mt5 { margin-top: 5px; } .mt8 { margin-top: 8px; } .mt10 { margin-top: 10px; } .mt15 { margin-top: 15px; } .mt20 { margin-top: 20px; } .mt30 { margin-top: 30px; } .mb5 { margin-bottom: 5px; } .mb10 { margin-bottom: 10px; } .mb15 { margin-bottom: 15px; } .mb20 { margin-bottom: 20px; } .mb40 { margin-bottom: 40px; } .mb50 { margin-bottom: 50px; } //字体 .fb { font-weight: bold; } .fz12 { font-size: 12px; } .fz14 { font-size: 14px; } .fz16 { font-size: 16px; } .fz18 { font-size: 18px; } .fz24 { font-size: 24px; } // 高度 .h20 { height: 20px; } .h28 { height: 28px; } .h64 { height: 64px; } .w45 { width: 45px; } .w50 { width: 50px; } .w60 { width: 60px; } .w70 { width: 70px; } .w84 { width: 84px; } .w80 { width: 80px; } .w90 { width: 90px; } .w100 { width: 100px; } .w106 { width: 106px; } .w115 { width: 115px; } .w120 { width: 120px; } .w125 { width: 125px; } .w140 { width: 140px; } .w150 { width: 150px; } .w157 { width: 157px; } .w190 { width: 190px; } .w200 { width: 200px; } .w230 { width: 230px; } .w250 { width: 250px; } .w280 { width: 280px; } .w300 { width: 300px; } .w308 { width: 308px; } .w350 { width: 350px; } .w370 { width: 370px; } .w400 { width: 400px; } .w420 { width: 420px; } .w440 { width: 440px; } .w485 { width: 485px; } .w530 { width: 530px; } .w550 { width: 550px; } .w580 { width: 580px; } .w650 { width: 650px; } .w680 { width: 680px; } .w700 { width: 700px; } .w720 { width: 720px; } .w750 { width: 750px; } .w950 { width: 950px; } .vm2 { vertical-align: -2px; } .vm3 { vertical-align: -3px; } .lh15 { line-height: 15px; } .lh18 { line-height: 18px; } .lh20 { line-height: 20px; } .lh23 { line-height: 23px; } .lh25 { line-height: 25px; } .lh28 { line-height: 28px; } .lh30 { line-height: 30px; } .pointer { cursor: pointer; } .tl { text-align: left; } .tc { text-align: center; } .tr { text-align: right; } .rel { position: relative; } .abs { position: absolute; } .fixd { position: fixed; } .vbh { visibility: hidden; } .nowrap { white-space: nowrap; } .wraps { word-wrap: break-word; word-break: break-all; } .omit { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .fs { font-family: "Microsoft Yahei"; } .ti2 { text-indent: 24px; } .vam { vertical-align: middle; } .normal { font-style: normal; } // 清除浮动 .fix { display: inline-table; display: block; height: 1%; } .fix:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } // 边框线 .bdt { border-top: 1px solid @bdColor; } .bdb { border-bottom: 1px solid @bdColor; } .bdr { border-right: 1px solid @bdColor; } .bdl { border-left: 1px solid @bdColor; } .bd { border: 1px solid @bdColor; } // 块 .inblock { display: inline-block; } .hide { display: none; } .db { display: block; } // 当前 @current: #f2bd00; // 主题色 @theme: #108ee9; // 背景色 @bgColor: #fcfcfc; // 边框色 @bdColor: #eee; // 栏目头色 @columnColor: #E8570C; // 更多字体色 @moreColor: #9DC; // 电影类型 @genre: #66CD00; ================================================ FILE: src/template/template.html ================================================ 简趣
    ================================================ FILE: webpack.dev.config.js ================================================ var webpack = require('webpack'); var path = require('path'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); // 打包css插件 var OpenBrowserPlugin = require('open-browser-webpack-plugin'); // 编译后自动打开浏览器 var ROOT_PATH = path.resolve(__dirname); // 项目跟路径 var APP_PATH = path.resolve(ROOT_PATH, 'src'); // 项目开发目录src var APP_FILE = path.resolve(APP_PATH, 'index.js'); // 项目入口的index.js module.exports = { entry: [ 'webpack/hot/dev-server', 'webpack-dev-server/client?http://localhost:8080', APP_FILE ], output: { path: APP_PATH, filename: 'bundle.js' }, // 可以在sources里调试 devtool: "cheap-module-eval-source-map", module: { loaders: [ { test: /\.css$/, loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer']) }, { test: /\.less$/, loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer', 'less']) }, { test: /\.js[x]?$/, exclude: /node_modules/, loaders: ['react-hot', 'babel?presets[]=es2015,presets[]=react,presets[]=stage-0'] }, { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }, { test: /\.(woff|woff2|eot|ttf|svg)(\?.*$|$)/, loader: 'url' } ] } , // 其它解决方案配置 resolve: { // 后缀 extensions: ['', '.js', '.jsx', '.json', '.less'] } , // 插件 plugins: [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('development') //定义生产环境 } }), new OpenBrowserPlugin({url: 'http://localhost:8080/#/'}), new ExtractTextPlugin("app.css") ] } ; ================================================ FILE: webpack.pub.config.js ================================================ var webpack = require("webpack"); var path = require('path'); var uglifyJsPlugin = webpack.optimize.UglifyJsPlugin; var ExtractTextPlugin = require("extract-text-webpack-plugin"); var HtmlWebpackPlugin = require('html-webpack-plugin'); var ROOT_PATH = path.resolve(__dirname); // 项目跟路径 var APP_PATH = path.resolve(ROOT_PATH, 'src'); // 项目开发目录src var APP_FILE = path.resolve(APP_PATH, 'index.js'); // 项目入口的index.js var DIST_PATH = path.resolve(ROOT_PATH, 'dist'); // 项目打包输出路径 module.exports = { entry: { app: APP_FILE, venders: [ 'react', 'react-dom', 'react-router', // 'antd',这里直接会把所有antd打包 'mockjs' ] }, output: { path: DIST_PATH, //表示资源的发布地址,当配置过该属性后,打包文件中所有通过相对路径引用的资源都会被配置的路径所替换 //publicPath: '/', filename: '[name].js', chunkFilename: 'js/[name].[chunkhash:5].min.js' }, module: { loaders: [ { test: /\.css$/, loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer']) }, { test: /\.less$/, loader: ExtractTextPlugin.extract('style', ['css', 'autoprefixer', 'less']) }, { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['react-hot', 'babel?presets[]=es2015,presets[]=react,presets[]=stage-0'] }, { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192&name=image/[hash:8].[name].[ext]' }, { test: /\.(woff|woff2|eot|ttf|svg)(\?.*$|$)/, loader: 'url' } ] }, resolve: { extensions: ['', '.js', '.jsx', '.json', '.less'] }, plugins: [ // new webpack.ProvidePlugin({ $: "jquery" }), // 这是将jquery变成全局变量,不用在自己文件require('jquery')了 new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') //定义生产环境 } }), new webpack.optimize.CommonsChunkPlugin('venders', 'venders.js'), // 这是妮第三方库打包生成的文件 new uglifyJsPlugin({ output: { comments: false, // remove all comments }, compress: { warnings: false } }), new ExtractTextPlugin("[name].css"), new HtmlWebpackPlugin({ template: './src/template/template.html', // html模板路径 htmlWebpackPlugin: { files: { css: ["app.css"], js: ["bundle.js", "venders.js"] } }, minify: { removeComments: true, // 移除HTML中的注释 collapseWhitespace: true, // 删除空白符与换行符 removeAttributeQuotes: true // 移除HTML中的属性引号 }, filename: 'index.html', favicon:'./src/favicon.ico', //favicon路径 }) ] };