├── src ├── components │ ├── App │ │ ├── app.scss │ │ └── index.js │ ├── City │ │ ├── CityActivityDisplayMain │ │ │ ├── cityActivityDisplayMain.scss │ │ │ └── index.js │ │ ├── CityActivityDisplay │ │ │ ├── cityActivityDisplay.scss │ │ │ └── index.js │ │ ├── CityTag │ │ │ ├── cityTag.scss │ │ │ └── index.js │ │ ├── CityTagItem │ │ │ ├── cityTagItem.scss │ │ │ └── index.js │ │ ├── CityActivityDisplayHeader │ │ │ ├── cityActivityDisplayHeader.scss │ │ │ └── index.js │ │ ├── CityActivityDisplayHeaderItem │ │ │ ├── cityActivityDisplayHeaderItem.scss │ │ │ └── index.js │ │ ├── CityActivityDisplayMainItem │ │ │ ├── cityActivityDisplayMainItem.scss │ │ │ └── index.js │ │ ├── CityActivity │ │ │ └── index.js │ │ └── CityActivityDisplayItem │ │ │ └── index.js │ ├── Main │ │ ├── main.scss │ │ └── index.js │ ├── Common │ │ ├── CommonSlider │ │ │ ├── CommonSliderDots │ │ │ │ ├── commonSliderDots.scss │ │ │ │ ├── CommonSliderDot │ │ │ │ │ ├── commonSliderDot.scss │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── CommonSliderArrows │ │ │ │ ├── commonSliderArrows.scss │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── CommonTagList │ │ │ ├── CommonTagItem │ │ │ │ ├── commonTagItem.scss │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── CommonModal │ │ │ ├── commonModal.scss │ │ │ └── index.js │ ├── Music │ │ └── MusicTagContent │ │ │ ├── MusicTag │ │ │ ├── musicTag.scss │ │ │ └── index.js │ │ │ ├── MusicTagItem │ │ │ ├── musicTagItem.scss │ │ │ └── index.js │ │ │ ├── MusicDisplay │ │ │ ├── index.js │ │ │ └── musicDisplay.scss │ │ │ └── index.js │ ├── Book │ │ ├── BookTag │ │ │ ├── BookTagsList │ │ │ │ ├── bookTagsList.scss │ │ │ │ └── index.js │ │ │ ├── bookTag.scss │ │ │ ├── BookTagsItem │ │ │ │ ├── bookTagsItem.scss │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── BookTagContent │ │ │ ├── bookTagContent.scss │ │ │ ├── BookTagDisplayList │ │ │ │ ├── bookTagDisplayList.scss │ │ │ │ └── index.js │ │ │ ├── BookTagDisplay │ │ │ │ ├── bookTagDisplay.scss │ │ │ │ └── index.js │ │ │ ├── BookTagDisplayItem │ │ │ │ ├── bookTagDisplayItem.scss │ │ │ │ └── index.js │ │ │ ├── BookTagPrompt │ │ │ │ ├── bookTagPrompt.scss │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── BookTagMoreContent │ │ │ ├── BookTagMoreDisplay │ │ │ │ ├── bookTagMoreDisplay.scss │ │ │ │ └── index.js │ │ │ ├── bookTagMoreContent.scss │ │ │ ├── LoadMoreBooks │ │ │ │ ├── loadMoreBooks.scss │ │ │ │ └── index.js │ │ │ ├── BookTagMoreDisplayList │ │ │ │ └── index.js │ │ │ ├── BookTagMoreDisplayItem │ │ │ │ ├── bookTagMoreDisplayItem.scss │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── BookTypeContent │ │ │ ├── bookTypeContent.scss │ │ │ ├── BookTypeDisplayList │ │ │ └── index.js │ │ │ ├── BookTypeDisplayItem │ │ │ ├── bookTypeDisplayItem.scss │ │ │ └── index.js │ │ │ └── index.js │ ├── Movie │ │ ├── MovieTagContent │ │ │ ├── movieTagContent.scss │ │ │ ├── MovieTagDisplayList │ │ │ │ ├── movieTagDisplayList.scss │ │ │ │ └── index.js │ │ │ ├── MovieTag │ │ │ │ ├── movieTag.scss │ │ │ │ └── index.js │ │ │ ├── MovieTagDisplay │ │ │ │ ├── movieTagDisplay.scss │ │ │ │ └── index.js │ │ │ ├── MovieTagDisplayItem │ │ │ │ ├── movieTagDisplayItem.scss │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── MovieTypeContent │ │ │ ├── MovieTypeDisplayList │ │ │ ├── movieTypeDisplayList.scss │ │ │ └── index.js │ │ │ ├── MovieModal │ │ │ ├── movieModal.scss │ │ │ └── index.js │ │ │ ├── movieTypeContent.scss │ │ │ ├── MovieTypeDisplayItem │ │ │ ├── movieTypeDisplayItem.scss │ │ │ └── index.js │ │ │ └── index.js │ └── Header │ │ ├── header.scss │ │ ├── HeaderSuggest │ │ ├── headerSuggest.scss │ │ └── index.js │ │ ├── HeaderInput │ │ ├── headerInput.scss │ │ └── index.js │ │ ├── HeaderModules │ │ ├── index.js │ │ └── headerModules.scss │ │ └── index.js ├── assets │ ├── images │ │ ├── book.png │ │ ├── city.png │ │ ├── favicon.ico │ │ ├── movie.png │ │ ├── music.png │ │ ├── scoring.png │ │ ├── book_search_btn.png │ │ ├── city_search_btn.png │ │ ├── movie_search_btn.png │ │ └── music_search_btn.png │ └── styles │ │ ├── vars.scss │ │ ├── mixin.scss │ │ └── index.scss ├── stores │ ├── index.js │ └── modules │ │ ├── musicStore.js │ │ ├── cityStore.js │ │ ├── headerStore.js │ │ ├── movieStore.js │ │ └── bookStore.js ├── index.js ├── apis │ └── index.js └── utils │ ├── utils.js │ └── registerServiceWorker.js ├── public ├── favicon.ico ├── manifest.json └── index.html ├── screenshot ├── search.png ├── bookTagContent.png ├── cityActivity.png ├── bookTypeContent.png ├── movieTagContent.png ├── movieTypeContent.png ├── musicTagContent.png └── bookTagMoreContent.png ├── .eslintignore ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── paths.js ├── env.js ├── webpackDevServer.config.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── .gitignore ├── .stylelintrc ├── .eslintrc ├── scripts ├── test.js ├── start.js └── build.js ├── README.md └── package.json /src/components/App/app.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplayMain/cityActivityDisplayMain.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /screenshot/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/screenshot/search.png -------------------------------------------------------------------------------- /src/components/Main/main.scss: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin: 0 auto; 3 | width: 936px; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/images/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/book.png -------------------------------------------------------------------------------- /src/assets/images/city.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/city.png -------------------------------------------------------------------------------- /screenshot/bookTagContent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/screenshot/bookTagContent.png -------------------------------------------------------------------------------- /screenshot/cityActivity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/screenshot/cityActivity.png -------------------------------------------------------------------------------- /src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /src/assets/images/movie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/movie.png -------------------------------------------------------------------------------- /src/assets/images/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/music.png -------------------------------------------------------------------------------- /src/assets/images/scoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/scoring.png -------------------------------------------------------------------------------- /screenshot/bookTypeContent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/screenshot/bookTypeContent.png -------------------------------------------------------------------------------- /screenshot/movieTagContent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/screenshot/movieTagContent.png -------------------------------------------------------------------------------- /screenshot/movieTypeContent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/screenshot/movieTypeContent.png -------------------------------------------------------------------------------- /screenshot/musicTagContent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/screenshot/musicTagContent.png -------------------------------------------------------------------------------- /screenshot/bookTagMoreContent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/screenshot/bookTagMoreContent.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /config/ 2 | /scripts/ 3 | /public/ 4 | /build/ 5 | /coverage/ 6 | 7 | /src/utils/registerServiceWorker.js 8 | -------------------------------------------------------------------------------- /src/components/Common/CommonSlider/CommonSliderDots/commonSliderDots.scss: -------------------------------------------------------------------------------- 1 | .slider-dots { 2 | display: inline-block; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Music/MusicTagContent/MusicTag/musicTag.scss: -------------------------------------------------------------------------------- 1 | .music-tag-list { 2 | border-bottom: 2px solid #02a682; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/images/book_search_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/book_search_btn.png -------------------------------------------------------------------------------- /src/assets/images/city_search_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/city_search_btn.png -------------------------------------------------------------------------------- /src/assets/images/movie_search_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/movie_search_btn.png -------------------------------------------------------------------------------- /src/assets/images/music_search_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nh0007/react-douban/HEAD/src/assets/images/music_search_btn.png -------------------------------------------------------------------------------- /src/components/Book/BookTag/BookTagsList/bookTagsList.scss: -------------------------------------------------------------------------------- 1 | .tags-list { 2 | border-bottom: 1px solid #eee; 3 | padding: 10px 0; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/styles/vars.scss: -------------------------------------------------------------------------------- 1 | $book-color: #f6f6f1; 2 | $movie-color: #f0f3f5; 3 | $music-color: #f0f3ef; 4 | $city-color: #f6f5f2; 5 | 6 | $common-font-color: #37a; 7 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/movieTagContent.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .display-content { 4 | @include display-content; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Common/CommonTagList/CommonTagItem/commonTagItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../.././../assets/styles/mixin'; 2 | 3 | .tag-item { 4 | @include aside-item; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplay/cityActivityDisplay.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .display-content { 4 | @include display-content; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTypeContent/MovieTypeDisplayList/movieTypeDisplayList.scss: -------------------------------------------------------------------------------- 1 | .movie-list { 2 | height: 560px; 3 | overflow: hidden; 4 | position: relative; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Book/BookTagContent/bookTagContent.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .display-content { 4 | @include display-content; 5 | height: 535px; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Book/BookTagMoreContent/BookTagMoreDisplay/bookTagMoreDisplay.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .display-header { 4 | @include main-header; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Book/BookTagMoreContent/bookTagMoreContent.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .display-content { 4 | @include display-content; 5 | font-size: 1.3rem; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Book/BookTag/bookTag.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .aside-content { 4 | @include aside-content; 5 | } 6 | 7 | .aside-header { 8 | @include aside-header; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Book/BookTagContent/BookTagDisplayList/bookTagDisplayList.scss: -------------------------------------------------------------------------------- 1 | .book-list { 2 | height: 489px; 3 | margin-top: 16px; 4 | overflow: hidden; 5 | position: relative; 6 | width: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/City/CityTag/cityTag.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .aside-content { 4 | @include aside-content; 5 | } 6 | 7 | .aside-header { 8 | @include aside-header; 9 | margin-bottom: 3px; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/MovieTagDisplayList/movieTagDisplayList.scss: -------------------------------------------------------------------------------- 1 | .movie-list { 2 | font-size: 1.3rem; 3 | height: 560px; 4 | margin-top: 16px; 5 | overflow: hidden; 6 | position: relative; 7 | width: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/MovieTag/movieTag.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .aside-content { 4 | @include aside-content; 5 | } 6 | 7 | .aside-header { 8 | @include aside-header; 9 | margin-bottom: 3px; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/App/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from '../Header'; 3 | import Main from '../Main'; 4 | 5 | export default function App() { 6 | return ( 7 |
8 |
9 |
10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Common/CommonSlider/CommonSliderDots/CommonSliderDot/commonSliderDot.scss: -------------------------------------------------------------------------------- 1 | .slider-dot { 2 | border-radius: 50%; 3 | cursor: pointer; 4 | display: inline-block; 5 | height: 5px; 6 | margin-left: 8px; 7 | outline: none; 8 | width: 5px; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTypeContent/MovieModal/movieModal.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .modal-header { 4 | color: #42b983; 5 | } 6 | 7 | .modal-title { 8 | margin: 0 3px; 9 | } 10 | 11 | .movie-link { 12 | @include common-link; 13 | margin: 0 6px; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/MovieTagDisplay/movieTagDisplay.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .display-header { 4 | @include main-header; 5 | } 6 | 7 | .display-title { 8 | display: inline-block; 9 | } 10 | 11 | .slider-component { 12 | float: right; 13 | margin-top: 5px; 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Book/BookTagMoreContent/LoadMoreBooks/loadMoreBooks.scss: -------------------------------------------------------------------------------- 1 | .load-more { 2 | background: #f6f6f1; 3 | color: #37a; 4 | cursor: pointer; 5 | height: 34px; 6 | line-height: 34px; 7 | margin-top: 15px; 8 | outline: none; 9 | text-align: center; 10 | 11 | &:hover { 12 | background: #e8e8e8; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/City/CityTagItem/cityTagItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | @import '../../../assets/styles/vars'; 3 | 4 | .tag-item { 5 | @include aside-item; 6 | } 7 | 8 | .in-active { 9 | background: $city-color; 10 | } 11 | 12 | .not-active { 13 | &:hover { 14 | background: $city-color; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/stores/index.js: -------------------------------------------------------------------------------- 1 | import headerStore from './modules/headerStore'; 2 | import bookStore from './modules/bookStore'; 3 | import movieStore from './modules/movieStore'; 4 | import musicStore from './modules/musicStore'; 5 | import cityStore from './modules/cityStore'; 6 | 7 | export default { 8 | headerStore, 9 | bookStore, 10 | movieStore, 11 | musicStore, 12 | cityStore 13 | }; 14 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Book/BookTagContent/BookTagDisplay/bookTagDisplay.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .display-header { 4 | @include main-header; 5 | } 6 | 7 | .display-title { 8 | display: inline-block; 9 | } 10 | 11 | .nav-link { 12 | @include common-link; 13 | margin-left: 15px; 14 | } 15 | 16 | .slider-component { 17 | float: right; 18 | margin-top: 5px; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplayHeader/cityActivityDisplayHeader.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .display-header { 4 | @include main-header; 5 | margin-bottom: 20px; 6 | } 7 | 8 | .display-title { 9 | display: inline-block; 10 | font-size: 1.4rem; 11 | font-weight: normal; 12 | margin-right: 40px; 13 | } 14 | 15 | .day-list { 16 | display: inline-block; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Common/CommonSlider/CommonSliderArrows/commonSliderArrows.scss: -------------------------------------------------------------------------------- 1 | .slider-arrows { 2 | display: inline-block; 3 | margin-left: 10px; 4 | } 5 | 6 | .slider-arrow { 7 | border-radius: 50%; 8 | color: #fff; 9 | cursor: pointer; 10 | display: inline-block; 11 | font-weight: bold; 12 | height: 18px; 13 | line-height: 18px; 14 | margin-left: 6px; 15 | outline: none; 16 | text-align: center; 17 | width: 18px; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Book/BookTagContent/BookTagDisplayItem/bookTagDisplayItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .book-page { 4 | @include common-slider-page; 5 | 6 | li { 7 | width: 20%; 8 | } 9 | } 10 | 11 | .book-image { 12 | height: 70%; 13 | width: 85%; 14 | } 15 | 16 | .book-title { 17 | @include common-title; 18 | } 19 | 20 | .book-author { 21 | @include text-hidden; 22 | font-size: 1.3rem; 23 | margin: 6px 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Book/BookTypeContent/bookTypeContent.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .display-header { 4 | @include main-header; 5 | margin-bottom: 16px; 6 | } 7 | 8 | .display-title { 9 | display: inline-block; 10 | } 11 | 12 | .type-link { 13 | @include common-link; 14 | cursor: default; 15 | margin-left: 12px; 16 | outline: none; 17 | } 18 | 19 | .in-active { 20 | @include in-active; 21 | } 22 | 23 | .not-active { 24 | @include not-active; 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | /dist/ 24 | 25 | # Editor directories and files 26 | .idea 27 | .vscode 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-sass-guidelines", 3 | "rules": { 4 | "selector-pseudo-class-no-unknown": [ 5 | true, 6 | { 7 | "ignorePseudoClasses": [ 8 | "global" 9 | ] 10 | } 11 | ], 12 | "selector-pseudo-element-no-unknown": [ 13 | true, 14 | { 15 | "ignorePseudoElements": [ 16 | "global" 17 | ] 18 | } 19 | ], 20 | "max-nesting-depth": 2, 21 | }, 22 | "ignoreFiles": [ 23 | "build/**/*.css" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/MovieTagDisplayItem/movieTagDisplayItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .movie-page { 4 | @include common-slider-page; 5 | 6 | li { 7 | width: 20%; 8 | } 9 | } 10 | 11 | .movie-image { 12 | height: 160px; 13 | width: 115px; 14 | } 15 | 16 | .movie-title { 17 | @include common-title; 18 | margin: 6px 0; 19 | } 20 | 21 | .movie-score { 22 | margin: 10px 0; 23 | 24 | span { 25 | color: #e09015; 26 | } 27 | } 28 | 29 | .movie-year { 30 | margin: 8px 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTypeContent/movieTypeContent.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .display-header { 4 | @include main-header; 5 | margin-bottom: 16px; 6 | } 7 | 8 | .display-title { 9 | display: inline-block; 10 | } 11 | 12 | .type-link { 13 | @include common-link; 14 | cursor: default; 15 | margin-left: 12px; 16 | outline: none; 17 | } 18 | 19 | .in-active { 20 | @include in-active; 21 | } 22 | 23 | .not-active { 24 | @include not-active; 25 | } 26 | 27 | .slider-component { 28 | float: right; 29 | margin-top: 5px; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Book/BookTag/BookTagsItem/bookTagsItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/vars'; 2 | 3 | .tags-item { 4 | display: inline-block; 5 | margin: 5px 30px 3px 0; 6 | padding: 3px 5px; 7 | 8 | button { 9 | background: none; 10 | color: $common-font-color; 11 | cursor: pointer; 12 | font-size: 1.2rem; 13 | 14 | &:hover { 15 | color: #09f; 16 | } 17 | } 18 | } 19 | 20 | .selected-item { 21 | background: #111; 22 | 23 | button { 24 | color: #fff; 25 | 26 | &:hover { 27 | color: #fff; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'mobx-react'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import stores from './stores'; 6 | import App from './components/App'; 7 | import registerServiceWorker from './utils/registerServiceWorker'; 8 | 9 | import './assets/styles/index.scss'; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | registerServiceWorker(); 20 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplayHeaderItem/cityActivityDisplayHeaderItem.scss: -------------------------------------------------------------------------------- 1 | .day-tag { 2 | display: inline-block; 3 | margin-right: 10px; 4 | 5 | span { 6 | border-radius: 2px; 7 | display: inline-block; 8 | height: 16px; 9 | line-height: 1.4; 10 | outline: none; 11 | padding: 1px 15px; 12 | } 13 | 14 | .in-active { 15 | background: #e6e6e6; 16 | color: #333; 17 | } 18 | 19 | .not-active { 20 | color: #0192b5; 21 | cursor: pointer; 22 | 23 | &:hover { 24 | background: #6cc1d6; 25 | color: #fff; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "react-app", 4 | "airbnb", 5 | "prettier" 6 | ], 7 | "plugins": [ 8 | "prettier" 9 | ], 10 | "env": { 11 | "browser": true, 12 | "node": true, 13 | "jest": true 14 | }, 15 | "rules": { 16 | "linebreak-style": 0, 17 | "prettier/prettier": "error", 18 | "func-names": 0, 19 | "no-console": 0, 20 | "jsx-a11y/click-events-have-key-events": 0, 21 | "react/jsx-filename-extension": [ 22 | "error", 23 | { 24 | "extensions": [ 25 | ".js", 26 | ".jsx" 27 | ] 28 | } 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /src/components/Book/BookTypeContent/BookTypeDisplayList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import BookTypeDisplayItem from '../BookTypeDisplayItem'; 4 | 5 | export default class BookTypeDisplayList extends PureComponent { 6 | static propTypes = { 7 | bookList: PropTypes.arrayOf(PropTypes.object).isRequired 8 | }; 9 | 10 | render() { 11 | const { bookList } = this.props; 12 | return ( 13 | 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Book/BookTagMoreContent/BookTagMoreDisplayList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import BookTagMoreDisplayItem from '../BookTagMoreDisplayItem'; 4 | 5 | export default class BookTagMoreDisplayList extends PureComponent { 6 | static propTypes = { 7 | bookList: PropTypes.arrayOf(PropTypes.object).isRequired 8 | }; 9 | 10 | render() { 11 | const { bookList } = this.props; 12 | return ( 13 | 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Music/MusicTagContent/MusicTagItem/musicTagItem.scss: -------------------------------------------------------------------------------- 1 | .music-tag-item { 2 | display: inline-block; 3 | font-size: 1.3rem; 4 | margin-bottom: 5px; 5 | vertical-align: middle; 6 | 7 | span { 8 | outline: none; 9 | } 10 | 11 | &::before { 12 | color: #222; 13 | content: ' | '; 14 | padding: 0 3px; 15 | } 16 | 17 | &:first-child::before { 18 | content: ' '; 19 | } 20 | } 21 | 22 | .in-active { 23 | color: #138a64; 24 | font-weight: bolder; 25 | } 26 | 27 | .not-active { 28 | color: #999; 29 | cursor: pointer; 30 | 31 | &:hover { 32 | color: #138a64; 33 | text-decoration: underline; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplayMain/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CityActivityDisplayMainItem from '../CityActivityDisplayMainItem'; 4 | 5 | export default class CityActivityDisplayMain extends PureComponent { 6 | static propTypes = { 7 | activityList: PropTypes.arrayOf(PropTypes.object).isRequired 8 | }; 9 | 10 | render() { 11 | const { activityList } = this.props; 12 | return ( 13 | 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Common/CommonModal/commonModal.scss: -------------------------------------------------------------------------------- 1 | .modal-mask { 2 | background-color: rgba(128, 128, 128, 0.5); 3 | display: table; 4 | height: 100%; 5 | left: 0; 6 | position: fixed; 7 | text-align: left; 8 | top: 0; 9 | transition: opacity 0.3s ease; 10 | width: 100%; 11 | z-index: 9998; 12 | } 13 | 14 | .modal-wrapper { 15 | display: table-cell; 16 | vertical-align: middle; 17 | } 18 | 19 | .modal-body { 20 | margin: 20px 0; 21 | } 22 | 23 | .modal-button { 24 | background-color: #409eff; 25 | border: 0; 26 | border-radius: 3px; 27 | color: #fff; 28 | cursor: pointer; 29 | float: right; 30 | outline: none; 31 | padding: 9px 15px; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Book/BookTagMoreContent/LoadMoreBooks/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './loadMoreBooks.scss'; 4 | 5 | export default class LoadMoreBooks extends PureComponent { 6 | static propTypes = { 7 | setPageCount: PropTypes.func.isRequired 8 | }; 9 | 10 | handleClick = () => { 11 | const { setPageCount } = this.props; 12 | setPageCount(null, 10); 13 | }; 14 | 15 | render() { 16 | return ( 17 |
23 | 加载更多 24 |
25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplayMainItem/cityActivityDisplayMainItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/mixin'; 2 | 3 | .activity-item { 4 | display: inline-block; 5 | height: 132px; 6 | overflow: hidden; 7 | width: 50%; 8 | } 9 | 10 | .image-link { 11 | float: left; 12 | } 13 | 14 | .activity-image { 15 | height: 97px; 16 | width: 75px; 17 | } 18 | 19 | .activity-info { 20 | color: #666; 21 | font-size: 1.2rem; 22 | margin-left: 90px; 23 | padding-right: 30px; 24 | } 25 | 26 | .activity-title { 27 | @include common-link; 28 | font-size: 1.3rem; 29 | } 30 | 31 | .date-info { 32 | margin-top: 6px; 33 | } 34 | 35 | .address-info { 36 | @include text-hidden; 37 | margin: 3px 0; 38 | width: 100%; 39 | } 40 | -------------------------------------------------------------------------------- /src/components/Header/header.scss: -------------------------------------------------------------------------------- 1 | .outer-header { 2 | margin-bottom: 40px; 3 | min-width: 936px; 4 | transition: background 0.5s ease-in-out; 5 | } 6 | 7 | .inner-header { 8 | margin: 0 auto; 9 | padding: 10px 0 5px; 10 | width: 936px; 11 | } 12 | 13 | .module-logo { 14 | background-position: 0 12px; 15 | background-repeat: no-repeat; 16 | color: transparent; 17 | display: inline-block; 18 | height: 56px; 19 | margin: 0 13px 0 0; 20 | width: 145px; 21 | } 22 | 23 | .book-logo { 24 | background-image: url('../../assets/images/book.png'); 25 | } 26 | 27 | .movie-logo { 28 | background-image: url('../../assets/images/movie.png'); 29 | } 30 | 31 | .music-logo { 32 | background-image: url('../../assets/images/music.png'); 33 | } 34 | 35 | .city-logo { 36 | background-image: url('../../assets/images/city.png'); 37 | } 38 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | let argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/components/Book/BookTypeContent/BookTypeDisplayItem/bookTypeDisplayItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .book-item { 4 | display: inline-block; 5 | font-size: 1.3rem; 6 | margin-bottom: 30px; 7 | overflow: hidden; 8 | vertical-align: middle; 9 | width: 310px; 10 | } 11 | 12 | .book-image { 13 | float: left; 14 | height: 137px; 15 | margin-right: 18px; 16 | width: 95px; 17 | } 18 | 19 | .book-title { 20 | @include common-title; 21 | margin-bottom: 6px; 22 | } 23 | 24 | .score-image { 25 | @include score-image('../../../../assets/images/scoring.png'); 26 | } 27 | 28 | .average-score { 29 | color: #e09015; 30 | margin-left: 6px; 31 | } 32 | 33 | .book-more-info { 34 | margin-right: 20px; 35 | 36 | p { 37 | margin: 4px 0; 38 | } 39 | } 40 | 41 | .book-details { 42 | clear: both; 43 | padding: 15px 0 3px; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Music/MusicTagContent/MusicTagItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import Proptypes from 'prop-types'; 3 | import styles from './musicTagItem.scss'; 4 | 5 | export default class MusicTagItem extends PureComponent { 6 | static propTypes = { 7 | tag: Proptypes.string.isRequired, 8 | isSelected: Proptypes.bool.isRequired, 9 | handleClick: Proptypes.func.isRequired 10 | }; 11 | 12 | render() { 13 | const { tag, isSelected, handleClick } = this.props; 14 | return ( 15 |
  • 16 | handleClick(tag)} 21 | > 22 | {tag} 23 | 24 |
  • 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Book/BookTag/BookTagsItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './bookTagsItem.scss'; 4 | 5 | export default class BookTagsItem extends PureComponent { 6 | static propTypes = { 7 | tagObject: PropTypes.shape({ 8 | tagName: PropTypes.string.isRequired 9 | }).isRequired, 10 | isSelected: PropTypes.bool.isRequired, 11 | handleClick: PropTypes.func.isRequired 12 | }; 13 | 14 | render() { 15 | const { tagObject, isSelected, handleClick } = this.props; 16 | const selectClass = isSelected ? styles['selected-item'] : ''; 17 | return ( 18 |
  • 19 | 22 |
  • 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/City/CityTagItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './cityTagItem.scss'; 4 | 5 | export default class CityTagItem extends PureComponent { 6 | static propTypes = { 7 | isSelected: PropTypes.bool.isRequired, 8 | city: PropTypes.shape({ 9 | name: PropTypes.string.isRequired 10 | }).isRequired, 11 | setCurrentCity: PropTypes.func.isRequired 12 | }; 13 | 14 | render() { 15 | const { isSelected, city, setCurrentCity } = this.props; 16 | return ( 17 |
  • 18 | 26 |
  • 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | 18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 19 | // We don't polyfill it in the browser--this is user's responsibility. 20 | if (process.env.NODE_ENV === 'test') { 21 | require('raf').polyfill(global); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Header/HeaderSuggest/headerSuggest.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/styles/vars'; 2 | 3 | .search-suggest { 4 | background: #fff; 5 | border: 1px solid #ddd; 6 | margin-top: 8px; 7 | position: absolute; 8 | width: 356px; 9 | z-index: 99; 10 | 11 | li { 12 | border-bottom: 1px solid #eee; 13 | overflow: hidden; 14 | 15 | &:hover { 16 | background: #f9f9f9; 17 | } 18 | } 19 | } 20 | 21 | .search-link { 22 | color: #999; 23 | display: block; 24 | overflow: hidden; 25 | padding: 6px; 26 | zoom: 1; 27 | } 28 | 29 | .search-image { 30 | float: left; 31 | height: 56px; 32 | margin-right: 8px; 33 | margin-top: 3px; 34 | width: 40px; 35 | } 36 | 37 | .search-title { 38 | color: $common-font-color; 39 | display: inline-block; 40 | font-size: 1.2rem; 41 | font-weight: normal; 42 | margin-right: 10px; 43 | } 44 | 45 | .search-author, 46 | .search-genres { 47 | margin-top: 10px; 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTypeContent/MovieTypeDisplayItem/movieTypeDisplayItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | 3 | .movie-page { 4 | @include common-slider-page; 5 | 6 | li { 7 | width: 16.66%; 8 | } 9 | } 10 | 11 | .movie-image { 12 | height: 160px; 13 | width: 115px; 14 | } 15 | 16 | .movie-title { 17 | @include text-hidden; 18 | font-size: 1.3rem; 19 | font-weight: normal; 20 | margin: 12px 0 16px; 21 | 22 | a { 23 | color: #333; 24 | } 25 | } 26 | 27 | .score-image { 28 | @include score-image('../../../../assets/images/scoring.png'); 29 | } 30 | 31 | .average-score { 32 | color: #e09015; 33 | margin-left: 4px; 34 | } 35 | 36 | .buy-ticket { 37 | background: #268dcd; 38 | border-radius: 2px; 39 | color: #fff; 40 | cursor: pointer; 41 | height: 24px; 42 | line-height: 24px; 43 | margin: 12px auto 0; 44 | text-align: center; 45 | width: 90px; 46 | } 47 | 48 | .movie-genres { 49 | margin: 12px 0; 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Book/BookTagContent/BookTagPrompt/bookTagPrompt.scss: -------------------------------------------------------------------------------- 1 | .book-prompt { 2 | background: #f9f9f7; 3 | border: 1px solid #acacac; 4 | border-radius: 5px; 5 | box-shadow: 0 1px 1px #fdfdfd inset, 0 1px 1px #dcdbd4; 6 | box-sizing: border-box; 7 | max-height: 220px; 8 | padding: 10px; 9 | position: absolute; 10 | width: 330px; 11 | } 12 | 13 | .outside-triangle, 14 | .inside-triangle { 15 | border-bottom: 8px solid transparent; 16 | border-top: 8px solid transparent; 17 | height: 0; 18 | position: absolute; 19 | top: 45%; 20 | width: 0; 21 | } 22 | 23 | .outside-triangle { 24 | border-right: 8px solid #acacac; 25 | left: -8px; 26 | } 27 | 28 | .inside-triangle { 29 | border-right: 8px solid #f9f9f9; 30 | left: -7px; 31 | } 32 | 33 | .prompt-title { 34 | color: #666; 35 | margin-bottom: 6px; 36 | } 37 | 38 | .prompt-introduce { 39 | margin-bottom: 6px; 40 | } 41 | 42 | .prompt-summary { 43 | line-height: 1.6; 44 | max-height: 130px; 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Common/CommonSlider/CommonSliderDots/CommonSliderDot/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './commonSliderDot.scss'; 4 | 5 | export default class CommonSliderDot extends PureComponent { 6 | static propTypes = { 7 | index: PropTypes.number.isRequired, 8 | isSelected: PropTypes.bool.isRequired, 9 | backgroundColor: PropTypes.string.isRequired, 10 | handleDotClick: PropTypes.func.isRequired 11 | }; 12 | 13 | handleClick = () => { 14 | const { index, handleDotClick } = this.props; 15 | handleDotClick(index); 16 | }; 17 | 18 | render() { 19 | const { isSelected, backgroundColor } = this.props; 20 | return ( 21 | 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplayHeaderItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './cityActivityDisplayHeaderItem.scss'; 4 | 5 | export default class CityActivityDisplayHeaderItem extends PureComponent { 6 | static propTypes = { 7 | dayType: PropTypes.shape({ 8 | text: PropTypes.string.isRequired, 9 | value: PropTypes.string.isRequired 10 | }).isRequired, 11 | isSelected: PropTypes.bool.isRequired, 12 | handleClick: PropTypes.func.isRequired 13 | }; 14 | 15 | render() { 16 | const { dayType, isSelected, handleClick } = this.props; 17 | return ( 18 |
  • 19 | handleClick(dayType)} 24 | > 25 | {dayType.text} 26 | 27 |
  • 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/MovieTagDisplayList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import MovieTagDisplayItem from '../MovieTagDisplayItem'; 4 | import styles from './movieTagDisplayList.scss'; 5 | 6 | export default class MovieTagDisplayList extends PureComponent { 7 | static propTypes = { 8 | currentPage: PropTypes.number.isRequired, 9 | currentDirection: PropTypes.string.isRequired, 10 | movieList: PropTypes.arrayOf(PropTypes.array).isRequired 11 | }; 12 | 13 | render() { 14 | const { currentPage, currentDirection, movieList } = this.props; 15 | return ( 16 |
    17 | {movieList.map((movieArray, index) => ( 18 | 24 | ))} 25 |
    26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Book/BookTag/BookTagsList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import BookTagsItem from '../BookTagsItem'; 4 | import styles from './bookTagsList.scss'; 5 | 6 | export default class BookTagsList extends PureComponent { 7 | static propTypes = { 8 | bookTags: PropTypes.arrayOf(PropTypes.object).isRequired, 9 | currentBookTags: PropTypes.shape({ 10 | tagName: PropTypes.string.isRequired 11 | }).isRequired, 12 | handleClick: PropTypes.func.isRequired 13 | }; 14 | 15 | render() { 16 | const { bookTags, currentBookTags, handleClick } = this.props; 17 | return ( 18 | 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Common/CommonTagList/CommonTagItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './commonTagItem.scss'; 4 | 5 | export default class CommonTagItem extends PureComponent { 6 | static propTypes = { 7 | tag: PropTypes.string.isRequired, 8 | isSelected: PropTypes.bool.isRequired, 9 | handleClick: PropTypes.func.isRequired, 10 | activeTagClass: PropTypes.string.isRequired, 11 | hoverTagClass: PropTypes.string.isRequired 12 | }; 13 | 14 | render() { 15 | const { 16 | tag, 17 | isSelected, 18 | handleClick, 19 | activeTagClass, 20 | hoverTagClass 21 | } = this.props; 22 | const selectClass = isSelected ? activeTagClass : ''; 23 | 24 | return ( 25 |
  • 26 | 32 |
  • 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/City/CityTag/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CityTagItem from '../CityTagItem'; 4 | import styles from './cityTag.scss'; 5 | 6 | export default class CityTag extends PureComponent { 7 | static propTypes = { 8 | cityList: PropTypes.arrayOf(PropTypes.object).isRequired, 9 | currentCity: PropTypes.shape({ 10 | id: PropTypes.string.isRequired, 11 | name: PropTypes.string.isRequired 12 | }).isRequired, 13 | setCurrentCity: PropTypes.func.isRequired 14 | }; 15 | 16 | render() { 17 | const { cityList, currentCity, setCurrentCity } = this.props; 18 | return ( 19 | 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Header/HeaderInput/headerInput.scss: -------------------------------------------------------------------------------- 1 | .search-content { 2 | display: inline-block; 3 | } 4 | 5 | .search-form { 6 | position: relative; 7 | top: 9px; 8 | } 9 | 10 | .search-input { 11 | background: #fff; 12 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.16); 13 | height: 32px; 14 | padding-left: 10px; 15 | width: 350px; 16 | } 17 | 18 | .search-submit { 19 | background-position: 0 -40px; 20 | background-repeat: no-repeat; 21 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.16); 22 | color: transparent; 23 | cursor: pointer; 24 | height: 32px; 25 | margin: 0 0 0 -4px; 26 | vertical-align: middle; 27 | width: 37px; 28 | } 29 | 30 | .book-search-icon { 31 | background-image: url('../../../assets/images/book_search_btn.png'); 32 | } 33 | 34 | .movie-search-icon { 35 | background-image: url('../../../assets/images/movie_search_btn.png'); 36 | } 37 | 38 | .music-search-icon { 39 | background-image: url('../../../assets/images/music_search_btn.png'); 40 | } 41 | 42 | .city-search-icon { 43 | background-image: url('../../../assets/images/city_search_btn.png'); 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Common/CommonTagList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CommonTagItem from './CommonTagItem'; 4 | 5 | export default class CommonTagList extends PureComponent { 6 | static propTypes = { 7 | tagList: PropTypes.arrayOf(PropTypes.string).isRequired, 8 | currentTag: PropTypes.string.isRequired, 9 | handleClick: PropTypes.func.isRequired, 10 | activeTagClass: PropTypes.string.isRequired, 11 | hoverTagClass: PropTypes.string.isRequired 12 | }; 13 | 14 | render() { 15 | const { 16 | tagList, 17 | currentTag, 18 | handleClick, 19 | activeTagClass, 20 | hoverTagClass 21 | } = this.props; 22 | 23 | return ( 24 | 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplay/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CityActivityDisplayItem from '../CityActivityDisplayItem'; 4 | import styles from './cityActivityDisplay.scss'; 5 | 6 | export default class CityActivityDisplay extends PureComponent { 7 | static propTypes = { 8 | currentCity: PropTypes.shape({ 9 | id: PropTypes.string.isRequired, 10 | name: PropTypes.string.isRequired 11 | }).isRequired, 12 | activityTypes: PropTypes.arrayOf(PropTypes.object).isRequired, 13 | dayTypes: PropTypes.arrayOf(PropTypes.object).isRequired 14 | }; 15 | 16 | render() { 17 | const { currentCity, activityTypes, dayTypes } = this.props; 18 | return ( 19 |
    20 | {activityTypes.map(activityType => ( 21 | 27 | ))} 28 |
    29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Music/MusicTagContent/MusicTag/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import MusicTagItem from '../MusicTagItem'; 4 | import styles from './musicTag.scss'; 5 | 6 | export default class MusicTag extends PureComponent { 7 | static propTypes = { 8 | musicTags: PropTypes.arrayOf(PropTypes.string).isRequired, 9 | currentMusicTag: PropTypes.string.isRequired, 10 | setCurrentMusicTag: PropTypes.func.isRequired, 11 | setTagMusics: PropTypes.func.isRequired 12 | }; 13 | 14 | handleClick = tag => { 15 | const { setCurrentMusicTag, setTagMusics } = this.props; 16 | setCurrentMusicTag(tag); 17 | setTagMusics(tag); 18 | }; 19 | 20 | render() { 21 | const { musicTags, currentMusicTag } = this.props; 22 | return ( 23 | 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Book/BookTagContent/BookTagDisplayList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import BookTagDisplayItem from '../BookTagDisplayItem'; 4 | import styles from './bookTagDisplayList.scss'; 5 | 6 | export default class BookTagDisplayList extends PureComponent { 7 | static propTypes = { 8 | currentPage: PropTypes.number.isRequired, 9 | currentDirection: PropTypes.string.isRequired, 10 | bookList: PropTypes.arrayOf(PropTypes.array).isRequired, 11 | setBookPrompt: PropTypes.func.isRequired 12 | }; 13 | 14 | render() { 15 | const { 16 | currentPage, 17 | currentDirection, 18 | bookList, 19 | setBookPrompt 20 | } = this.props; 21 | return ( 22 |
    23 | {bookList.map((bookArray, index) => ( 24 | 31 | ))} 32 |
    33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Book/BookTagMoreContent/BookTagMoreDisplayItem/bookTagMoreDisplayItem.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../assets/styles/mixin'; 2 | @import '../../../../assets/styles/vars'; 3 | 4 | .book-item { 5 | border-bottom: 1px solid #ddd; 6 | overflow: hidden; 7 | padding: 15px 0; 8 | zoom: 1; 9 | } 10 | 11 | .book-image { 12 | float: left; 13 | height: 128px; 14 | margin-right: 20px; 15 | width: 90px; 16 | } 17 | 18 | .book-title { 19 | @include common-title; 20 | } 21 | 22 | .book-info { 23 | overflow: hidden; 24 | zoom: 1; 25 | } 26 | 27 | .book-other-info { 28 | margin: 6px 0 8px; 29 | } 30 | 31 | .score-image { 32 | @include score-image('../../../../assets/images/scoring.png'); 33 | } 34 | 35 | .average-score { 36 | @include average-score; 37 | margin: 0 9px; 38 | } 39 | 40 | .book-summary { 41 | line-height: 1.6; 42 | margin: 12px 0 10px; 43 | } 44 | 45 | .buy-books { 46 | color: $common-font-color; 47 | 48 | &:hover { 49 | background: $common-font-color; 50 | color: #fff; 51 | } 52 | } 53 | 54 | .e-book { 55 | color: #973f3a; 56 | float: right; 57 | 58 | &:hover { 59 | background: #973f31; 60 | color: #fff; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/Header/HeaderModules/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | import styles from './headerModules.scss'; 5 | 6 | export default class headerModules extends PureComponent { 7 | static propTypes = { 8 | modules: PropTypes.arrayOf(PropTypes.object).isRequired 9 | }; 10 | 11 | render() { 12 | const { modules } = this.props; 13 | 14 | return ( 15 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/MovieTag/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CommonTagList from '../../../Common/CommonTagList'; 4 | import styles from './movieTag.scss'; 5 | 6 | export default class MovieTag extends PureComponent { 7 | static propTypes = { 8 | movieTags: PropTypes.arrayOf(PropTypes.string).isRequired, 9 | currentMovieTag: PropTypes.string.isRequired, 10 | setCurrentMovieTag: PropTypes.func.isRequired, 11 | setTagMovies: PropTypes.func.isRequired 12 | }; 13 | 14 | handleClick = tag => { 15 | const { setCurrentMovieTag, setTagMovies } = this.props; 16 | setCurrentMovieTag(tag); 17 | setTagMovies(tag); 18 | }; 19 | 20 | render() { 21 | const { movieTags, currentMovieTag } = this.props; 22 | return ( 23 | 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Common/CommonSlider/CommonSliderDots/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import Proptypes from 'prop-types'; 3 | import CommonSliderDot from './CommonSliderDot'; 4 | import styles from './commonSliderDots.scss'; 5 | 6 | export default class CommonSliderDots extends PureComponent { 7 | static propTypes = { 8 | pageCount: Proptypes.number.isRequired, 9 | currentPage: Proptypes.number.isRequired, 10 | handleDotClick: Proptypes.func.isRequired, 11 | backgroundColor: Proptypes.string.isRequired 12 | }; 13 | 14 | getDotList() { 15 | const { 16 | pageCount, 17 | currentPage, 18 | handleDotClick, 19 | backgroundColor 20 | } = this.props; 21 | const dotList = []; 22 | for (let i = 0; i < pageCount; i += 1) { 23 | dotList.push( 24 | 31 | ); 32 | } 33 | return dotList; 34 | } 35 | 36 | render() { 37 | const dotList = this.getDotList(); 38 | return
    {dotList}
    ; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Common/CommonModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './commonModal.scss'; 4 | 5 | export default class CommonModal extends PureComponent { 6 | static propTypes = { 7 | hideModal: PropTypes.func.isRequired, 8 | header: PropTypes.node, 9 | body: PropTypes.node, 10 | footer: PropTypes.node 11 | }; 12 | 13 | static defaultProps = { 14 | header: 'default header', 15 | body: 'default body', 16 | footer: '' 17 | }; 18 | 19 | render() { 20 | const { header, body, footer, hideModal } = this.props; 21 | return ( 22 |
    23 |
    24 |
    25 |
    {header}
    26 | 27 |
    {body}
    28 | 29 |
    30 | {footer} 31 | 34 |
    35 |
    36 |
    37 |
    38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Common/CommonSlider/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CommonSliderDots from './CommonSliderDots'; 4 | import CommonSliderArrows from './CommonSliderArrows'; 5 | 6 | export default class CommonSlider extends PureComponent { 7 | static propTypes = { 8 | pageCount: PropTypes.number.isRequired, 9 | currentPage: PropTypes.number.isRequired, 10 | handleDotClick: PropTypes.func.isRequired, 11 | handleArrowClick: PropTypes.func.isRequired, 12 | backgroundColor: PropTypes.string 13 | }; 14 | 15 | static defaultProps = { 16 | backgroundColor: '#9b9a8e' 17 | }; 18 | 19 | render() { 20 | const { 21 | pageCount, 22 | currentPage, 23 | handleDotClick, 24 | handleArrowClick, 25 | backgroundColor 26 | } = this.props; 27 | return ( 28 |
    29 | 35 | 39 |
    40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Header/HeaderModules/headerModules.scss: -------------------------------------------------------------------------------- 1 | .module-list { 2 | display: inline-block; 3 | font-size: 1.3rem; 4 | left: 40px; 5 | position: relative; 6 | top: 10px; 7 | 8 | li { 9 | cursor: pointer; 10 | display: inline-block; 11 | margin-left: 25px; 12 | padding: 2px 5px; 13 | 14 | &:hover { 15 | background: #37a; 16 | color: #fff; 17 | } 18 | 19 | &:hover .outer-module-target { 20 | display: block; 21 | } 22 | } 23 | } 24 | 25 | .outer-module-target { 26 | display: none; 27 | margin-left: -40px; 28 | position: absolute; 29 | } 30 | 31 | .inner-module-target { 32 | background: #fff; 33 | border: 1px solid #e7eaf1; 34 | box-shadow: 0 5px 20px rgba(0, 34, 77, 0.1); 35 | margin-top: 10px; 36 | width: 100px; 37 | 38 | &::before { 39 | border-bottom: 10px solid #fff; 40 | border-left: 10px solid transparent; 41 | border-right: 10px solid transparent; 42 | content: ''; 43 | height: 0; 44 | margin-left: calc(50% - 10px); 45 | margin-top: -10px; 46 | position: absolute; 47 | width: 0; 48 | } 49 | } 50 | 51 | .module-link { 52 | color: #000; 53 | display: block; 54 | padding: 10px; 55 | text-align: center; 56 | 57 | &:hover { 58 | background: #f4f8fb; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/stores/modules/musicStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action, computed, flow } from 'mobx'; 2 | import { getCurrentTagMusics } from '../../apis'; 3 | 4 | const musicTags = [ 5 | '华语', 6 | '欧美', 7 | '日韩', 8 | '流行', 9 | '摇滚', 10 | '民谣', 11 | '原声', 12 | '轻音乐', 13 | '古典', 14 | '粤语', 15 | 'R&B' 16 | ]; 17 | 18 | class MusicStore { 19 | musicTags = musicTags; 20 | @observable currentMusicTag = musicTags[0]; 21 | @observable tagMusics = new Map(); 22 | 23 | @action 24 | setCurrentMusicTag = tag => { 25 | if (tag !== this.currentMusicTag) { 26 | this.currentMusicTag = tag; 27 | } 28 | }; 29 | 30 | @computed 31 | get currentTagMusicList() { 32 | const currentTagMusics = this.tagMusics.get(this.currentMusicTag); 33 | return currentTagMusics || []; 34 | } 35 | 36 | setTagMusics = flow( 37 | function*(tag, start, count, isAfresh = false) { 38 | const oldData = this.tagMusics.get(tag); 39 | if (oldData && !isAfresh) return; 40 | try { 41 | const response = yield getCurrentTagMusics(tag, start, count); 42 | const data = response.data.musics; 43 | this.tagMusics.set(tag, data); 44 | } catch (error) { 45 | console.log(error); 46 | } 47 | }.bind(this) 48 | ); 49 | } 50 | 51 | const musicStore = new MusicStore(); 52 | export default musicStore; 53 | -------------------------------------------------------------------------------- /src/components/Main/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route } from 'react-router-dom'; 3 | import BookTagContent from '../Book/BookTagContent'; 4 | import BookTagMoreContent from '../Book/BookTagMoreContent'; 5 | import BookTypeContent from '../Book/BookTypeContent'; 6 | import MovieTypeContent from '../Movie/MovieTypeContent'; 7 | import MovieTagContent from '../Movie/MovieTagContent'; 8 | import MusicTagContent from '../Music/MusicTagContent'; 9 | import CityActivity from '../City/CityActivity'; 10 | import styles from './main.scss'; 11 | 12 | export default function Main() { 13 | return ( 14 |
    15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
    30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Common/CommonSlider/CommonSliderArrows/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './commonSliderArrows.scss'; 4 | 5 | export default class CommonSliderArrows extends PureComponent { 6 | static propTypes = { 7 | handleArrowClick: PropTypes.func.isRequired, 8 | backgroundColor: PropTypes.string.isRequired 9 | }; 10 | 11 | handleLeftBtnClick = () => { 12 | const { handleArrowClick } = this.props; 13 | handleArrowClick('left'); 14 | }; 15 | 16 | handleRightBtnClick = () => { 17 | const { handleArrowClick } = this.props; 18 | handleArrowClick('right'); 19 | }; 20 | 21 | render() { 22 | return ( 23 |
    24 | 31 | ‹ 32 | 33 | 34 | 41 | › 42 | 43 |
    44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTypeContent/MovieTypeDisplayList/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import MovieTypeDisplayItem from '../MovieTypeDisplayItem'; 4 | import styles from './movieTypeDisplayList.scss'; 5 | 6 | export default class MovieTypeDisplayList extends PureComponent { 7 | static propTypes = { 8 | currentPage: PropTypes.number.isRequired, 9 | currentDirection: PropTypes.string.isRequired, 10 | movieList: PropTypes.arrayOf(PropTypes.array).isRequired, 11 | currentMovieType: PropTypes.shape({ 12 | value: PropTypes.string.isRequired, 13 | text: PropTypes.string.isRequired 14 | }).isRequired, 15 | setSelectedMovie: PropTypes.func.isRequired 16 | }; 17 | 18 | render() { 19 | const { 20 | currentPage, 21 | currentDirection, 22 | movieList, 23 | currentMovieType, 24 | setSelectedMovie 25 | } = this.props; 26 | return ( 27 |
    28 | {movieList.map((movieArray, index) => ( 29 | 37 | ))} 38 |
    39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplayHeader/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CityActivityDisplayHeaderItem from '../CityActivityDisplayHeaderItem'; 4 | import styles from './cityActivityDisplayHeader.scss'; 5 | 6 | export default class CityActivityDisplayHeader extends PureComponent { 7 | static propTypes = { 8 | currentDayType: PropTypes.shape({ 9 | value: PropTypes.string.isRequired, 10 | text: PropTypes.string.isRequired 11 | }).isRequired, 12 | currentActivityType: PropTypes.shape({ 13 | value: PropTypes.string.isRequired, 14 | text: PropTypes.string.isRequired 15 | }).isRequired, 16 | dayTypes: PropTypes.arrayOf(PropTypes.object).isRequired, 17 | handleClick: PropTypes.func.isRequired 18 | }; 19 | 20 | render() { 21 | const { 22 | currentDayType, 23 | currentActivityType, 24 | dayTypes, 25 | handleClick 26 | } = this.props; 27 | return ( 28 |
    29 |

    {currentActivityType.text}

    30 | 31 |
      32 | {dayTypes.map(dayType => ( 33 | 39 | ))} 40 |
    41 |
    42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/stores/modules/cityStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action, flow } from 'mobx'; 2 | import { getCities } from '../../apis'; 3 | 4 | const activityTypes = [ 5 | { value: 'music', text: '音乐' }, 6 | { value: 'drama', text: '戏剧' }, 7 | { value: 'exhibition', text: '展览' }, 8 | { value: 'salon', text: '讲座' }, 9 | { value: 'party', text: '聚会' }, 10 | { value: 'sports', text: '运动' }, 11 | { value: 'travel', text: '旅行' }, 12 | { value: 'commonweal', text: '公益' }, 13 | { value: 'film', text: '电影' } 14 | ]; 15 | 16 | const dayTypes = [ 17 | { value: 'today', text: '今天' }, 18 | { value: 'tomorrow', text: '明天' }, 19 | { value: 'weekend', text: '周末' }, 20 | { value: 'week', text: '最近一周' } 21 | ]; 22 | 23 | class CityStore { 24 | activityTypes = activityTypes; 25 | dayTypes = dayTypes; 26 | @observable cities = []; 27 | @observable currentCity = {}; 28 | 29 | @action 30 | setCurrentCity = city => { 31 | if (city.id !== this.currentCity.id) { 32 | this.currentCity = city; 33 | } 34 | }; 35 | 36 | setCities = flow( 37 | function*(start, count, isAfresh = false) { 38 | if (this.cities.length !== 0 && !isAfresh) { 39 | return; 40 | } 41 | try { 42 | const response = yield getCities(start, count); 43 | const data = response.data.locs; 44 | this.cities = data; 45 | this.setCurrentCity(data[0]); 46 | } catch (error) { 47 | console.log(error); 48 | } 49 | }.bind(this) 50 | ); 51 | } 52 | 53 | const cityStore = new CityStore(); 54 | export default cityStore; 55 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTypeContent/MovieModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CSSTransition } from 'react-transition-group'; 4 | import CommonModal from '../../../Common/CommonModal'; 5 | import styles from './movieModal.scss'; 6 | 7 | export default class MovieModal extends PureComponent { 8 | static propTypes = { 9 | selectedMovie: PropTypes.shape({ 10 | title: PropTypes.string.isRequired 11 | }).isRequired, 12 | isShowModal: PropTypes.bool.isRequired, 13 | hideModal: PropTypes.func.isRequired 14 | }; 15 | 16 | header = null; 17 | body = null; 18 | 19 | render() { 20 | const { selectedMovie, isShowModal, hideModal } = this.props; 21 | if (isShowModal) { 22 | this.header =

    您好

    ; 23 | this.body = ( 24 | 25 | 本程序并无实际购票功能,若您喜欢 26 | 27 | {selectedMovie.title} 28 | 29 | ,可往 30 | 31 | 豆瓣 32 | 33 | 查看 34 | 35 | ); 36 | } 37 | return ( 38 | 44 | 49 | 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
    29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/MovieTagDisplayItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { CSSTransition } from 'react-transition-group'; 3 | import PropTypes from 'prop-types'; 4 | import styles from './movieTagDisplayItem.scss'; 5 | 6 | export default class MovieTagDisplayItem extends PureComponent { 7 | static propTypes = { 8 | isShow: PropTypes.bool.isRequired, 9 | currentDirection: PropTypes.string.isRequired, 10 | movieArray: PropTypes.arrayOf(PropTypes.object).isRequired 11 | }; 12 | 13 | render() { 14 | const { isShow, currentDirection, movieArray } = this.props; 15 | return ( 16 | 22 |
      23 | {movieArray.map(movie => ( 24 |
    • 25 | 26 | {movie.title} 31 | 32 | 33 |

      34 | 35 | {movie.title} 36 | 37 |

      38 | 39 |

      40 | 评分: 41 | {movie.rating.average} 42 |

      43 | 44 |

      上映时间:{movie.year}

      45 |
    • 46 | ))} 47 |
    48 |
    49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/City/CityActivityDisplayMainItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getDateString } from '../../../utils/utils'; 4 | import styles from './cityActivityDisplayMainItem.scss'; 5 | 6 | export default class CityActivityDisplayMainItem extends PureComponent { 7 | static propTypes = { 8 | activity: PropTypes.shape({ 9 | alt: PropTypes.string.isRequired, 10 | image: PropTypes.string.isRequired, 11 | title: PropTypes.string.isRequired, 12 | begin_time: PropTypes.string.isRequired, 13 | end_time: PropTypes.string.isRequired, 14 | address: PropTypes.string.isRequired, 15 | wisher_count: PropTypes.number.isRequired, 16 | participant_count: PropTypes.number.isRequired 17 | }).isRequired 18 | }; 19 | 20 | render() { 21 | const { activity } = this.props; 22 | return ( 23 |
  • 24 | 25 | {activity.title} 30 | 31 | 32 |
    33 | 34 | {activity.title} 35 | 36 | 37 |

    38 | {getDateString(activity.begin_time, activity.end_time)} 39 |

    40 | 41 |

    42 | {activity.address} 43 |

    44 | 45 |

    {activity.wisher_count + activity.participant_count}人关注

    46 |
    47 |
  • 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Music/MusicTagContent/MusicDisplay/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getSongArray } from '../../../../utils/utils'; 4 | import styles from './musicDisplay.scss'; 5 | 6 | export default class MusicDisplay extends PureComponent { 7 | static propTypes = { 8 | musicList: PropTypes.arrayOf(PropTypes.object).isRequired 9 | }; 10 | 11 | render() { 12 | const { musicList } = this.props; 13 | return ( 14 | 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/City/CityActivity/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer, inject } from 'mobx-react'; 3 | import PropTypes from 'prop-types'; 4 | import { Facebook as Loading } from 'react-content-loader'; 5 | import CityActivityDisplay from '../CityActivityDisplay'; 6 | import CityTag from '../CityTag'; 7 | 8 | @inject('cityStore') 9 | @observer 10 | export default class CityActivity extends Component { 11 | static propTypes = { 12 | cityStore: PropTypes.shape({ 13 | cities: PropTypes.arrayOf(PropTypes.object).isRequired, 14 | currentCity: PropTypes.object.isRequired, 15 | activityTypes: PropTypes.arrayOf(PropTypes.object).isRequired, 16 | dayTypes: PropTypes.arrayOf(PropTypes.object).isRequired, 17 | setCurrentCity: PropTypes.func.isRequired, 18 | setCities: PropTypes.func.isRequired 19 | }).isRequired 20 | }; 21 | 22 | componentDidMount() { 23 | const { setCities } = this.props.cityStore; 24 | setCities(); 25 | } 26 | 27 | render() { 28 | const { 29 | cities, 30 | currentCity, 31 | activityTypes, 32 | dayTypes, 33 | setCurrentCity 34 | } = this.props.cityStore; 35 | return ( 36 |
    37 | {cities.length !== 0 ? ( 38 |
    39 | 44 | 45 | 50 |
    51 | ) : ( 52 | 53 | )} 54 |
    55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/Music/MusicTagContent/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer, inject } from 'mobx-react'; 3 | import PropTypes from 'prop-types'; 4 | import { Facebook as Loading } from 'react-content-loader'; 5 | import MusicTag from './MusicTag'; 6 | import MusicDisplay from './MusicDisplay'; 7 | 8 | @inject('musicStore') 9 | @observer 10 | export default class MusicTagContent extends Component { 11 | static propTypes = { 12 | musicStore: PropTypes.shape({ 13 | musicTags: PropTypes.arrayOf(PropTypes.string).isRequired, 14 | currentMusicTag: PropTypes.string.isRequired, 15 | currentTagMusicList: PropTypes.arrayOf(PropTypes.object).isRequired, 16 | setCurrentMusicTag: PropTypes.func.isRequired, 17 | setTagMusics: PropTypes.func.isRequired 18 | }).isRequired 19 | }; 20 | 21 | componentDidMount() { 22 | const { currentMusicTag, setTagMusics } = this.props.musicStore; 23 | setTagMusics(currentMusicTag); 24 | } 25 | 26 | render() { 27 | const { 28 | musicTags, 29 | currentMusicTag, 30 | currentTagMusicList, 31 | setCurrentMusicTag, 32 | setTagMusics 33 | } = this.props.musicStore; 34 | return ( 35 |
    36 | 42 | {currentTagMusicList.length !== 0 ? ( 43 | 44 | ) : ( 45 | 52 | )} 53 |
    54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/Book/BookTagMoreContent/BookTagMoreDisplay/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observable, action } from 'mobx'; 3 | import { observer } from 'mobx-react'; 4 | import PropTypes from 'prop-types'; 5 | import BookTagMoreDisplayList from '../BookTagMoreDisplayList'; 6 | import LoadMoreBooks from '../LoadMoreBooks'; 7 | import styles from './bookTagMoreDisplay.scss'; 8 | 9 | @observer 10 | export default class BookTagMoreDisplay extends Component { 11 | static propTypes = { 12 | currentBookTag: PropTypes.string.isRequired, 13 | bookList: PropTypes.arrayOf(PropTypes.object).isRequired, 14 | setTagBooks: PropTypes.func.isRequired 15 | }; 16 | 17 | componentWillReceiveProps(nextProps) { 18 | if (nextProps.currentBookTag !== this.props.currentBookTag) { 19 | this.setPageCount(10); 20 | } 21 | } 22 | 23 | @action 24 | setPageCount = (count, growth) => { 25 | if (count) { 26 | this.pageCount = count; 27 | } else { 28 | this.pageCount += growth; 29 | } 30 | const { currentBookTag, bookList, setTagBooks } = this.props; 31 | const { length } = bookList; 32 | if (this.pageCount > length) { 33 | setTagBooks( 34 | currentBookTag, 35 | this.pageCount, 36 | this.pageCount - length, 37 | true, 38 | true 39 | ); 40 | } 41 | }; 42 | 43 | @observable pageCount = 10; 44 | 45 | render() { 46 | const { currentBookTag, bookList } = this.props; 47 | const newBookList = bookList.slice(0, this.pageCount); 48 | return ( 49 |
    50 |
    51 |

    豆瓣读书标签: {currentBookTag}

    52 |
    53 | 54 | 55 | 56 |
    57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Book/BookTag/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import BookTagsList from './BookTagsList'; 4 | import CommonTagList from '../../Common/CommonTagList'; 5 | import styles from './bookTag.scss'; 6 | 7 | export default class BookTag extends PureComponent { 8 | static propTypes = { 9 | bookTags: PropTypes.arrayOf(PropTypes.object).isRequired, 10 | currentBookTags: PropTypes.shape({ 11 | tagName: PropTypes.string.isRequired 12 | }).isRequired, 13 | currentBookTag: PropTypes.string.isRequired, 14 | setCurrentBookTags: PropTypes.func.isRequired, 15 | setCurrentBookTag: PropTypes.func.isRequired, 16 | setTagBooks: PropTypes.func.isRequired 17 | }; 18 | 19 | handleTagsClick = tags => { 20 | const { setCurrentBookTags, setCurrentBookTag, setTagBooks } = this.props; 21 | setCurrentBookTags(tags); 22 | setCurrentBookTag(tags.subTags[0]); 23 | setTagBooks(tags.subTags[0]); 24 | }; 25 | 26 | handleSubTagClick = tag => { 27 | const { setCurrentBookTag, setTagBooks } = this.props; 28 | setCurrentBookTag(tag); 29 | setTagBooks(tag); 30 | }; 31 | 32 | render() { 33 | const { bookTags, currentBookTags, currentBookTag } = this.props; 34 | 35 | return ( 36 | 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer, inject } from 'mobx-react'; 3 | import PropTypes from 'prop-types'; 4 | import HeaderInput from './HeaderInput'; 5 | import HeaderModules from './HeaderModules'; 6 | import styles from './header.scss'; 7 | 8 | @inject('headerStore') 9 | @observer 10 | export default class Header extends Component { 11 | static propTypes = { 12 | headerStore: PropTypes.shape({ 13 | currentModule: PropTypes.object.isRequired, 14 | modules: PropTypes.arrayOf(PropTypes.object).isRequired, 15 | setCurrentModule: PropTypes.func.isRequired 16 | }).isRequired 17 | }; 18 | 19 | componentWillMount() { 20 | this.onHashChange(); 21 | } 22 | 23 | componentWillReceiveProps() { 24 | this.onHashChange(); 25 | } 26 | 27 | // 路由变化时,切换到不同路由组件 28 | onHashChange() { 29 | const { modules, currentModule, setCurrentModule } = this.props.headerStore; 30 | const hashname = window.location.pathname; 31 | if (hashname.indexOf(currentModule.value) !== -1) return; 32 | for (let i = 0, ii = modules.length; i < ii; i += 1) { 33 | if (hashname.indexOf(modules[i].value) !== -1) { 34 | setCurrentModule(modules[i]); 35 | return; 36 | } 37 | } 38 | } 39 | 40 | render() { 41 | const { currentModule, modules } = this.props.headerStore; 42 | 43 | return ( 44 |
    48 |
    49 |

    52 | {currentModule.text} 53 |

    54 | 55 | 56 | 57 |
    58 |
    59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/stores/modules/headerStore.js: -------------------------------------------------------------------------------- 1 | import { observable, action } from 'mobx'; 2 | 3 | const modules = [ 4 | { 5 | value: 'book', 6 | text: '读书', 7 | field: 'books', 8 | logo: 'book-logo', 9 | searchIcon: 'book-search-icon', 10 | placeholder: '书名、作者、ISBN', 11 | backgroundColor: '#f6f6f1', 12 | subTypes: [ 13 | { 14 | text: '按标签分类', 15 | path: 'book-tag' 16 | }, 17 | { 18 | text: '按类型分类', 19 | path: 'book-type' 20 | } 21 | ] 22 | }, 23 | { 24 | value: 'movie', 25 | text: '电影', 26 | field: 'subjects', 27 | logo: 'movie-logo', 28 | searchIcon: 'movie-search-icon', 29 | placeholder: '电影、影人、影院、电视剧', 30 | backgroundColor: '#f0f3f5', 31 | subTypes: [ 32 | { 33 | text: '按时间分类', 34 | path: 'movie-show-time' 35 | }, 36 | { 37 | text: '按标签分类', 38 | path: 'movie-tag' 39 | } 40 | ] 41 | }, 42 | { 43 | value: 'music', 44 | text: '音乐', 45 | field: 'musics', 46 | logo: 'music-logo', 47 | searchIcon: 'music-search-icon', 48 | placeholder: '唱片名、表演者、条码、ISRC', 49 | backgroundColor: '#f0f3ef', 50 | subTypes: [ 51 | { 52 | text: '按标签分类', 53 | path: 'music-tag' 54 | } 55 | ] 56 | }, 57 | { 58 | value: 'city', 59 | text: '同城', 60 | logo: 'city-logo', 61 | searchIcon: 'city-search-icon', 62 | placeholder: 'sorry, 同城活动暂无搜索功能', 63 | backgroundColor: '#f6f5f2', 64 | subTypes: [ 65 | { 66 | text: '按城市分类', 67 | path: 'city' 68 | } 69 | ] 70 | } 71 | ]; 72 | 73 | class HeaderStore { 74 | modules = modules; 75 | @observable currentModule = modules[0]; 76 | 77 | @action 78 | setCurrentModule = module => { 79 | this.currentModule = module; 80 | }; 81 | } 82 | 83 | const headerStore = new HeaderStore(); 84 | export default headerStore; 85 | -------------------------------------------------------------------------------- /src/components/Book/BookTagContent/BookTagPrompt/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './bookTagPrompt.scss'; 4 | 5 | export default class BookTagPrompt extends PureComponent { 6 | static propTypes = { 7 | bookPrompt: PropTypes.shape({ 8 | book: PropTypes.object, 9 | index: PropTypes.number 10 | }).isRequired, 11 | position: PropTypes.shape({ 12 | width: PropTypes.number, 13 | height: PropTypes.number, 14 | top: PropTypes.number, 15 | left: PropTypes.number 16 | }).isRequired 17 | }; 18 | 19 | // 计算提示框显示位置 20 | getPromptPosition() { 21 | const { bookPrompt, position } = this.props; 22 | const { width, height, top } = position; 23 | const { index } = bookPrompt; 24 | const promptTop = index < 5 ? top + 35 : top + height * 0.5 + 10; 25 | const columnIndex = (index % 5) + 1; 26 | const promptLeft = columnIndex * (width * 0.2) - columnIndex * 8; 27 | return { 28 | top: `${promptTop}px`, 29 | left: `${promptLeft}px` 30 | }; 31 | } 32 | 33 | render() { 34 | const { book } = this.props.bookPrompt; 35 | const promptPosition = this.getPromptPosition(); 36 | return ( 37 |
    41 | 42 | 43 |

    {book.title}

    44 |

    45 | {book.author.join()} / {book.pubdate} / {book.publisher} 46 |

    47 |

    48 | {book.summary.length >= 160 49 | ? `${book.summary.substring(0, 160)}...` 50 | : book.summary} 51 |

    52 |
    53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Book/BookTagContent/BookTagDisplayItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { CSSTransition } from 'react-transition-group'; 3 | import PropTypes from 'prop-types'; 4 | import styles from './bookTagDisplayItem.scss'; 5 | 6 | export default class BookTagDisplayItem extends PureComponent { 7 | static propTypes = { 8 | isShow: PropTypes.bool.isRequired, 9 | currentDirection: PropTypes.string.isRequired, 10 | bookArray: PropTypes.arrayOf(PropTypes.object).isRequired, 11 | setBookPrompt: PropTypes.func.isRequired 12 | }; 13 | 14 | onMouseChange = (bookObject, index) => { 15 | const { setBookPrompt } = this.props; 16 | setBookPrompt(bookObject, index); 17 | }; 18 | 19 | render() { 20 | const { isShow, currentDirection, bookArray } = this.props; 21 | return ( 22 | 28 | 52 | 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Movie/MovieTagContent/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer, inject } from 'mobx-react'; 3 | import PropTypes from 'prop-types'; 4 | import { Facebook as Loading } from 'react-content-loader'; 5 | import MovieTagDisplay from './MovieTagDisplay'; 6 | import MovieTag from './MovieTag'; 7 | import styles from './movieTagContent.scss'; 8 | 9 | @inject('movieStore') 10 | @observer 11 | export default class MovieTagContent extends Component { 12 | static propTypes = { 13 | movieStore: PropTypes.shape({ 14 | movieTags: PropTypes.arrayOf(PropTypes.string).isRequired, 15 | currentMovieTag: PropTypes.string.isRequired, 16 | currentTagMovieList: PropTypes.arrayOf(PropTypes.array).isRequired, 17 | setCurrentMovieTag: PropTypes.func.isRequired, 18 | setTagMovies: PropTypes.func.isRequired 19 | }).isRequired 20 | }; 21 | 22 | componentDidMount() { 23 | const { currentMovieTag, setTagMovies } = this.props.movieStore; 24 | setTagMovies(currentMovieTag); 25 | } 26 | 27 | pageCount = 0; 28 | 29 | render() { 30 | const { 31 | movieTags, 32 | currentMovieTag, 33 | currentTagMovieList, 34 | setCurrentMovieTag, 35 | setTagMovies 36 | } = this.props.movieStore; 37 | this.pageCount = currentTagMovieList.length; 38 | return ( 39 |
    40 |
    41 | {this.pageCount !== 0 ? ( 42 | 46 | ) : ( 47 | 48 | )} 49 |
    50 | 51 | 57 |
    58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/apis/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export function getSearchData(keyword, moduleType, start = 0, count = 6) { 4 | return axios.get(`/${moduleType}/search`, { 5 | params: { 6 | q: keyword, 7 | start, 8 | count 9 | } 10 | }); 11 | } 12 | 13 | export function getCurrentTagBooks(tag, start = 0, count = 40) { 14 | return axios.get('/book/search', { 15 | params: { 16 | tag, 17 | start, 18 | count 19 | } 20 | }); 21 | } 22 | 23 | export function getCurrentTypeBooks(type, start = 0, count = 12) { 24 | return axios.get('/book/search', { 25 | params: { 26 | q: type, 27 | start, 28 | count 29 | } 30 | }); 31 | } 32 | 33 | export function getCurrentTypeMovies(type, start = 0, count = 48) { 34 | return axios.get(`/movie/${type}`, { 35 | params: { 36 | start, 37 | count 38 | } 39 | }); 40 | } 41 | 42 | export function getCurrentTagMovies(tag, start = 0, count = 20) { 43 | return axios.get('/movie/search', { 44 | params: { 45 | tag, 46 | start, 47 | count 48 | } 49 | }); 50 | } 51 | 52 | export function getCurrentTagMusics(tag, start = 0, count = 24) { 53 | return axios.get('/music/search', { 54 | params: { 55 | tag, 56 | start, 57 | count 58 | } 59 | }); 60 | } 61 | 62 | export function getCities(start = 0, count = 20) { 63 | return axios.get('/loc/list', { 64 | params: { 65 | start, 66 | count 67 | } 68 | }); 69 | } 70 | 71 | export function getActivities(loc, dayType, type, start = 0, count = 6) { 72 | return axios.get('/event/list', { 73 | params: { 74 | loc, 75 | day_type: dayType, 76 | type, 77 | start, 78 | count 79 | } 80 | }); 81 | } 82 | 83 | export default { 84 | getSearchData, 85 | getCurrentTagBooks, 86 | getCurrentTypeBooks, 87 | getCurrentTypeMovies, 88 | getCurrentTagMovies, 89 | getCurrentTagMusics, 90 | getCities, 91 | getActivities 92 | }; 93 | -------------------------------------------------------------------------------- /src/components/Header/HeaderSuggest/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { observable, action } from 'mobx'; 3 | import PropTypes from 'prop-types'; 4 | import { processedAuthor } from '../../../utils/utils'; 5 | import styles from './headerSuggest.scss'; 6 | 7 | export default class HeaderSuggest extends PureComponent { 8 | static propTypes = { 9 | searchData: PropTypes.arrayOf(PropTypes.object).isRequired, 10 | isFocusOnInput: PropTypes.bool.isRequired 11 | }; 12 | 13 | @action 14 | setIsFocusOnList = isFocus => { 15 | this.isFocusOnList = isFocus; 16 | }; 17 | 18 | @observable isFocusOnList = false; 19 | 20 | render() { 21 | const { isFocusOnInput, searchData } = this.props; 22 | 23 | return ( 24 | 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right