├── .vscode └── settings.json ├── .eslintignore ├── .gitignore ├── src ├── view │ ├── miao │ │ ├── index.js │ │ ├── miao.less │ │ └── miao-component.js │ ├── list │ │ ├── list.less │ │ ├── index.js │ │ └── list-component.js │ └── home │ │ ├── home.less │ │ ├── index.js │ │ └── home-component.js ├── .DS_Store ├── favicon.ico ├── module │ ├── mo-button │ │ ├── hand.png │ │ ├── index.js │ │ └── button.less │ ├── mo-steps │ │ ├── index.js │ │ ├── steps.js │ │ ├── step.js │ │ └── steps.less │ ├── mo-spin │ │ ├── spin.less.json │ │ ├── index.js │ │ └── spin.less │ ├── co-simple-list │ │ ├── list.less │ │ ├── index.js │ │ └── mo-toast │ │ │ ├── index.js │ │ │ ├── toast.less │ │ │ ├── assets │ │ │ ├── success.svg │ │ │ ├── info.svg │ │ │ ├── warning.svg │ │ │ └── error.svg │ │ │ └── toast.js │ ├── mo-result-card │ │ ├── resultcard.less │ │ └── index.js │ ├── mo-toast │ │ ├── assets │ │ │ ├── success.svg │ │ │ ├── info.svg │ │ │ ├── warning.svg │ │ │ └── error.svg │ │ ├── toast.css │ │ ├── index.js │ │ ├── toast.less │ │ └── toast.js │ ├── mo-list-loading │ │ ├── index.js │ │ └── listloading.less │ ├── mo-carousel │ │ ├── index.js │ │ ├── carousel.css │ │ ├── carousel.less │ │ ├── method.js │ │ ├── pagination.js │ │ └── carousel.js │ ├── mo-transtion │ │ ├── index.js │ │ └── transtion.less │ ├── mo-table │ │ ├── table.less │ │ └── index.js │ ├── mo-segmented │ │ ├── segmented.less │ │ ├── tabs-modules.js │ │ └── index.js │ ├── mo-icon │ │ ├── icon.less │ │ ├── index.js │ │ └── svg-config.js │ ├── mo-dialog │ │ ├── modal.less │ │ ├── index.js │ │ ├── modal.js │ │ └── modal-modules.js │ ├── mo-infinite-scroll │ │ └── index.js │ ├── mo-tabs │ │ ├── tabs-modules.js │ │ ├── tabs.less │ │ └── index.js │ └── mo-time-count │ │ └── index.js ├── reducers │ ├── index.js │ ├── user-info-reducer.js │ └── data-list-reducer.js ├── router │ ├── router.less │ └── index.js ├── store │ └── index.js ├── libs │ └── my-util │ │ └── index.js ├── app.js ├── layout │ ├── user-card │ │ ├── index.js │ │ ├── user-card.less │ │ └── component.js │ └── list-tabs │ │ ├── list-tabs.less │ │ ├── index.js │ │ └── component.js ├── styles │ └── reset.less └── couter.js ├── img ├── demo.gif ├── iterm1.png ├── iterm2.png ├── 文件结构.xmind └── structure.png ├── old-verson ├── old-verson-1.0.zip └── old-verson-2.0.zip ├── .babelrc ├── postcss.config.js ├── index.html ├── dist ├── index.html └── bundle.css ├── server.js ├── .eslintrc.js ├── mock ├── db.js └── api.js ├── LICENSE ├── webpack.dev.config.js ├── package.json ├── webpack.prod.config.js └── README.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | dependent 4 | coverage 5 | webpack.*.js 6 | *Server.js 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | npm-debug.log 4 | yarn-debug.log 5 | .DS_Store 6 | .vscode -------------------------------------------------------------------------------- /src/view/miao/index.js: -------------------------------------------------------------------------------- 1 | import Miao from './miao-component.js' 2 | 3 | 4 | export default Miao -------------------------------------------------------------------------------- /img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/img/demo.gif -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /img/iterm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/img/iterm1.png -------------------------------------------------------------------------------- /img/iterm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/img/iterm2.png -------------------------------------------------------------------------------- /img/文件结构.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/img/文件结构.xmind -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /img/structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/img/structure.png -------------------------------------------------------------------------------- /old-verson/old-verson-1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/old-verson/old-verson-1.0.zip -------------------------------------------------------------------------------- /old-verson/old-verson-2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/old-verson/old-verson-2.0.zip -------------------------------------------------------------------------------- /src/module/mo-button/hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumars/boilerplate-webpack-react-es6-cssModule/HEAD/src/module/mo-button/hand.png -------------------------------------------------------------------------------- /src/module/mo-steps/index.js: -------------------------------------------------------------------------------- 1 | import Steps from './steps' 2 | import Step from './step' 3 | 4 | Steps.Step = Step 5 | 6 | 7 | export default Steps 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/module/mo-spin/spin.less.json: -------------------------------------------------------------------------------- 1 | {"dyy":"spin-dyy-oX8Ta","laballspin":"spin-laballspin-2LrMx","la-dark":"spin-la-dark-1KlJY","ball-spin":"spin-ball-spin-3cuzS","la-sm":"spin-la-sm-2MZyy","la-2x":"spin-la-2x-1om4p","la-3x":"spin-la-3x-320-W"} -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false 5 | }], 6 | "stage-0", 7 | "react" 8 | ], 9 | "plugins": [ 10 | "react-hot-loader/babel", 11 | "transform-decorators-legacy" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { UserReducer } from './user-info-reducer' 3 | import { DataListReducer } from './data-list-reducer' 4 | 5 | export default combineReducers({ 6 | UserReducer, 7 | DataListReducer 8 | }) -------------------------------------------------------------------------------- /src/view/list/list.less: -------------------------------------------------------------------------------- 1 | .wrap { 2 | box-sizing: border-box; 3 | padding: 20px; 4 | } 5 | 6 | .btn { 7 | width: 300px !important; 8 | margin: 40px auto !important; 9 | color: #fff !important; 10 | background: #ff5252 !important; 11 | } 12 | -------------------------------------------------------------------------------- /src/module/co-simple-list/list.less: -------------------------------------------------------------------------------- 1 | .list { 2 | padding-top: 20px; 3 | } 4 | .single { 5 | text-align: left; 6 | line-height: 2.5; 7 | &:nth-child(2n+1) { 8 | background-color: #eee 9 | } 10 | div { 11 | display: inline-block; 12 | padding: 0 40px; 13 | text-align: center; 14 | } 15 | } -------------------------------------------------------------------------------- /src/router/router.less: -------------------------------------------------------------------------------- 1 | .fill { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | width: 100%; 8 | max-width: 750px; 9 | margin: 0 auto; 10 | font-size: 32px; 11 | } 12 | 13 | .notfund { 14 | padding-top: 50px; 15 | text-align: center; 16 | font-size: 24px; 17 | } 18 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware} from 'redux' 2 | import thunk from 'redux-thunk' 3 | import reducers from '../reducers' 4 | 5 | 6 | let store = createStore( 7 | reducers, 8 | window.devToolsExtension && window.devToolsExtension(), 9 | applyMiddleware(thunk) 10 | ) 11 | 12 | export default store 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer'), 4 | require('postcss-px-to-viewport')({ 5 | viewportWidth: 750, 6 | viewportHeight: 568, 7 | unitPrecision: 5, 8 | viewportUnit: 'vw', 9 | selectorBlackList: [], 10 | minPixelValue: 1, 11 | mediaQuery: false 12 | }) 13 | ] 14 | } -------------------------------------------------------------------------------- /src/view/list/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import ListComponent from './list-component.js' 3 | 4 | 5 | const mapDispatchToProps = (dispatch,props) => { 6 | return { 7 | goBack() { 8 | props.history.replace('/') 9 | } 10 | } 11 | } 12 | 13 | 14 | const List = connect( 15 | null, 16 | mapDispatchToProps 17 | )(ListComponent) 18 | 19 | export default List -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | react-starter-kit 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | react-starter-kit
-------------------------------------------------------------------------------- /src/module/mo-result-card/resultcard.less: -------------------------------------------------------------------------------- 1 | .wrap { 2 | position: relative; 3 | box-sizing: border-box; 4 | padding: 60px; 5 | line-height: 1; 6 | text-align: center; 7 | background: #fff; 8 | h2 { 9 | margin: 30px auto; 10 | font-size: 40px; 11 | font-weight: normal; 12 | } 13 | h3 { 14 | font-size: 28px; 15 | font-weight: normal; 16 | color: #666; 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/libs/my-util/index.js: -------------------------------------------------------------------------------- 1 | const _ut = (function() { 2 | const fixNum = function(num) { 3 | return num.toFixed(2) 4 | } 5 | 6 | const myfetch = async function(url, option) { 7 | const res = await fetch(url,option) 8 | if (!res.ok) { 9 | return Promise.reject(res) 10 | } 11 | return res.json() 12 | } 13 | 14 | return { 15 | fixNum, 16 | fetch: myfetch 17 | } 18 | })() 19 | 20 | export default _ut -------------------------------------------------------------------------------- /src/view/home/home.less: -------------------------------------------------------------------------------- 1 | .wrap { 2 | box-sizing: border-box; 3 | padding: 20px; 4 | } 5 | 6 | .tip { 7 | font-size: 24px; 8 | color: #666; 9 | text-align: center; 10 | } 11 | 12 | .hello { 13 | margin: 60px auto 80px; 14 | padding: 6px 0; 15 | text-align: center; 16 | border-bottom: solid 1px #0e90d2; 17 | } 18 | 19 | .button { 20 | width: 400px; 21 | margin: 40px auto; 22 | line-height: 2.3; 23 | color: #fff; 24 | text-align: center; 25 | border-radius: 5px; 26 | background: #0e90d2; 27 | } -------------------------------------------------------------------------------- /src/module/mo-toast/assets/success.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './router'; 4 | import { AppContainer } from 'react-hot-loader'; 5 | 6 | if(process.env.NODE_ENV === 'development') { 7 | const render = (Component) => { 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root')); 13 | }; 14 | 15 | render(App); 16 | 17 | if (module.hot) { 18 | module.hot.accept('./router', () => { 19 | render(App) 20 | }); 21 | } 22 | } else { 23 | ReactDOM.render(, document.getElementById('root')); 24 | } 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.dev.config'); 4 | 5 | var compiler = webpack(config); 6 | var server = new WebpackDevServer(compiler, { 7 | publicPath: config.output.publicPath, 8 | hot: true, 9 | historyApiFallback: true, 10 | inline: true, 11 | stats: { 12 | colors: true, 13 | hash: false, 14 | timings: true, 15 | chunks: false, 16 | chunkModules: false, 17 | modules: false 18 | } 19 | }); 20 | 21 | server.listen(3000, 'localhost', function(err, result) { 22 | if (err) { 23 | return console.log(err); 24 | } 25 | console.log('Listening at http://localhost:3000/'); 26 | }); -------------------------------------------------------------------------------- /src/module/mo-list-loading/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types' 3 | import './listloading.less'; 4 | 5 | const ListLoading = ({className}) => ( 6 |
7 |
8 |
加载中...
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ) 19 | 20 | ListLoading.propTypes = { 21 | className: PropTypes.bool 22 | }; 23 | 24 | export default ListLoading 25 | 26 | 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "parserOptions": { 3 | "ecmaVersion": 8, 4 | "ecmaFeatures": { 5 | "jsx": true 6 | } 7 | }, 8 | "parser": "babel-eslint", 9 | "env": { 10 | "browser": true, 11 | "es6": true, 12 | "node": true, 13 | "mocha": true 14 | }, 15 | "rules": { 16 | "no-console": ["error", { 17 | "allow": ["warn", "error", "log", "info"] 18 | }], 19 | "react/jsx-uses-react": "error", 20 | "react/jsx-uses-vars": "error", 21 | "react/prop-types": [0] 22 | }, 23 | "plugins": [ 24 | "react" 25 | ], 26 | "extends": ["eslint:recommended", "plugin:react/recommended"] 27 | }; -------------------------------------------------------------------------------- /mock/db.js: -------------------------------------------------------------------------------- 1 | 2 | const list = { 3 | "movie": { 4 | "data": [ 5 | {"name": "肖申克的救赎"}, 6 | {"name": "这个杀手不太冷"}, 7 | {"name": "霸王别姬"}, 8 | {"name": "阿甘正传"}, 9 | {"name": "美丽人生"}, 10 | {"name": "千与千寻"}, 11 | {"name": "辛德勒的名单"}, 12 | {"name": "海上钢琴师"}, 13 | {"name": "机器人总动员"}, 14 | {"name": "盗梦空间"} 15 | ] 16 | }, 17 | "book": { 18 | "data": [ 19 | {"name": "窗边的小豆豆"}, 20 | {"name": "摆渡人"}, 21 | {"name": "追风筝的人"}, 22 | {"name": "浮生六记"}, 23 | {"name": "现代汉语词典"}, 24 | {"name": "万历十五年"}, 25 | {"name": "天才在左 疯子在右"}, 26 | {"name": "解忧杂货店"}, 27 | {"name": "半小时漫画中国史"}, 28 | {"name": "新版中日交流标准日本语"} 29 | ] 30 | } 31 | } 32 | 33 | 34 | 35 | module.exports = { 36 | list 37 | } 38 | -------------------------------------------------------------------------------- /src/module/mo-carousel/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import Carousel from "./carousel" 4 | import PaginationDecorator from "./pagination" 5 | 6 | const PaginationCarousel = PaginationDecorator(Carousel); 7 | 8 | class Wraper extends Component { 9 | constructor(props) { 10 | super(props) 11 | } 12 | 13 | render() { 14 | const { showDot } = this.props 15 | if(showDot) { 16 | return 17 | } 18 | 19 | return ( 20 | 21 | ) 22 | } 23 | } 24 | 25 | Carousel.propTypes = { 26 | showDot: PropTypes.bool 27 | } 28 | 29 | 30 | export default Wraper -------------------------------------------------------------------------------- /src/module/mo-spin/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types' 3 | import './spin.less'; 4 | 5 | var classNames = require('classnames'); 6 | 7 | 8 | const Spin = ({visible=true, className, style, color="#333"}) => ( 9 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ) 27 | 28 | Spin.propTypes = { 29 | visible: PropTypes.bool, 30 | className: PropTypes.bool, 31 | style: PropTypes.object 32 | }; 33 | 34 | export default Spin 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/view/miao/miao.less: -------------------------------------------------------------------------------- 1 | .wrap { 2 | box-sizing: border-box; 3 | } 4 | .top { 5 | padding: 40px 0; 6 | text-align: center; 7 | background: #fff; 8 | } 9 | .steps { 10 | padding: 50px 50px 0; 11 | border-top: solid 1px #eee; 12 | background: #fff; 13 | } 14 | 15 | .detail { 16 | margin-top: 30px; 17 | padding: 20px 0; 18 | font-size: 28px; 19 | background: #fff; 20 | li { 21 | display: flex; 22 | justify-content: space-between; 23 | padding: 0 30px; 24 | line-height: 70px; 25 | 26 | div:first-child { 27 | color: #666666; 28 | } 29 | 30 | b { 31 | color: #f40; 32 | } 33 | } 34 | } 35 | 36 | .btn { 37 | position: fixed; 38 | bottom: 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/view/list/list-component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import CSSModules from 'react-css-modules' 4 | import Button from 'mo-button' 5 | import ListTabs from 'layout/list-tabs' 6 | import UserCard from 'layout/user-card' 7 | import style from './list.less' 8 | 9 | 10 | @CSSModules(style) 11 | class ListComponent extends Component { 12 | constructor(props) { 13 | super(props) 14 | } 15 | render() { 16 | const { goBack } = this.props 17 | return ( 18 |
19 | 20 | 21 | 22 |
23 | ) 24 | } 25 | } 26 | 27 | ListComponent.propTypes = { 28 | goBack: PropTypes.func 29 | } 30 | 31 | export default ListComponent -------------------------------------------------------------------------------- /src/view/home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import Dialog from 'mo-dialog' 4 | import HomeComponent from './home-component.js' 5 | 6 | const mapDispatchToProps = (dispatch, props) => { 7 | return { 8 | initData() { 9 | console.log('init index') 10 | }, 11 | goListPage() { 12 | props.history.push('/list') 13 | }, 14 | goMiaoPage() { 15 | props.history.push('/miao') 16 | }, 17 | openDialog() { 18 | Dialog.alert(

Hello from the Moon~

) 19 | } 20 | } 21 | } 22 | 23 | const mapStateToProps = (state) => { 24 | return { 25 | ...state.HomeReducer 26 | } 27 | } 28 | 29 | 30 | const Home = connect( 31 | mapStateToProps, 32 | mapDispatchToProps 33 | )(HomeComponent) 34 | 35 | export default Home; -------------------------------------------------------------------------------- /src/module/mo-transtion/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CSSTransition } from 'react-transition-group' 3 | import './transtion.less' 4 | 5 | export const SlideTransition = (props) => ( 6 | 11 | ) 12 | 13 | export const FadeTransition = (props) => ( 14 | 19 | ) 20 | 21 | export const SpreadTransition = (props) => ( 22 | 27 | ) 28 | 29 | export const PopTransition = (props) => ( 30 | 35 | ) 36 | -------------------------------------------------------------------------------- /src/layout/user-card/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import _ut from 'my-util' 3 | import { changeUserInfo, increaseCount } from 'reducers/user-info-reducer' 4 | import Toast from 'mo-toast' 5 | import Component from './component.js' 6 | 7 | const mapDispatchToProps = (dispatch) => { 8 | return { 9 | initData () { 10 | const wid = _ut.fixNum(window.innerWidth) 11 | const hei = _ut.fixNum(window.innerHeight) 12 | 13 | dispatch(changeUserInfo(`${wid} * ${hei}`)) 14 | }, 15 | increaseCount() { 16 | dispatch(increaseCount()) 17 | Toast('+1') 18 | } 19 | } 20 | } 21 | 22 | const mapStateToProps = (state) => { 23 | const { screenSize, countValue, tomorrow } = state.UserReducer 24 | return { 25 | screenSize, countValue, tomorrow 26 | } 27 | } 28 | 29 | 30 | export default connect( 31 | mapStateToProps, 32 | mapDispatchToProps 33 | )(Component) -------------------------------------------------------------------------------- /src/module/mo-button/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import './button.less' 4 | var classNames = require('classnames'); 5 | 6 | const Button = ({children, onClick, type="primary", size, bg, className, style}) => { 7 | if(bg) { 8 | style.background = bg 9 | } 10 | const cln = classNames("tj-btn", { 11 | ["tj-btn__large"]: size === 'large', 12 | ["tj-btn__small"]: size === 'small', 13 | ["tj-btn__primary"]: type === 'primary', 14 | }, className) 15 | return ( 16 | 17 | {children} 18 | 19 | ) 20 | } 21 | 22 | Button.propTypes = { 23 | onClick: PropTypes.func, 24 | type: PropTypes.string, 25 | size: PropTypes.string, 26 | bg: PropTypes.string, 27 | className: PropTypes.bool, 28 | style: PropTypes.object 29 | }; 30 | 31 | 32 | export default Button 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/module/mo-table/table.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | @c1: #fba112; 3 | @c7: #666666; 4 | 5 | .tj-table__content { 6 | display: table; 7 | width: 100%; 8 | line-height: 2.8; 9 | font-size: 26px; 10 | overflow: hidden; 11 | } 12 | 13 | .tj-table__row { 14 | display: table-row; 15 | // justify-content: space-between; 16 | width: 100%; 17 | overflow: hidden; 18 | 19 | &:nth-child(2n+1) { 20 | background: #f6f4f4; 21 | } 22 | } 23 | 24 | th.tj-table__row { 25 | background: #f6f4f4; 26 | } 27 | 28 | .tj-table__col { 29 | // flex-grow: 1; 30 | display: table-cell; 31 | text-align: center; 32 | word-break: break-all; 33 | } 34 | 35 | .tj-table__col--top { 36 | .tj-table__col; 37 | font-weight: normal 38 | } 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/module/co-simple-list/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { TransitionGroup } from 'react-transition-group' 4 | import { SpreadTransition } from '../mo-transtion' 5 | import style from './list.less' 6 | 7 | const Row = ({ value }) => ( 8 |
9 |
{ value[0] }
10 |
{ value[1] }
11 |
12 | ) 13 | 14 | const MyList = ({ data }) => { 15 | const items = data.map(item => ( 16 | 17 | 18 | 19 | )) 20 | return ( 21 |
22 | 23 | {items} 24 | 25 |
26 | ) 27 | } 28 | 29 | MyList.propTypes = { 30 | data: PropTypes.array 31 | } 32 | 33 | export default MyList; -------------------------------------------------------------------------------- /src/module/mo-segmented/segmented.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | .tj-segmented__content { 3 | position: relative; 4 | display: table; 5 | width: 100%; 6 | height: 60px; 7 | line-height: 60px; 8 | font-size: 32px; 9 | text-align: center; 10 | color: #fff; 11 | border: solid 1px #fff; 12 | border-radius: 12px; 13 | overflow: hidden; 14 | } 15 | 16 | .tj-segmented__item { 17 | display: inline-block; 18 | height: inherit; 19 | box-sizing: border-box; 20 | list-style: none; 21 | color: #fff;; 22 | border-right: solid 1px #fff; 23 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 24 | cursor: pointer; 25 | 26 | &:last-child { 27 | border-right: none; 28 | } 29 | } 30 | 31 | .tj-segmented__item--active { 32 | // background: #fff; 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/module/mo-toast/toast.css: -------------------------------------------------------------------------------- 1 | :global .tj-toast { 2 | position: fixed; 3 | left: 50%; 4 | top: 50%; 5 | transform: translateX(-50%) translateY(-100%); 6 | box-sizing: border-box; 7 | padding: 0 30px; 8 | height: 60px; 9 | line-height: 60px; 10 | font-size: 28px; 11 | color: #fff; 12 | text-align: center; 13 | border-radius: 10px; 14 | background: rgba(0, 0, 0, 0.7); 15 | } 16 | :global .tj-toast--hasicon { 17 | padding: 40px 30px; 18 | width: 100px; 19 | height: 100px; 20 | } 21 | :global .tj-toast-img { 22 | display: block; 23 | margin: 0 auto; 24 | width: 25px; 25 | height: 25px; 26 | margin-bottom: 30px; 27 | overflow: hidden; 28 | } 29 | :global .tj-toast-close { 30 | display: inline-block; 31 | box-sizing: border-box; 32 | padding-left: 20px; 33 | margin-left: 20px; 34 | margin-right: -20px; 35 | border-left: solid 1px #fff; 36 | height: 28px; 37 | line-height: 28px; 38 | font-size: 28px; 39 | cursor: pointer; 40 | } 41 | -------------------------------------------------------------------------------- /src/module/mo-button/button.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | .tj-btn { 3 | display: block; 4 | width: 600px; 5 | margin: 20px auto 0; 6 | line-height: 2.5; 7 | font-size: 32px; 8 | text-align: center; 9 | color: #000; 10 | background: #fff; 11 | border-radius: 4px; 12 | box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.12), 0px 2px 2px 0px rgba(0,0,0,0.24); 13 | } 14 | 15 | .tj-btn__large { 16 | width: 100%; 17 | border: none; 18 | border-top: solid 1px #eee; 19 | border-radius: 0; 20 | box-shadow: 0px 0px -2px 0px rgba(0,0,0,0.12), 0px -2px -2px 0px rgba(0,0,0,0.24); 21 | } 22 | 23 | .tj-btn__small { 24 | width: 150px; 25 | margin: 20px auto 0; 26 | line-height: 2.5; 27 | font-size: 24px; 28 | box-shadow: none; 29 | } 30 | 31 | .tj-btn__primary { 32 | color: #fff; 33 | background: #2196f3; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/layout/user-card/user-card.less: -------------------------------------------------------------------------------- 1 | .wrap { 2 | position: relative; 3 | box-sizing: border-box; 4 | padding: 20px 30px 50px; 5 | width: 100%; 6 | background: #fff; 7 | box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.12), 0px 2px 2px 0px rgba(0,0,0,0.24); 8 | } 9 | 10 | .title { 11 | width: 100%; 12 | padding: 9px 0 0; 13 | margin-bottom: 12px; 14 | font-size: 32px; 15 | } 16 | 17 | .tip { 18 | line-height: 2; 19 | font-size: 26px; 20 | color: #212121; 21 | } 22 | 23 | .hit { 24 | display: inline-block; 25 | min-width: 80px; 26 | line-height: 1.2; 27 | text-align: center; 28 | color: #2196f3; 29 | border-bottom: solid 1px #2196f3 30 | } 31 | 32 | .logo { 33 | position: absolute; 34 | display: inline-block; 35 | bottom: 10px; 36 | right: 10px; 37 | width: 180px; 38 | height: 105px; 39 | background: #ffc400; 40 | background: linear-gradient(to bottom right, #ffc400 0%,#ffc400 65%, #ffeb3b 65%, #ffeb3b 100%) 41 | } -------------------------------------------------------------------------------- /src/module/mo-result-card/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Icon from 'mo-icon' 4 | import styles from './resultcard.less' 5 | 6 | var classNames = require('classnames'); 7 | 8 | const ResultCard = ({type, title, desc, className, style}) => { 9 | const isSuccess = type === 'success'; 10 | const stroke = isSuccess ? '#7ed321' : '#ff7748' 11 | const iconType = isSuccess ? 'check-circle' : 'fail-circle' 12 | return ( 13 |
14 | 15 |

{title}

16 |

{desc}

17 |
18 | ) 19 | } 20 | 21 | ResultCard.propTypes = { 22 | type: PropTypes.string, 23 | title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), 24 | desc: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), 25 | className: PropTypes.bool, 26 | style: PropTypes.object 27 | }; 28 | 29 | export default ResultCard 30 | 31 | 32 | -------------------------------------------------------------------------------- /mock/api.js: -------------------------------------------------------------------------------- 1 | // import { SetActiveRecordForAPP, GetActiveCollection } from './config' 2 | 3 | var db = require('./db') 4 | var http = require('http'), 5 | url = require('url'); 6 | 7 | var { list } = db; 8 | 9 | function mytimeout(t) { 10 | return new Promise(resolve =>{ 11 | setTimeout(resolve, t) 12 | }) 13 | } 14 | 15 | // 创建http server,并传入回调函数: 16 | var server = http.createServer(async function (request, response) { 17 | await mytimeout(2000) 18 | 19 | // 回调函数接收request和response对象, 20 | // 获得HTTP请求的method和url: 21 | console.log(request.method + ': ' + request.url); 22 | // 将HTTP响应200写入response, 同时设置Content-Type: text/html: 23 | response.writeHead(200, {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'}); 24 | var pathname = url.parse(request.url).pathname; 25 | response.end(JSON.stringify(list[pathname.slice(1)])); 26 | }); 27 | 28 | // 让服务器监听3003端口: 29 | server.listen(3003); 30 | 31 | console.log('Server is running at http://127.0.0.1:3003/'); -------------------------------------------------------------------------------- /src/module/mo-steps/steps.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import './steps.less' 4 | 5 | var classNames = require('classnames'); 6 | 7 | 8 | class Steps extends Component { 9 | constructor(props) { 10 | super(props); 11 | } 12 | render() { 13 | const { 14 | current, 15 | children, 16 | className, 17 | style, 18 | } = this.props; 19 | const len = children.length; 20 | 21 | return ( 22 |
23 | { 24 | React.Children.map(children, (child, index)=> { 25 | return React.cloneElement(child, { 26 | status: current >= index ? 'success' : 'wait', 27 | lineStyle: {display: len-1 === index ? 'none' : 'auto'} 28 | }) 29 | }) 30 | } 31 |
32 | ) 33 | } 34 | } 35 | 36 | 37 | Steps.propTypes = { 38 | current: PropTypes.number, 39 | className: PropTypes.bool, 40 | style: PropTypes.object 41 | }; 42 | 43 | export default Steps 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/module/mo-toast/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Toast from './Toast'; 5 | 6 | export default function Message(props = {}, option) { 7 | const div = document.createElement('div') 8 | document.body.appendChild(div) 9 | 10 | if (typeof props === 'string') { 11 | props = { 12 | content: props 13 | }; 14 | } 15 | if (option) { 16 | props = {...props, ...option} 17 | } 18 | const component = React.createElement(Toast, Object.assign({}, props, { 19 | onClose: () => { 20 | ReactDOM.unmountComponentAtNode(div) 21 | document.body.removeChild(div) 22 | 23 | if (props.onClose instanceof Function) { 24 | props.onClose(); 25 | } 26 | } 27 | })); 28 | 29 | ReactDOM.render(component, div); 30 | } 31 | 32 | /* eslint-disable */ 33 | ['text', 'success', 'warning', 'info', 'error', 'loading'].forEach(type => { 34 | Message[type] = (options = {}) => { 35 | return Message(options, type); 36 | }; 37 | }); 38 | /* eslint-enable */ 39 | -------------------------------------------------------------------------------- /src/module/co-simple-list/mo-toast/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Toast from './Toast'; 5 | 6 | export default function Message(props = {}, option) { 7 | const div = document.createElement('div'); 8 | 9 | document.body.appendChild(div); 10 | 11 | if (typeof props === 'string') { 12 | props = { 13 | content: props 14 | }; 15 | } 16 | if (option) { 17 | props = {...props, ...option} 18 | } 19 | const component = React.createElement(Toast, Object.assign(props, { 20 | willUnmount: () => { 21 | ReactDOM.unmountComponentAtNode(div); 22 | document.body.removeChild(div); 23 | if (props.onClose instanceof Function) { 24 | props.onClose(); 25 | } 26 | } 27 | })); 28 | 29 | ReactDOM.render(component, div); 30 | } 31 | 32 | /* eslint-disable */ 33 | ['text', 'success', 'warning', 'info', 'error', 'loading'].forEach(type => { 34 | Message[type] = (options = {}) => { 35 | return Message(options, type); 36 | }; 37 | }); 38 | /* eslint-enable */ 39 | -------------------------------------------------------------------------------- /src/styles/reset.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | html, 3 | body, 4 | h1, 5 | h2, 6 | h3, 7 | h4, 8 | h5, 9 | h6, 10 | p, 11 | blockquote, 12 | ul, 13 | ol, 14 | li, 15 | dl, 16 | dt, 17 | dd, 18 | form, 19 | fieldset, 20 | legend, 21 | input, 22 | textarea, 23 | button, 24 | th, 25 | td { 26 | margin: 0; 27 | padding: 0; 28 | border: 0; 29 | vertical-align: baseline; 30 | } 31 | html { 32 | line-height: 1; 33 | } 34 | table { 35 | border-collapse: collapse; 36 | border-spacing: 0; 37 | } 38 | ul, 39 | ol { 40 | list-style: none; 41 | } 42 | img { 43 | border: 0; 44 | } 45 | b, 46 | strong { 47 | font-weight: 700; 48 | } 49 | article, 50 | aside, 51 | details, 52 | figcaption, 53 | figure, 54 | footer, 55 | header, 56 | hgroup, 57 | main, 58 | nav, 59 | section, 60 | summary { 61 | display: block; 62 | } 63 | audio, 64 | canvas, 65 | progress, 66 | video { 67 | display: inline-block; 68 | vertical-align: baseline; 69 | } 70 | 71 | body{ 72 | line-height: 1.5; 73 | font-family: Helvetica, Tahoma, Arial, "Microsoft YaHei", "微软雅黑", Heiti, "黑体", SimSun, "宋体", sans-serif; 74 | background: #eee; 75 | } 76 | } -------------------------------------------------------------------------------- /src/module/co-simple-list/mo-toast/toast.less: -------------------------------------------------------------------------------- 1 | :global { 2 | .tj-msg { 3 | position: fixed; 4 | left: 50%; 5 | top: 50%; 6 | transform: translateX(-50%) translateY(-50%); 7 | box-sizing: border-box; 8 | padding: 0 30px; 9 | height: 60px; 10 | line-height: 60px; 11 | font-size: 28px;; 12 | color: #fff; 13 | text-align: center; 14 | border-radius: 10px; 15 | background: rgba(0, 0, 0, .7); 16 | } 17 | 18 | .tj-msg--hasicon { 19 | padding: 40px 30px; 20 | width: 100px; 21 | height: 100px; 22 | } 23 | 24 | .tj-msg-img { 25 | display: block; 26 | margin: 0 auto; 27 | width: 25px; 28 | height: 25px; 29 | margin-bottom: 30px; 30 | overflow: hidden; 31 | } 32 | 33 | .tj-msg-close { 34 | display: inline-block; 35 | box-sizing: border-box; 36 | padding-left: 20px; 37 | margin-left: 20px; 38 | margin-right: -20px; 39 | border-left: solid 1px #fff; 40 | height: 28px; 41 | line-height: 28px; 42 | font-size: 28px; 43 | cursor: pointer 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/module/mo-icon/icon.less: -------------------------------------------------------------------------------- 1 | 2 | :global{ 3 | .tj-icon { 4 | display: inline-block; 5 | width: 46px; 6 | height: 46px; 7 | box-sizing: border-box; 8 | padding: 2px; 9 | stroke: #f3f3f3; 10 | } 11 | 12 | .tj-icon_large { 13 | width: 130px; 14 | height: 130px; 15 | } 16 | 17 | .tj-icon_medium { 18 | width: 80px; 19 | height: 80px; 20 | stroke-dasharray: 0 1000px; 21 | } 22 | 23 | .tj-icon_ani { 24 | stroke-width: 50px; 25 | fill-opacity: 0; 26 | stroke-dashoffset: 7% 7%; 27 | stroke-dasharray: 0 35%; 28 | :local { 29 | animation: tj-icon__ani_fillIn 1s 1 linear; 30 | animation-fill-mode: forwards; 31 | } 32 | } 33 | } 34 | 35 | @keyframes tj-icon__ani_fillIn { 36 | 0%{ 37 | fill-opacity: 0; 38 | stroke-dashoffset: 7%, 7%; 39 | } 40 | 70%{ 41 | fill-opacity: 0; 42 | stroke-dashoffset: 100%, 7%; 43 | } 44 | 100% { 45 | fill-opacity: 1; 46 | stroke-dasharray: 100%, 0; 47 | } 48 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 tumars 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/view/home/home-component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import CSSModules from 'react-css-modules' 4 | import Button from 'mo-button' 5 | import UserCard from 'layout/user-card' 6 | import style from './home.less' 7 | 8 | @CSSModules(style, {handleNotFoundStyleName: 'ignore'}) 9 | class HomeComponent extends Component { 10 | constructor(props) { 11 | super(props) 12 | } 13 | componentDidMount() { 14 | this.props.initData() 15 | } 16 | render() { 17 | const { 18 | openDialog, 19 | goListPage, 20 | goMiaoPage, 21 | countValue 22 | } = this.props 23 | 24 | return ( 25 |
26 | 27 |

你好,我是个简单的示例 domo

28 |

{countValue}

29 | 30 | 31 | 32 |
33 | ) 34 | } 35 | } 36 | 37 | HomeComponent.propTypes = { 38 | initData: PropTypes.func, 39 | goListPage: PropTypes.func 40 | } 41 | 42 | export default HomeComponent -------------------------------------------------------------------------------- /src/module/mo-toast/toast.less: -------------------------------------------------------------------------------- 1 | :global { 2 | .tj-toast { 3 | position: fixed; 4 | left: 50%; 5 | top: 50%; 6 | transform: translateX(-50%) translateY(-100%); 7 | box-sizing: border-box; 8 | padding: 0 30px; 9 | height: 60px; 10 | line-height: 60px; 11 | font-size: 28px;; 12 | color: #fff; 13 | text-align: center; 14 | border-radius: 10px; 15 | background: rgba(0, 0, 0, .7); 16 | } 17 | 18 | .tj-toast--hasicon { 19 | padding: 40px 30px; 20 | width: 100px; 21 | height: 100px; 22 | } 23 | 24 | .tj-toast-img { 25 | display: block; 26 | margin: 0 auto; 27 | width: 25px; 28 | height: 25px; 29 | margin-bottom: 30px; 30 | overflow: hidden; 31 | } 32 | 33 | .tj-toast-close { 34 | display: inline-block; 35 | box-sizing: border-box; 36 | padding-left: 20px; 37 | margin-left: 20px; 38 | margin-right: -20px; 39 | border-left: solid 1px #fff; 40 | height: 28px; 41 | line-height: 28px; 42 | font-size: 28px; 43 | cursor: pointer 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/layout/user-card/component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import CSSModules from 'react-css-modules' 4 | import TimeCount from 'mo-time-count' 5 | import style from './user-card.less' 6 | 7 | @CSSModules(style) 8 | class UserCard extends Component { 9 | constructor(props) { 10 | super(props) 11 | this.state = { 12 | timeCount: 0 13 | } 14 | } 15 | componentDidMount() { 16 | this.props.initData() 17 | } 18 | render() { 19 | const { 20 | screenSize, 21 | countValue, 22 | tomorrow, 23 | increaseCount 24 | } = this.props 25 | 26 | return ( 27 |
28 |
用户信息
29 |

你的浏览器可视面积为:{screenSize}

30 |

你无聊的点击了:+{countValue}

31 |

距离明天还有:

32 |
33 |
34 | ) 35 | } 36 | } 37 | 38 | UserCard.propTypes = { 39 | countValue: PropTypes.number, 40 | screenSize: PropTypes.string, 41 | tomorrow: PropTypes.number, 42 | initData: PropTypes.func, 43 | increaseCount: PropTypes.func 44 | } 45 | 46 | export default UserCard -------------------------------------------------------------------------------- /src/module/mo-icon/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import SvgConfig from './svg-config' 4 | import './icon.less' 5 | var classNames = require('classnames'); 6 | 7 | 8 | const Icon = ({ 9 | type, 10 | size, 11 | color='none', 12 | stroke='none', 13 | style={}, 14 | className, 15 | ani=false 16 | }) => { 17 | const cln = classNames('tj-icon', { 18 | ['tj-icon_large']: size === 'large', 19 | ['tj-icon_medium']: size === 'medium', 20 | ['tj-icon_small']: size === 'small', 21 | }, className) 22 | style.fill = color; 23 | style.stroke = stroke; 24 | 25 | return ( 26 | 31 | 35 | 36 | ) 37 | } 38 | 39 | 40 | Icon.propTypes = { 41 | type: PropTypes.string, 42 | size: PropTypes.string, 43 | color: PropTypes.string, 44 | stroke: PropTypes.string, 45 | className: PropTypes.string, 46 | ani: PropTypes.bool, 47 | }; 48 | 49 | export default Icon; -------------------------------------------------------------------------------- /src/module/mo-carousel/carousel.css: -------------------------------------------------------------------------------- 1 | :global .tj-carousel__wrap { 2 | position: relative; 3 | width: 100%; 4 | } 5 | :global .tj-carousel__slide-list { 6 | position: relative; 7 | display: flex; 8 | flex-wrap: nowrap; 9 | touch-action: pan-y pan-x; 10 | will-change: transfrom; 11 | transition-property: transform; 12 | box-sizing: content-box; 13 | } 14 | :global .tj-carousel__slide { 15 | display: inline-block; 16 | flex: 0 1 auto; 17 | position: relative; 18 | } 19 | :global .tj-carousel__pagination-wrap { 20 | position: relative; 21 | overflow: hidden; 22 | } 23 | :global .tj-carousel__pagination-content { 24 | position: absolute; 25 | bottom: 10px; 26 | width: 100%; 27 | height: 25px; 28 | box-sizing: border-box; 29 | padding: 0; 30 | line-height: 1; 31 | text-align: center; 32 | overflow: hidden; 33 | } 34 | :global .tj-carousel__bullet { 35 | display: inline-block; 36 | width: 20px; 37 | height: 20px; 38 | margin: 0 10px; 39 | padding: 0; 40 | background: #d4a143; 41 | border-radius: 50%; 42 | } 43 | :global .tj-carousel__bullet_active { 44 | display: inline-block; 45 | width: 20px; 46 | height: 20px; 47 | margin: 0 10px; 48 | padding: 0; 49 | background: #d4a143; 50 | border-radius: 50%; 51 | background: #a32229; 52 | } 53 | -------------------------------------------------------------------------------- /src/layout/list-tabs/list-tabs.less: -------------------------------------------------------------------------------- 1 | .wrap { 2 | position: relative; 3 | box-sizing: border-box; 4 | padding: 20px; 5 | width: 100%; 6 | background: #fff; 7 | box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.12), 0px 2px 2px 0px rgba(0,0,0,0.24); 8 | } 9 | 10 | .book-tab { 11 | text-align: center; 12 | 13 | h2 { 14 | margin-top: 30px; 15 | font-size: 48px; 16 | font-weight: normal; 17 | } 18 | 19 | h3 { 20 | box-sizing: border-box; 21 | margin: 50px 0 20px; 22 | padding: 30px 0 0 5%; 23 | font-size: 30px; 24 | font-weight: normal; 25 | text-align: left; 26 | border-top: dashed 1px #0e90d2; 27 | 28 | b { 29 | display: inline-block; 30 | box-sizing: border-box; 31 | margin: 0 10px; 32 | padding: 5px 10px; 33 | line-height: 1; 34 | font-size: .95em; 35 | color: #0e90d2; 36 | } 37 | } 38 | 39 | .segmented { 40 | width: 90%; 41 | margin: 0 auto; 42 | } 43 | } 44 | 45 | .button { 46 | width: 300px; 47 | margin: 40px auto; 48 | line-height: 2; 49 | color: #fff; 50 | text-align: center; 51 | border-radius: 10px; 52 | background: #0e90d2; 53 | } 54 | 55 | .panel { 56 | position: relative; 57 | } 58 | -------------------------------------------------------------------------------- /src/module/mo-carousel/carousel.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | .tj-carousel__wrap { 3 | position: relative; 4 | width: 100%; 5 | } 6 | .tj-carousel__slide-list { 7 | position: relative; 8 | display: flex; 9 | flex-wrap: nowrap; 10 | touch-action: pan-y pan-x; 11 | will-change: transfrom; 12 | transition-property: transform; 13 | box-sizing: content-box; 14 | } 15 | 16 | .tj-carousel__slide { 17 | display: inline-block; 18 | flex: 0 1 auto; 19 | position: relative; 20 | } 21 | 22 | .tj-carousel__pagination-wrap { 23 | position: relative; 24 | overflow: hidden; 25 | } 26 | .tj-carousel__pagination-content { 27 | position: absolute; 28 | bottom: 10px; 29 | width: 100%; 30 | height: 25px; 31 | box-sizing: border-box; 32 | padding: 0; 33 | line-height: 1; 34 | text-align: center; 35 | overflow: hidden; 36 | // pointer-events: none 37 | } 38 | 39 | .tj-carousel__bullet { 40 | display: inline-block; 41 | width: 20px; 42 | height: 20px; 43 | margin: 0 10px; 44 | padding: 0; 45 | background: #d4a143; 46 | border-radius: 50%; 47 | } 48 | 49 | .tj-carousel__bullet_active { 50 | .tj-carousel__bullet; 51 | background: #a32229; 52 | } 53 | } -------------------------------------------------------------------------------- /src/reducers/user-info-reducer.js: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------*/ 2 | /*User Reducer*/ 3 | /*-----------------------------------------------------------------*/ 4 | const getTomorrow = () => { 5 | const now = new Date(), 6 | date = now.getDate() + 1; 7 | 8 | return Date.parse(new Date(now.getFullYear(), now.getMonth(), date)) 9 | } 10 | 11 | 12 | const initUserInfo = { 13 | screenSize: null, 14 | countValue:0, 15 | tomorrow: getTomorrow() 16 | } 17 | 18 | const UserReducer = (state = initUserInfo, action) => { 19 | switch (action.type) { 20 | case 'USER_INFO': 21 | return Object.assign({}, state, {screenSize: action.info}) 22 | case 'USER_INCREMENT': 23 | return Object.assign({}, state, {countValue: state.countValue + action.num}) 24 | case 'USER_CLEAR_COUNT': 25 | return Object.assign({}, state, {countValue: 0}) 26 | default: 27 | return state 28 | } 29 | } 30 | 31 | 32 | /*-----------------------------------------------------------------*/ 33 | /*User Action*/ 34 | /*-----------------------------------------------------------------*/ 35 | const changeUserInfo = (info) => ({type: 'USER_INFO', info}) 36 | const increaseCount = (num=1) => ({type: 'USER_INCREMENT', num}) 37 | const clearCount = () => ({type: 'USER_CLEAR_COUNT'}) 38 | 39 | 40 | export { 41 | UserReducer, 42 | changeUserInfo, 43 | increaseCount , 44 | clearCount 45 | } -------------------------------------------------------------------------------- /src/module/mo-steps/step.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import Icon from 'mo-icon' 4 | import './steps.less' 5 | 6 | var classNames = require('classnames'); 7 | 8 | class Step extends Component { 9 | static defaultProps = { 10 | status: 'wait' 11 | }; 12 | constructor(props) { 13 | super(props); 14 | } 15 | render() { 16 | const { 17 | title, 18 | description, 19 | status, 20 | style, 21 | lineStyle 22 | } = this.props; 23 | const statusClass = `is-${status}`; 24 | 25 | return ( 26 |
27 |
28 |
29 | 30 |
31 |
32 | { 33 | status === 'success' 34 | ? 35 | : 36 | } 37 |
38 |
39 |
40 |
{title}
41 |
{description}
42 |
43 |
44 | ) 45 | 46 | 47 | } 48 | } 49 | 50 | 51 | Step.propTypes = { 52 | title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), 53 | description: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), 54 | status: PropTypes.string, 55 | style: PropTypes.object, 56 | lineStyle: PropTypes.object, 57 | }; 58 | 59 | export default Step 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/view/miao/miao-component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import CSSModules from 'react-css-modules' 4 | import ResultCard from 'mo-result-card' 5 | import Steps from 'mo-steps' 6 | import Button from 'mo-button' 7 | import style from './miao.less' 8 | 9 | 10 | @CSSModules(style, {handleNotFoundStyleName: 'ignore'}) 11 | class MiaoComponent extends Component { 12 | constructor(props) { 13 | super(props) 14 | } 15 | render() { 16 | return ( 17 |
18 | 24 | 25 | 26 | 27 | 28 | 29 |
    30 |
  • 31 |
    32 |
    湿粮干粮猫罐头
    33 |
  • 34 |
  • 35 |
    玩具
    36 |
    瓦楞纸激光枪逗猫棒
    37 |
  • 38 |
  • 39 |
    厕所
    40 |
    1 2 厕每天铲
    41 |
  • 42 |
  • 43 |
    猫窝
    44 |
    沙发跟床都是窝
    45 |
  • 46 |
47 | 48 |
49 | ) 50 | } 51 | } 52 | 53 | MiaoComponent.propTypes = { 54 | goBack: PropTypes.func 55 | } 56 | 57 | export default MiaoComponent 58 | -------------------------------------------------------------------------------- /src/layout/list-tabs/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React from 'react' 3 | import { connect } from 'react-redux' 4 | import { createSelector } from 'reselect' 5 | import _ut from 'my-util' 6 | import { fetchAddList, clearList } from 'reducers/data-list-reducer' 7 | import ListComponent from './component.js' 8 | import Dialog from 'mo-dialog' 9 | 10 | 11 | const ErrorTip = () => 网络出错, 请执行 yarn mock 启动接口 12 | const handleFetchError = (fetchPromise) => { 13 | Promise.resolve(fetchPromise) 14 | .then(res=> res) 15 | .catch(e=>{ 16 | Dialog.alert() 17 | }) 18 | } 19 | 20 | 21 | const mapDispatchToProps = (dispatch) => { 22 | const getMovie = () => { 23 | handleFetchError(dispatch(fetchAddList())) 24 | } 25 | return { 26 | getMovie, 27 | initData() { 28 | dispatch(clearList()) 29 | getMovie() 30 | }, 31 | tabChange(index) { 32 | console.log(index) 33 | } 34 | } 35 | } 36 | 37 | 38 | 39 | /* 使用 formatList 将列表转换成其他格式,并使用 reselect 避免重复计算与渲染 */ 40 | const formatList = (data)=> { 41 | if(!data) {return null} 42 | const { list } = data 43 | if(!Array.isArray(list)){return data} 44 | 45 | const newList = list.map((item, i)=> 46 | ([i + 1, item.name]) 47 | ) 48 | 49 | return Object.assign({}, data, {list: newList}) 50 | } 51 | 52 | const selectorMovieList = createSelector( 53 | state => state.DataListReducer, 54 | formatList 55 | ) 56 | 57 | const mapStateToProps = (state) => { 58 | return { 59 | movieListInfo : selectorMovieList(state) 60 | } 61 | } 62 | 63 | export default connect( 64 | mapStateToProps, 65 | mapDispatchToProps 66 | )(ListComponent) -------------------------------------------------------------------------------- /src/module/mo-dialog/modal.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | .tj-dialog__dyy { 3 | position: fixed; 4 | z-index: 10; 5 | top: 0; 6 | left: 0; 7 | box-sizing: border-box; 8 | width: 100%; 9 | height: 100%; 10 | background: rgba(0, 0, 0, 0.4); 11 | -webkit-tap-highlight-color: transparent; 12 | } 13 | 14 | .tj-dialog__content { 15 | z-index: 20; 16 | position: fixed; 17 | top: 100px; 18 | left: 50%; 19 | right: 0; 20 | width: 700px; 21 | max-height: 800px; 22 | margin-left: -350px; 23 | box-sizing: border-box; 24 | color: #000; 25 | font-size: 36px; 26 | line-height: 1.5; 27 | text-align: center; 28 | box-shadow: rgba(0, 0, 0, 0.25) 0px 28px 90px, rgba(0, 0, 0, 0.22) 0px 20px 36px; 29 | border-radius: 4px; 30 | background: #fff; 31 | overflow-x: hidden; 32 | overflow-y: scroll; 33 | -webkit-overflow-scrolling: touch; 34 | &::-webkit-scrollbar {display:none} 35 | } 36 | 37 | .tj-dialog__close { 38 | position: absolute; 39 | z-index: 30; 40 | right: 0; 41 | top: 0; 42 | width: 0.88*75px; 43 | height: 0.88*75px; 44 | line-height: 1.19*75px; 45 | font-size: 0.5*75px; 46 | font-weight: bold; 47 | text-align: center; 48 | -webkit-tap-highlight-color: transparent; 49 | 50 | svg { 51 | width: 0.63*75px; 52 | height: 0.63*75px; 53 | fill: #999; 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/module/mo-carousel/method.js: -------------------------------------------------------------------------------- 1 | function GetSlideAngle(dx,dy) { 2 | return Math.atan2(dy,dx) * 180 / Math.PI; 3 | } 4 | 5 | // 根据起点和终点返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑动 6 | function GetSlideDirection(startX,startY, endX, endY) { 7 | var dy = startY - endY; 8 | var dx = endX - startX; 9 | var result = 0; // 没滑动 10 | 11 | //如果滑动距离太短 12 | if (Math.abs(dx) < 2 && Math.abs(dy) < 2) { 13 | return result; 14 | } 15 | var angle = GetSlideAngle(dx, dy); 16 | if (angle >= -45 && angle < 45) { 17 | result = 1;// 向右 18 | }else if (angle >= 45 && angle < 135) { 19 | result = 0; // 向上 20 | }else if (angle >= -135 && angle < -45) { 21 | result = 0; // 向下 22 | }else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) { 23 | result = -1; // 向左 24 | } 25 | return result; 26 | } 27 | 28 | 29 | 30 | // 设置 css3 属性 31 | function setCss3Style(el, prop, val) { 32 | var vendors = [ 33 | '-webkit-', 34 | '-o-', 35 | '-moz-', 36 | '-ms-', 37 | '' 38 | ]; 39 | function toCamelCase(str) { 40 | return str.toLowerCase().replace(/(\/-[a-z])/g, function($1) { 41 | return $1.toUpperCase().replace('-', ''); 42 | }); 43 | } 44 | 45 | function setCss3Style(el, prop, val) { 46 | vendors.forEach(function(vendor) { 47 | var property = toCamelCase(vendor + prop); 48 | 49 | if(property in el.style) { 50 | el.style[property] = val; 51 | } 52 | }); 53 | } 54 | 55 | return setCss3Style(el, prop, val) 56 | } 57 | 58 | export { 59 | GetSlideDirection, 60 | setCss3Style 61 | } -------------------------------------------------------------------------------- /src/module/mo-infinite-scroll/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | var classNames = require('classnames') 3 | 4 | class InfiniteScroll extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.state = { 8 | isLoading: false 9 | } 10 | } 11 | componentDidMount() { 12 | const { onLoadMore } = this.props; 13 | const handleLoadError = () =>{ this.setState({isLoading: false}) } 14 | const handleLoadStart = () => { this.setState({isLoading: true}) } 15 | const handleLoadFinish = () => { this.setState({isLoading: false})} 16 | 17 | this.myscroll.addEventListener("scroll", () => { 18 | const { scrollTop , clientHeight, scrollHeight } = this.myscroll; 19 | const isCatchBottom = scrollTop + clientHeight >= scrollHeight - 10; 20 | if (isCatchBottom && !this.state.isLoading) { 21 | handleLoadStart(); 22 | Promise.resolve(onLoadMore()) 23 | .catch(handleLoadError) 24 | .finally(handleLoadFinish); 25 | } 26 | }); 27 | } 28 | render() { 29 | const { isLoading } = this.state; 30 | const { height = '420px', loader, className, style } = this.props; 31 | 32 | return ( 33 |
    this.myscroll = n} 35 | className={classNames(className)} 36 | style={{ height, overflow: "auto", ...style }} 37 | > 38 | {this.props.children} 39 | {loader || ( isLoading &&

    loading...

    )} 40 |
41 | ) 42 | } 43 | } 44 | 45 | 46 | export default InfiniteScroll -------------------------------------------------------------------------------- /src/module/mo-toast/assets/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/module/mo-segmented/tabs-modules.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import './tabs.less'; 4 | 5 | var classNames = require('classnames'); 6 | 7 | // 标签页内容 8 | const Panel = ({children, isActive }) => ( 9 |
15 | {children} 16 |
17 | ) 18 | 19 | Panel.propTypes = { 20 | key: PropTypes.oneOfType([ 21 | PropTypes.number, 22 | PropTypes.string 23 | ]), 24 | isActive: PropTypes.bool 25 | } 26 | 27 | // 标签 28 | const Nav = ({titles, activeIndex, onChange}) => { 29 | const len = titles.length 30 | return ( 31 |
32 | { 33 | titles.map((title, i) => 34 |
44 | { title } 45 |
46 | ) 47 | } 48 |
49 |
50 | ) 51 | } 52 | 53 | Nav.propTypes = { 54 | titles: PropTypes.array, 55 | activeIndex: PropTypes.number, 56 | onChange: PropTypes.func, 57 | } 58 | 59 | export { 60 | Panel, 61 | Nav 62 | } -------------------------------------------------------------------------------- /src/module/mo-table/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import './table.less'; 4 | 5 | 6 | const TjTable = ({columns, dataSource, cLassName}) => ( 7 |
8 | 9 | 10 | 11 | { 12 | columns.map(v=> 13 | 14 | ) 15 | } 16 | 17 | { 18 | dataSource.map(rowItem=> 19 | 20 | { 21 | columns.map(colItem=> 22 | 23 | ) 24 | } 25 | 26 | ) 27 | } 28 | 29 |
{v.title}
{rowItem[colItem.dataIndex]}
30 |
31 | ) 32 | 33 | 34 | TjTable.propTypes = { 35 | dataSource : PropTypes.arrayOf(PropTypes.object), 36 | columns : PropTypes.arrayOf(PropTypes.object), 37 | cLassName: PropTypes.string 38 | } 39 | 40 | TjTable.defaultProps = { 41 | dataSource: [ 42 | {key: 1, n1: '某某某', n2: '某某某', n3: '某某某'}, 43 | {key: 2, n1: '某某某', n2: '某某某', n3: '某某某'}, 44 | {key: 3, n1: '某某某', n2: '某某某', n3: '某某某'}, 45 | {key: 4, n1: '某某某', n2: '某某某', n3: '某某某'}, 46 | ], 47 | columns:[ 48 | {title:'属性1', dataIndex:'n1'}, 49 | {title:'属性2', dataIndex:'n2'}, 50 | {title:'属性3', dataIndex:'n3'}, 51 | ] 52 | } 53 | 54 | export default TjTable -------------------------------------------------------------------------------- /src/module/mo-toast/assets/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/module/mo-toast/assets/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/module/mo-steps/steps.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | .tj-step { 3 | position: relative; 4 | height: 150px; 5 | line-height: 1.5; 6 | vertical-align: top; 7 | } 8 | .tj-step__head { 9 | display: inline-block; 10 | width: 50px; 11 | height: 50px; 12 | border-radius: 50%; 13 | color: #fff; 14 | vertical-align: top; 15 | } 16 | .tj-step__line { 17 | position: absolute; 18 | top:30px; 19 | bottom: 0; 20 | left: 23px; 21 | width: 1px; 22 | height: 133px; 23 | box-sizing: border-box; 24 | border-color: inherit; 25 | background-color: #ccc; 26 | } 27 | .is-success .tj-step__line{ 28 | top:46px; 29 | } 30 | .tj-step__line-inner { 31 | display: block; 32 | border-width: 1px; 33 | border-style: solid; 34 | border-color: inherit; 35 | transition: all 150ms; 36 | box-sizing: border-box; 37 | width: 0; 38 | height: 0; 39 | } 40 | .is-success .tj-step__line-inner { 41 | border-left: solid 1px #7ED321; 42 | height: 100%; 43 | } 44 | .tj-step__icon-none { 45 | position: relative; 46 | left: 14px; 47 | display: inline-block; 48 | width: 16px; 49 | height: 16px; 50 | border: solid 1px #ccc; 51 | border-radius: 50%; 52 | background: #fff; 53 | } 54 | 55 | .tj-step__main { 56 | display: inline-block; 57 | padding-left: 20px; 58 | padding-right: 10px; 59 | white-space: normal; 60 | text-align: left; 61 | } 62 | 63 | .tj-step__title { 64 | font-size: 34px; 65 | color: #000; 66 | } 67 | 68 | .tj-step__description { 69 | font-size: 28px; 70 | color: #666666; 71 | } 72 | } -------------------------------------------------------------------------------- /src/module/mo-tabs/tabs-modules.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import './tabs.less'; 4 | 5 | var classNames = require('classnames'); 6 | 7 | // 标签页内容 8 | const Panel = ({children, isActive, className, style }) => ( 9 |
17 | {children} 18 |
19 | ) 20 | 21 | Panel.propTypes = { 22 | key: PropTypes.oneOfType([ 23 | PropTypes.number, 24 | PropTypes.string 25 | ]), 26 | isActive: PropTypes.bool 27 | } 28 | 29 | // 标签 30 | const Nav = ({titles, activeIndex, onChange}) => { 31 | const len = titles.length 32 | return ( 33 |
34 | { 35 | titles.map((title, i) => 36 |
46 | { title } 47 |
48 | ) 49 | } 50 |
51 |
52 | ) 53 | } 54 | 55 | Nav.propTypes = { 56 | titles: PropTypes.array, 57 | activeIndex: PropTypes.number, 58 | onChange: PropTypes.func, 59 | } 60 | 61 | export { 62 | Panel, 63 | Nav 64 | } -------------------------------------------------------------------------------- /src/module/co-simple-list/mo-toast/assets/success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router' 3 | import { HashRouter, Switch } from 'react-router-dom' 4 | import { Provider } from 'react-redux' 5 | import { TransitionGroup } from 'react-transition-group' 6 | import { SlideTransition } from 'mo-transtion' 7 | import store from '../store' 8 | 9 | /*引入页面组件*/ 10 | import Home from '../view/home' 11 | import List from '../view/list' 12 | import Miao from '../view/miao' 13 | 14 | /*引入路由切换样式*/ 15 | import style from './router.less' 16 | 17 | /*引入全局样式*/ 18 | import '../styles/reset.less' 19 | 20 | /* 做 vw vh 的降级处理 */ 21 | // require('viewport-units-buggyfill').init(); 22 | 23 | 24 | /*定义路由配置数组*/ 25 | const routes_config = [ 26 | { 27 | path: '/', 28 | component: Home, 29 | isExact: true 30 | }, { 31 | path: '/list', 32 | component: List 33 | }, { 34 | path: '/miao', 35 | component: Miao 36 | } 37 | ] 38 | 39 | 40 | const App = () => ( 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | { 49 | routes_config.map(config=> 50 | 51 | ) 52 | } 53 | 54 |
55 |
56 |
57 | }/> 58 |
59 |
60 | ) 61 | 62 | 63 | /*如果不需要页面过渡效果,参考下面更简单易懂的写法*/ 64 | /*const App = () => ( 65 | 66 | 67 | // import { Switch } from 'react-router' 68 | 69 | 70 | 71 | 72 | 73 | ) */ 74 | 75 | 76 | export default App -------------------------------------------------------------------------------- /src/module/mo-tabs/tabs.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | @c1: #fba112; 3 | @c7: #000; 4 | 5 | .tj-tabs__content { 6 | position: relative; 7 | width: 100%; 8 | overflow: hidden; 9 | } 10 | 11 | .tj-tabs__nav-wrap { 12 | display: block; 13 | position: relative; 14 | box-sizing: border-box; 15 | padding-left: 0; 16 | margin: 0; 17 | list-style: none; 18 | transition: transform .5s cubic-bezier(.645,.045,.355,1); 19 | overflow: hidden; 20 | } 21 | 22 | .tj-tabs__nav { 23 | display: inline-block; 24 | height: 60px; 25 | box-sizing: border-box; 26 | line-height: 60px; 27 | list-style: none; 28 | font-size: 32px; 29 | color: @c7; 30 | text-align: center; 31 | position: relative; 32 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 33 | cursor: pointer; 34 | } 35 | 36 | .tj-tabs__nav--active { 37 | color: @c1 38 | } 39 | 40 | .tj-tabs__activebar { 41 | position: absolute; 42 | bottom: 0; 43 | left: 0; 44 | height: 4px; 45 | background-color: @c1; 46 | z-index: 1; 47 | will-change: margin-left; 48 | transition: margin-left .2s cubic-bezier(.645,.045,.355,1); 49 | list-style: none; 50 | } 51 | 52 | .tj-tabs__pane-wrap { 53 | display: flex; 54 | flex-direction: row; 55 | flex-wrap: nowrap; 56 | width: 100%; 57 | margin-top: 30px; 58 | will-change: transform; 59 | } 60 | .tj-tabs__pane-wrap--ani { 61 | transition: transform .2s cubic-bezier(.645,.045,.355,1); 62 | } 63 | 64 | 65 | .tj-tabs__pane { 66 | width: 100%; 67 | flex-shrink: 0; 68 | transition: opacity .45s; 69 | opacity: 1; 70 | } 71 | 72 | .tj-tabs__pane--inactive { 73 | // opacity: 0; 74 | // height: 0; 75 | // padding: 0; 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/layout/list-tabs/component.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import CSSModules from 'react-css-modules' 4 | import Tabs from 'mo-tabs' 5 | import SimpleList from 'co-simple-list' 6 | import InfiniteScroll from 'mo-infinite-scroll' 7 | import ListLoading from 'mo-list-loading' 8 | import Spin from 'mo-spin' 9 | import Segmented from 'mo-segmented' 10 | import style from './list-tabs.less' 11 | 12 | const Loader = () => ( 13 |
14 | 加载中 15 |
16 | ) 17 | 18 | @CSSModules(style) 19 | class ListTabs extends Component { 20 | constructor(props) { 21 | super(props) 22 | } 23 | 24 | componentDidMount() { 25 | this.props.initData() 26 | } 27 | 28 | render() { 29 | const { 30 | movieListInfo:{list}, 31 | tabChange, getMovie 32 | } = this.props 33 | 34 | return ( 35 |
36 | tabChange(next)} 39 | > 40 | 41 | } 46 | > 47 | { 48 | Array.isArray(list) && list.length > 0 49 | ? 50 | : 51 | } 52 | 53 | 54 | 55 | 56 |

并没有图书列表

57 |

来看看无处安放的segmented组件:

58 | 64 |
65 |
66 |
67 | ) 68 | } 69 | } 70 | 71 | ListTabs.propTypes = { 72 | movieListInfo: PropTypes.object, 73 | getMovie: PropTypes.func, 74 | } 75 | 76 | export default ListTabs -------------------------------------------------------------------------------- /src/module/mo-carousel/pagination.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | const PaginationDecorator = (Carousel) =>( 4 | class Pagination extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.state = { 8 | activeIndex: this.props.activeIndex || 0 9 | } 10 | } 11 | 12 | handleTabClick(i) { 13 | const { activeIndex } = this.state 14 | 15 | if(i !== activeIndex) { 16 | this.setState({ 17 | activeIndex: i 18 | }) 19 | } 20 | } 21 | 22 | render() { 23 | const { activeIndex } = this.state 24 | return ( 25 |
26 | { 30 | this.setState({activeIndex:next}) 31 | this.props.onChange && this.props.onChange(perv, next) 32 | }} 33 | > 34 | {this.props.children} 35 | 36 | 37 |
38 | { 39 | this.props.children.map((v,i)=> 40 |
this.handleTabClick(i)} 44 | > 45 |
46 | ) 47 | } 48 |
49 |
50 | ) 51 | } 52 | } 53 | ) 54 | 55 | 56 | export default PaginationDecorator -------------------------------------------------------------------------------- /src/module/co-simple-list/mo-toast/assets/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon_info 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/module/co-simple-list/mo-toast/assets/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon_warning 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/module/co-simple-list/mo-toast/assets/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon_danger 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/module/mo-list-loading/listloading.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | .tj-list-loading__content { 3 | width: 100%; 4 | margin: 0 auto; 5 | position: relative; 6 | } 7 | .tj-list-loading__errtip { 8 | position: relative; 9 | margin-top: 20px; 10 | // filter: blur(2px); 11 | // filter: ~"progid\:DXImageTransform\.Microsoft\.Blur(PixelRadius\=1, MakeShadow\=false)"; /* IE6~IE9 */ 12 | div { 13 | position: relative; 14 | display: block; 15 | height: 20px; 16 | margin: 0 0 18px; 17 | background: #ddd; 18 | border-radius: 8px; 19 | overflow: hidden; 20 | 21 | span{ 22 | content: ""; 23 | opacity: .8; 24 | display: block; 25 | width: 100%; 26 | height: inherit; 27 | background: #eee; 28 | :local { 29 | animation: tj-list-loading__progressactive 1s infinite linear; 30 | } 31 | 32 | } 33 | 34 | &:nth-child(1) {width: 30%;} 35 | &:nth-child(2) {width: 20%;margin-bottom: 30px;} 36 | &:nth-child(3) {width: 60%;} 37 | &:nth-child(4) {width: 80%;} 38 | &:nth-child(5) {width: 50%;} 39 | } 40 | } 41 | .tj-list-loading__word { 42 | position: absolute; 43 | top: 0; 44 | right: 0; 45 | color: #333; 46 | font-size: 13px; 47 | font-weight: bold; 48 | :local { 49 | animation: tj-list-loading__fadein .8s infinite; 50 | } 51 | } 52 | 53 | 54 | } 55 | 56 | 57 | @keyframes tj-list-loading__fadein { 58 | 0%{ 59 | opacity: 1; 60 | } 61 | 50%{ 62 | opacity: .8; 63 | } 64 | 100% { 65 | opacity: 1; 66 | } 67 | } 68 | 69 | 70 | @keyframes tj-list-loading__progressactive { 71 | 0% { 72 | opacity: 0; 73 | transform: translateX(0); 74 | } 75 | 76 | 50% { 77 | opacity: .8; 78 | transform: translateX(50%); 79 | } 80 | 81 | 100% { 82 | opacity: 0; 83 | transform: translateX(100%); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/module/mo-dialog/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Modal from './modal' 4 | 5 | function alert(msg, props) { 6 | props = { 7 | msg:
{msg}
, 8 | visible: true, 9 | ...props 10 | } 11 | return next(props) 12 | } 13 | 14 | 15 | function next(props) { 16 | const div = document.createElement('div') 17 | document.body.appendChild(div) 18 | 19 | if(props.lockScroll != false) { 20 | document.body.style.setProperty('overflow', 'hidden') 21 | } 22 | 23 | const component = React.createElement(Modal, Object.assign({}, props, { 24 | onClose: () => { 25 | setTimeout(()=>{ 26 | ReactDOM.unmountComponentAtNode(div) 27 | document.body.removeChild(div) 28 | document.body.style.removeProperty('overflow') 29 | }, 200) 30 | 31 | 32 | if (props.onClose instanceof Function) { 33 | props.onClose() 34 | } 35 | } 36 | })) 37 | 38 | ReactDOM.render(component, div) 39 | } 40 | 41 | 42 | 43 | const modalRoot = document.body; 44 | 45 | class Dialog extends Component { 46 | constructor(props) { 47 | super(props); 48 | this.el = document.createElement('div'); 49 | } 50 | 51 | static alert = alert 52 | 53 | renderPoral() { 54 | const props = this.props 55 | return React.createElement(Modal, Object.assign({}, props, { 56 | onClose: () => { 57 | setTimeout(()=>{ 58 | document.body.style.removeProperty('overflow') 59 | }, 200) 60 | 61 | if (props.onClose instanceof Function) { 62 | props.onClose() 63 | } 64 | } 65 | })) 66 | } 67 | 68 | componentDidMount() { 69 | modalRoot.appendChild(this.el); 70 | } 71 | 72 | componentWillUnmount() { 73 | modalRoot.removeChild(this.el); 74 | } 75 | 76 | render() { 77 | return ReactDOM.createPortal( 78 | this.renderPoral(), 79 | this.el, 80 | ); 81 | } 82 | } 83 | 84 | export default Dialog -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const { 2 | resolve 3 | } = require('path'); 4 | var webpack = require('webpack'); 5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | 7 | // process.noDeprecation = true 8 | 9 | module.exports = { 10 | devtool: 'cheap-module-eval-source-map', 11 | entry: [ 12 | 'react-hot-loader/patch', 13 | 'webpack-dev-server/client?http://localhost:3000', 14 | 'webpack/hot/only-dev-server', 15 | 'babel-polyfill', 16 | './src/app' 17 | ], 18 | output: { 19 | path: resolve(__dirname, '/dist/'), 20 | filename: 'bundle.js', 21 | publicPath: '/' 22 | }, 23 | plugins: [ 24 | new webpack.HotModuleReplacementPlugin(), 25 | new webpack.NamedModulesPlugin(), 26 | new HtmlWebpackPlugin({ 27 | template: 'index.html', 28 | filename: './index.html', 29 | inject: true 30 | }), 31 | new webpack.ProvidePlugin({ 32 | 'Promise':'es6-promise', 33 | 'fetch': 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch' 34 | }), 35 | new webpack.DefinePlugin({ 36 | 'process.env': { 37 | 'NODE_ENV': JSON.stringify('development') 38 | } 39 | }) 40 | ], 41 | module: { 42 | rules: [{ 43 | test: /\.js$/, 44 | include: /src/, 45 | use: [ 46 | "babel-loader", 47 | "eslint-loader" 48 | ] 49 | }, { 50 | test: /\.less$/, 51 | exclude: /node_modules/, 52 | use: [ 53 | 'style-loader', { 54 | loader: 'css-loader', 55 | options: { 56 | minimize: true, 57 | modules: true, 58 | localIdentName: '[name]-[local]-[hash:base64:5]', 59 | importLoaders: 2 60 | } 61 | }, 62 | 'postcss-loader', 63 | 'less-loader' 64 | ], 65 | }, { 66 | test: /\.(jpe?g|png|gif|svg)$/i, 67 | include: /src/, 68 | use: [ 69 | 'url-loader?limit=8192&name=img/[hash:8].[name].[ext]' // 图片小于8k就转化为 base64, 或者单独作为文件 70 | ] 71 | }] 72 | }, 73 | resolve: { 74 | extensions: ['.js', '.jsx', '.json'], 75 | modules: ['node_modules', './src/module', './src/action', './src/util/'], 76 | alias: { 77 | 'my-util': resolve(__dirname, './src/libs/my-util'), 78 | 'action': resolve(__dirname, './src/action/index.js'), 79 | 'stroe': resolve(__dirname, './src/store/index.js'), 80 | 'layout': resolve(__dirname, './src/layout'), 81 | 'reducers': resolve(__dirname, './src/reducers') 82 | } 83 | } 84 | }; -------------------------------------------------------------------------------- /src/module/mo-spin/spin.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | .tj-laballspin { 3 | position: relative; 4 | display: block; 5 | width: 32px; 6 | height: 32px; 7 | font-size: 0; 8 | 9 | div { 10 | position: absolute; 11 | display: inline-block; 12 | width: 8px; 13 | height: 8px; 14 | border-radius: 100%; 15 | background-color: currentColor; 16 | border: 0 solid currentColor; 17 | :local { 18 | animation: tj-laballspin-spin 1s infinite ease-in-out; 19 | } 20 | 21 | 22 | &:nth-child(1) { 23 | top: 5%; 24 | left: 50%; 25 | animation-delay: -1.125s; 26 | } 27 | &:nth-child(2) { 28 | top: 18.1801948466%; 29 | left: 81.8198051534%; 30 | animation-delay: -1.25s; 31 | } 32 | &:nth-child(3) { 33 | top: 50%; 34 | left: 95%; 35 | animation-delay: -1.375s; 36 | } 37 | &:nth-child(4) { 38 | top: 81.8198051534%; 39 | left: 81.8198051534%; 40 | animation-delay: -1.5s; 41 | } 42 | &:nth-child(5) { 43 | top: 94.9999999966%; 44 | left: 50.0000000005%; 45 | animation-delay: -1.625s; 46 | } 47 | &:nth-child(6) { 48 | top: 81.8198046966%; 49 | left: 18.1801949248%; 50 | animation-delay: -1.75s; 51 | } 52 | &:nth-child(7) { 53 | top: 49.9999750815%; 54 | left: 5.0000051215%; 55 | animation-delay: -1.875s; 56 | } 57 | &:nth-child(8) { 58 | top: 18.179464974%; 59 | left: 18.1803700518%; 60 | animation-delay: -2s; 61 | } 62 | 63 | } 64 | } 65 | 66 | } 67 | 68 | @keyframes tj-laballspin-spin { 69 | 0%, 70 | 100% { 71 | opacity: 1; 72 | transform: scale(1); 73 | } 74 | 20% { 75 | opacity: 1; 76 | } 77 | 80% { 78 | opacity: 0; 79 | transform: scale(0); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/module/mo-transtion/transtion.less: -------------------------------------------------------------------------------- 1 | :global{ 2 | .tj-trans__slide-appear, 3 | .tj-trans__slide-enter { 4 | opacity: 0; 5 | transform: translate(100%); 6 | } 7 | 8 | .tj-trans__slide-appear-active, 9 | .tj-trans__slide-enter-active { 10 | opacity: 1; 11 | transform: translate(0); 12 | transition: transform 500ms ease, opacity 500ms ease-in; 13 | } 14 | 15 | .tj-trans__slide-exit { 16 | opacity: 1; 17 | transform: translate(0); 18 | } 19 | 20 | .tj-trans__slide-exit-active { 21 | opacity: 0.5; 22 | transform: translate(-100%); 23 | transition: transform 500ms ease, opacity 500ms ease-out; 24 | } 25 | 26 | 27 | .tj-trans__fade-appear, 28 | .tj-trans__fade-enter { 29 | opacity: 0; 30 | } 31 | 32 | .tj-trans__fade-appear-active, 33 | .tj-trans__fade-enter-active { 34 | transition: opacity .3s linear; 35 | opacity: 1; 36 | } 37 | 38 | .tj-trans__fade-exit { 39 | transition: opacity .2s linear; 40 | opacity: 1; 41 | } 42 | 43 | .tj-trans__fade-exit-active { 44 | opacity: 0; 45 | } 46 | 47 | 48 | 49 | 50 | .tj-trans__spread-enter { 51 | opacity: 0.01; 52 | transform: scale(.8); 53 | } 54 | .tj-trans__spread-enter-active { 55 | opacity: 1; 56 | transform: scale(1); 57 | transition: transform 100ms linear, opacity 100ms linear; 58 | } 59 | 60 | .tj-trans__spread-exit { 61 | opacity: 1; 62 | transform: translate(0, 0); 63 | } 64 | .tj-trans__spread-exit-active { 65 | opacity: 0.01; 66 | transform: translate(0, 2rem) scale(.8); 67 | transition: transform 200ms ease-in, opacity 200ms linear; 68 | } 69 | 70 | 71 | 72 | 73 | .tj-trans__pop-enter { 74 | opacity: 0.01; 75 | transform: scale(.8); 76 | } 77 | .tj-trans__pop-enter-active { 78 | opacity: 1; 79 | transform: scale(1); 80 | transition: transform 100ms linear, opacity 100ms linear; 81 | } 82 | 83 | .tj-trans__pop-exit { 84 | opacity: 1; 85 | transform: translate(0, 0); 86 | } 87 | .tj-trans__pop-exit-active { 88 | opacity: 0.01; 89 | transform: translate(0, 150px) scale(.8); 90 | transition: transform 200ms ease-in, opacity 200ms linear; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-boilerplate", 3 | "version": "3.0", 4 | "description": "", 5 | "private": false, 6 | "license": "MIT", 7 | "scripts": { 8 | "mock": "node mock/api.js", 9 | "start": "node server.js", 10 | "lint": "eslint src", 11 | "build-mac": "rm -rf dist && webpack --config ./webpack.prod.config.js --progress --profile --colors", 12 | "build-win": "del dist && webpack --config ./webpack.prod.config.js --progress --profile --colors" 13 | }, 14 | "author": "tumars", 15 | "devDependencies": { 16 | "autoprefixer": "^7.2.3", 17 | "babel-core": "^6.24.1", 18 | "babel-eslint": "^8.0.3", 19 | "babel-loader": "^7.0.0", 20 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 21 | "babel-preset-env": "^1.6.1", 22 | "babel-preset-react": "^6.24.1", 23 | "babel-preset-stage-0": "^6.24.1", 24 | "css-loader": "^0.28.7", 25 | "eslint": "^4.13.1", 26 | "eslint-loader": "^1.7.1", 27 | "eslint-plugin-react": "^7.5.1", 28 | "eventsource-polyfill": "^0.9.6", 29 | "exports-loader": "^0.6.3", 30 | "express": "^4.16.2", 31 | "extract-text-webpack-plugin": "^3.0.2", 32 | "fetch-jsonp": "^1.0.6", 33 | "file-loader": "^1.1.6", 34 | "html-webpack-plugin": "^2.17.0", 35 | "imports-loader": "^0.7.0", 36 | "less": "^2.7.3", 37 | "less-loader": "^4.0.3", 38 | "postcss-loader": "^2.0.9", 39 | "postcss-px-to-viewport": "^0.0.3", 40 | "react-hot-loader": "3.1.3", 41 | "redux-saga": "^0.16.0", 42 | "resolve-url-loader": "^2.2.1", 43 | "style-loader": "^0.19.1", 44 | "url-loader": "^0.6.2", 45 | "webpack": "^3.10.0", 46 | "webpack-dev-middleware": "^2.0.2", 47 | "webpack-dev-server": "^2.9.7" 48 | }, 49 | "dependencies": { 50 | "babel-polyfill": "^6.23.0", 51 | "es6-promise": "^4.1.1", 52 | "history": "^4.6.1", 53 | "prop-types": "^15.5.10", 54 | "react": "^16.2.0", 55 | "react-css-modules": "^4.7.1", 56 | "react-dom": "^16.2.0", 57 | "react-redux": "^5.0.4", 58 | "react-router": "^4.2.0", 59 | "react-router-dom": "^4.1.1", 60 | "react-transition-group": "^2.2.1", 61 | "redux": "^3.6.0", 62 | "redux-thunk": "^2.1.0", 63 | "reselect": "^3.0.1", 64 | "viewport-units-buggyfill": "^0.6.2", 65 | "whatwg-fetch": "^2.0.1" 66 | }, 67 | "homepage": "https://github.com/tumars/boilerplate-webpack-react-es6-cssModule" 68 | } 69 | -------------------------------------------------------------------------------- /src/module/mo-dialog/modal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Mask, Content } from './modal-modules' 4 | 5 | class Modal extends Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = { 10 | isShow: false 11 | }; 12 | 13 | this.enter = this.enter.bind(this) 14 | this.leave = this.leave.bind(this) 15 | this.handleMaskClick = this.handleMaskClick.bind(this) 16 | } 17 | 18 | componentDidMount() { 19 | this.props.visible && this.enter(); 20 | } 21 | 22 | componentWillReceiveProps(nextProps) { 23 | console.log(123, nextProps) 24 | if (nextProps.visible && !this.state.isShow) { 25 | this.enter() 26 | } else if(!nextProps.visible && this.state.isShow) { 27 | this.leave() 28 | } 29 | } 30 | 31 | shouldComponentUpdate(nextProps) { 32 | if(!nextProps.visible && !this.isShow) { 33 | return false 34 | } else { 35 | return true 36 | } 37 | } 38 | 39 | enter() { 40 | this.setState({isShow: true}) 41 | } 42 | 43 | leave() { 44 | this.setState({isShow: false}); 45 | this.props.onClose && this.props.onClose() 46 | } 47 | 48 | handleMaskClick() { 49 | this.props.closeOnClickModal && this.leave() 50 | } 51 | 52 | render() { 53 | const {msg, children, showCloseIcon} = this.props 54 | const { isShow } = this.state 55 | 56 | return ( 57 |
58 | 59 | 65 | {children} 66 | 67 |
68 | ); 69 | } 70 | } 71 | 72 | Modal.propTypes = { 73 | onClose: PropTypes.func, 74 | visible: PropTypes.bool, 75 | children:PropTypes.node, 76 | msg: PropTypes.node, 77 | showCloseIcon: PropTypes.bool, 78 | closeOnClickModal: PropTypes.bool 79 | }; 80 | 81 | Modal.defaultProps = { 82 | visible: false, 83 | showCloseIcon: true, 84 | closeOnClickModal: false 85 | }; 86 | 87 | export default Modal; -------------------------------------------------------------------------------- /src/module/mo-dialog/modal-modules.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TransitionGroup } from 'react-transition-group' 3 | import { FadeTransition, PopTransition } from '../mo-transtion' 4 | import style from './modal.less'; 5 | 6 | var classNames = require('classnames'); 7 | 8 | // 控制显示 9 | const View = ({ isshow, className, style, children, onClick}) => { 10 | return ( 11 |
16 | {children} 17 |
18 | ) 19 | } 20 | 21 | 22 | //关闭按钮 23 | const CloseIcon = ({visible, onClick}) => ( 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | 31 | 32 | // 背景透明层 33 | const Mask = ({visible, onClick}) => ( 34 | 35 | 36 | 41 | 42 | 43 | ) 44 | 45 | 46 | // 内容主体 47 | const Content = ({className, visible, msg, children, showCloseIcon, onCloseIcon}) => ( 48 | 49 | 50 | 51 | {children} 52 | {msg} 53 | 54 | 55 | 56 | 57 | ) 58 | 59 | 60 | 61 | export { 62 | Mask, 63 | Content 64 | } 65 | -------------------------------------------------------------------------------- /src/module/mo-icon/svg-config.js: -------------------------------------------------------------------------------- 1 | const SvgConfig = { 2 | "check-circle": 'M51.2 512C51.2 257.501288 257.501288 51.2 512 51.2s460.8 206.301288 460.8 460.8c0 254.498712-206.301288 460.8-460.8 460.8S51.2 766.498712 51.2 512zM0 512c0 282.771525 229.228475 512 512 512 282.771525 0 512-229.228475 512-512 0-282.771525-229.228475-512-512-512C229.228475 0 0 229.228475 0 512zM704.104136 369.23878c-13.876068-14.014915-36.334644-14.014915-50.453695 0L452.833627 573.144949l-83.230373-83.863864c-13.624407-14.014915-36.334644-14.014915-49.950373 0-13.624407 14.023593-13.624407 36.707797 0.503322 50.471051l105.697627 107.320407 1.770305 2.030644c14.119051 13.763254 36.325966 13.763254 50.445017 0l226.034983-228.890034C718.231864 405.937898 718.231864 383.505356 704.104136 369.23878z', 3 | "fail-circle": 'M512.697 75.445c-247.539 0-448.208 200.669-448.208 448.208 0 247.54 200.669 448.208 448.208 448.208 247.54 0 448.208-200.668 448.208-448.208 0-247.539-200.668-448.208-448.208-448.208zM690.686 657.208c12.265 12.274 12.265 32.171 0 44.427-12.283 12.274-32.171 12.274-44.453 0l-133.316-133.316-133.327 133.316c-12.274 12.274-32.161 12.274-44.445 0-12.265-12.256-12.265-32.153 0-44.427l133.335-133.353-133.335-133.317c-12.265-12.256-12.265-32.171 0-44.444 12.283-12.256 32.171-12.256 44.445 0l133.327 133.335 133.316-133.335c12.283-12.256 32.171-12.256 44.453 0 12.265 12.274 12.265 32.189 0 44.444l-133.335 133.317 133.335 133.353z', 4 | "check": 'M416.832 798.08C400.64 798.08 384.512 791.872 372.16 779.52L119.424 525.76C94.784 500.992 94.784 460.8 119.424 436.032 144.128 411.264 184.128 411.264 208.768 436.032L416.832 644.928 814.4 245.76C839.04 220.928 879.04 220.928 903.744 245.76 928.384 270.528 928.384 310.656 903.744 335.424L461.504 779.52C449.152 791.872 432.96 798.08 416.832 798.08Z', 5 | "fail": 'M813.378513 293.592688 596.64533 510.324848 813.378513 727.058031c23.917736 23.917736 23.939225 62.757323 0.002047 86.695524-23.938202 23.938202-62.777789 23.916712-86.696548-0.001023L509.950829 597.019349 293.217646 813.752532c-23.938202 23.938202-62.774719 23.917736-86.692455 0-23.958668-23.957645-23.938202-62.755276 0-86.693478L423.257352 510.325871 206.525192 293.593711c-23.960715-23.960715-23.937179-62.757323 0-86.694501 23.937179-23.937179 62.732763-23.960715 86.693478 0l216.73216 216.73216L726.684012 206.89921c23.938202-23.938202 62.73788-23.959691 86.695524-0.001023C837.297272 230.815923 837.315692 269.654486 813.378513 293.592688z', 6 | } 7 | 8 | export default SvgConfig; -------------------------------------------------------------------------------- /src/module/mo-tabs/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import Carousel from '../mo-carousel' 4 | import { Panel, Nav} from './tabs-modules' 5 | import './tabs.less'; 6 | 7 | 8 | class Tabs extends Component { 9 | static Panel = Panel 10 | constructor(props) { 11 | super(props) 12 | this.state = { 13 | activeIndex: this.props.activeIndex 14 | } 15 | this.handleChange = this.handleChange.bind(this) 16 | } 17 | 18 | shouldComponentUpdate(nextProps, nextState) { 19 | if (this.state.activeIndex !== nextState.activeIndex) { 20 | return true 21 | } 22 | if(this.props.children !== nextProps.children) { 23 | return true 24 | } 25 | return true 26 | } 27 | 28 | handleChange(nextIndex) { 29 | const { activeIndex } = this.state 30 | if(nextIndex !== activeIndex) { 31 | this.setState({ 32 | activeIndex: nextIndex 33 | }) 34 | this.props.onTabChange && this.props.onTabChange(activeIndex, nextIndex) 35 | } 36 | } 37 | 38 | render() { 39 | const { activeIndex } = this.state 40 | if(this.props.children.length==0) { 41 | return 'missing Tabs Panel' 42 | } 43 | const panels = this.props.children 44 | 45 | return ( 46 |
this.tabContent = n} className="tj-tabs__content"> 47 |
70 | ) 71 | } 72 | } 73 | 74 | Tabs.propTypes = { 75 | activeIndex: PropTypes.number, 76 | type: PropTypes.string, 77 | onTabChange: PropTypes.func 78 | } 79 | 80 | Tabs.defaultProps = { 81 | activeIndex: 0 82 | } 83 | 84 | export default Tabs -------------------------------------------------------------------------------- /src/reducers/data-list-reducer.js: -------------------------------------------------------------------------------- 1 | import _ut from 'my-util' 2 | 3 | /*-----------------------------------------------------------------*/ 4 | /*List Reducer*/ 5 | /*-----------------------------------------------------------------*/ 6 | 7 | const initListInfo = { 8 | isFetching: false, 9 | list: [] 10 | } 11 | 12 | 13 | const DataListReducer = (state = initListInfo, action) => { 14 | switch (action.type) { 15 | case 'LIST_ADD': 16 | { 17 | switch (action.status) { 18 | case 'error': 19 | { 20 | return Object.assign({}, state, { 21 | isFetching: false 22 | }) 23 | } 24 | case 'success': 25 | { 26 | const { list } = action; 27 | const oldList = state['list']; 28 | return Object.assign({}, { 29 | list: oldList.concat(list), 30 | isFetching: false} 31 | ) 32 | } 33 | default: 34 | { 35 | return Object.assign({}, state, { 36 | isFetching: true 37 | }) 38 | } 39 | } 40 | } 41 | case 'LIST_CLEAR': 42 | { 43 | return Object.assign({}, state, initListInfo) 44 | } 45 | default: 46 | return state 47 | } 48 | } 49 | 50 | 51 | 52 | /*-----------------------------------------------------------------*/ 53 | /*List Action*/ 54 | /*-----------------------------------------------------------------*/ 55 | const addList = (list) => ({ 56 | type: 'LIST_ADD', 57 | status: 'success', 58 | list 59 | }) 60 | 61 | const fetchStart = () => ({ 62 | type: 'LIST_ADD', 63 | }) 64 | 65 | const fetchError = () => ({ 66 | type: 'LIST_ADD', 67 | status: 'error', 68 | }) 69 | 70 | const clearList = () => ({ 71 | type: 'LIST_CLEAR' 72 | }) 73 | 74 | 75 | 76 | 77 | /*-----------------------------------------------------------------*/ 78 | /*Async List Action Depend on Redux-Thunk*/ 79 | /*-----------------------------------------------------------------*/ 80 | const fetchAddList = () => async dispatch => { 81 | dispatch(fetchStart()) 82 | try { 83 | const list = await _ut.fetch(`http://localhost:3003/movie`) 84 | dispatch(addList(list.data)) 85 | } catch(e) { 86 | dispatch(fetchError()) 87 | console.log(e) 88 | return Promise.reject(e) 89 | } 90 | } 91 | 92 | 93 | 94 | export { 95 | DataListReducer, 96 | addList, clearList, 97 | fetchAddList 98 | } -------------------------------------------------------------------------------- /src/module/mo-segmented/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import './segmented.less'; 4 | 5 | var classNames = require('classnames'); 6 | 7 | 8 | class Segmented extends Component { 9 | constructor(props) { 10 | super(props) 11 | this.state = { 12 | activeIndex: this.props.activeIndex 13 | }; 14 | this.handleChange = this.handleChange.bind(this); 15 | 16 | const { tintColor, tintBg } = props 17 | 18 | this.defaultStyle = { 19 | color: tintColor, 20 | background: tintBg 21 | } 22 | this.activeStyle = { 23 | color: tintBg, 24 | background:tintColor 25 | } 26 | } 27 | 28 | shouldComponentUpdate(nextProps, nextState) { 29 | if (this.state.activeIndex !== nextState.activeIndex) { 30 | return true 31 | } 32 | if(this.props.children !== nextProps.children) { 33 | return true 34 | } 35 | return true 36 | } 37 | 38 | handleChange(nextIndex) { 39 | const { activeIndex } = this.state 40 | if(nextIndex !== activeIndex) { 41 | this.setState({ 42 | activeIndex: nextIndex 43 | }) 44 | this.props.onChange && this.props.onChange(activeIndex, nextIndex) 45 | } 46 | } 47 | 48 | render() { 49 | const { activeIndex } = this.state; 50 | const { values, tintBg, lineColor=tintBg, style, className } = this.props; 51 | const len = values.length; 52 | 53 | return ( 54 |
this.tabContent = n} 56 | className={classNames("tj-segmented__content", className)} 57 | style={{...style, borderColor: tintBg}} 58 | > 59 | { 60 | values.map((title, i) => 61 |
75 | { title } 76 |
77 | ) 78 | } 79 |
80 | ) 81 | } 82 | } 83 | 84 | Segmented.propTypes = { 85 | activeIndex: PropTypes.number, 86 | values: PropTypes.array, 87 | tintColor: PropTypes.string, 88 | tintBg: PropTypes.string, 89 | onChange: PropTypes.func 90 | } 91 | 92 | Segmented.defaultProps = { 93 | activeIndex: 0, 94 | tintColor: '#0C037B', 95 | tintBg: '#fff' 96 | } 97 | 98 | export default Segmented -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | const { resolve } = require('path'); 3 | var webpack = require('webpack'); 4 | var autoprefixer = require('autoprefixer'); 5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 7 | 8 | module.exports = { 9 | devtool: false, 10 | entry: ['babel-polyfill', './src/app.js'], 11 | output: { 12 | path: path.join(__dirname, '/dist/'), 13 | filename: '[name]-[hash:5].min.js', 14 | chunkFilename: '[name]-[hash:5].chunk.js', 15 | publicPath: './' 16 | }, 17 | plugins: [ 18 | new webpack.LoaderOptionsPlugin({ 19 | minimize: true, 20 | debug: false 21 | }), 22 | new webpack.optimize.UglifyJsPlugin({ 23 | beautify: false, 24 | mangle: { 25 | screw_ie8: true, 26 | keep_fnames: true 27 | }, 28 | compress: { 29 | screw_ie8: true 30 | }, 31 | comments: false 32 | }), 33 | new HtmlWebpackPlugin({ 34 | template: './index.html', 35 | minify: { 36 | removeComments: true, 37 | collapseWhitespace: true, 38 | removeRedundantAttributes: true, 39 | useShortDoctype: true, 40 | removeEmptyAttributes: true, 41 | removeStyleLinkTypeAttributes: true, 42 | keepClosingSlash: true, 43 | minifyJS: true, 44 | minifyCSS: true, 45 | minifyURLs: true 46 | }, 47 | inject: 'body', 48 | filename: 'index.html' 49 | }), 50 | new ExtractTextPlugin({ 51 | filename: 'bundle.css', 52 | disable: false, 53 | allChunks: true 54 | }), 55 | new webpack.ProvidePlugin({ 56 | 'Promise':'es6-promise', 57 | 'fetch': 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch' 58 | }), 59 | new webpack.DefinePlugin({ 60 | 'process.env': { 61 | 'NODE_ENV': JSON.stringify('production') 62 | } 63 | }) 64 | ], 65 | module: { 66 | rules: [{ 67 | test: /\.js$/, 68 | include: resolve(__dirname, 'src'), 69 | use: ['babel-loader'] 70 | }, { 71 | test: /\.less$/, 72 | exclude: [/node_modules/], 73 | use: ExtractTextPlugin.extract({ 74 | fallback: 'style-loader', 75 | use: 'css-loader?modules,localIdentName="[name]-[local]-[hash:base64:5]"!postcss-loader!less-loader' 76 | }), 77 | }, { 78 | test:/\.(png|jpg|gif|svg)$/, 79 | exclude: [/node_modules/], 80 | use: 'url-loader?limit=8192&name=build/[name].[ext]' 81 | }] 82 | }, 83 | resolve: { 84 | extensions: ['.js', '.jsx', '.json'], 85 | modules: ['node_modules', './src/module', './src/action', './src/util/'], 86 | alias: { 87 | 'my-util': resolve(__dirname, './src/libs/my-util'), 88 | 'action': resolve(__dirname, './src/action/index.js'), 89 | 'stroe': resolve(__dirname, './src/store/index.js'), 90 | 'layout': resolve(__dirname, './src/layout'), 91 | 'reducers': resolve(__dirname, './src/reducers') 92 | } 93 | } 94 | }; -------------------------------------------------------------------------------- /src/module/co-simple-list/mo-toast/toast.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { TransitionGroup } from 'react-transition-group' 4 | import { FadeTransition} from '../mo-transtion' 5 | 6 | import './toast.less' 7 | 8 | var classNames = require('classnames'); 9 | 10 | 11 | const icons = { 12 | error: require('./assets/error.svg'), 13 | info: require('./assets/info.svg'), 14 | success: require('./assets/success.svg'), 15 | warning: require('./assets/warning.svg') 16 | } 17 | 18 | class Toast extends Component { 19 | constructor(props) { 20 | super(props); 21 | 22 | this.state = { 23 | isVisible: this.props.visible 24 | }; 25 | } 26 | 27 | componentDidMount() { 28 | this.setState({ 29 | isVisible: true 30 | }) 31 | this.startTimer(); 32 | } 33 | 34 | componentWillUnmount() { 35 | this.stopTimer(); 36 | } 37 | 38 | onClose() { 39 | this.stopTimer(); 40 | 41 | this.setState({ 42 | isVisible: false 43 | }); 44 | 45 | const { onClose } = this.props; 46 | onClose && onClose(); 47 | } 48 | 49 | startTimer() { 50 | if (this.props.duration > 0) { 51 | this.timeout = setTimeout(() => { 52 | !this.props.showClose && this.onClose() 53 | }, this.props.duration) 54 | } 55 | } 56 | 57 | stopTimer() { 58 | clearTimeout(this.timeout); 59 | } 60 | 61 | renderContent() { 62 | const { iconClass, className, type } = this.props; 63 | const { isVisible } = this.state 64 | const boxClassNames = classNames( 65 | 'tj-msg', 66 | {'tj-msg--hasicon': type !== 'text'}, 67 | className 68 | ) 69 | return ( 70 |
76 | { iconClass && } 77 | { !iconClass && type !== 'text' && } 78 | { this.props.content } 79 | { this.props.showClose &&
} 80 |
81 | ) 82 | } 83 | 84 | render() { 85 | const { isVisible } = this.state 86 | 87 | return ( 88 | 89 | 90 | { this.renderContent() } 91 | 92 | 93 | ); 94 | } 95 | } 96 | 97 | Toast.propTypes = { 98 | type: PropTypes.oneOf(['text', 'success', 'warning', 'info', 'error', 'loading']), 99 | content: PropTypes.string.isRequired, 100 | duration: PropTypes.number, 101 | showClose: PropTypes.bool, 102 | className: PropTypes.string, 103 | iconClass: PropTypes.string 104 | }; 105 | 106 | Toast.defaultProps = { 107 | type: 'text', 108 | duration: 3000, 109 | showClose: false 110 | }; 111 | 112 | export default Toast; -------------------------------------------------------------------------------- /src/module/mo-toast/toast.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { TransitionGroup } from 'react-transition-group' 4 | import { FadeTransition} from '../mo-transtion' 5 | 6 | import './toast.less' 7 | 8 | var classNames = require('classnames'); 9 | 10 | 11 | const icons = { 12 | error: require('./assets/error.svg'), 13 | info: require('./assets/info.svg'), 14 | success: require('./assets/success.svg'), 15 | warning: require('./assets/warning.svg') 16 | } 17 | 18 | class Toast extends Component { 19 | constructor(props) { 20 | super(props); 21 | 22 | this.state = { 23 | isVisible: this.props.visible 24 | }; 25 | } 26 | 27 | componentDidMount() { 28 | this.setState({ 29 | isVisible: true 30 | }) 31 | this.startTimer(); 32 | } 33 | 34 | componentWillUnmount() { 35 | this.stopTimer(); 36 | } 37 | 38 | onClose() { 39 | this.stopTimer(); 40 | this.setState({ 41 | isVisible: false 42 | }); 43 | 44 | const { onClose } = this.props; 45 | if(onClose instanceof Function) { 46 | onClose(); 47 | } 48 | } 49 | 50 | startTimer() { 51 | if (this.props.duration > 0) { 52 | this.timeout = setTimeout(() => { 53 | !this.props.showClose && this.onClose() 54 | }, this.props.duration) 55 | } 56 | } 57 | 58 | stopTimer() { 59 | clearTimeout(this.timeout); 60 | } 61 | 62 | renderContent() { 63 | const { iconClass, className, type } = this.props; 64 | const { isVisible } = this.state 65 | const boxClassNames = classNames( 66 | 'tj-toast', 67 | {'tj-toast--hasicon': type !== 'text'}, 68 | className 69 | ) 70 | return ( 71 |
77 | { iconClass && } 78 | { !iconClass && type !== 'text' && } 79 | { this.props.content } 80 | { this.props.showClose &&
} 81 |
82 | ) 83 | } 84 | 85 | render() { 86 | const { isVisible } = this.state 87 | 88 | return ( 89 | 90 | 91 | { this.renderContent() } 92 | 93 | 94 | ); 95 | } 96 | } 97 | 98 | Toast.propTypes = { 99 | type: PropTypes.oneOf(['text', 'success', 'warning', 'info', 'error', 'loading']), 100 | content: PropTypes.string.isRequired, 101 | duration: PropTypes.number, 102 | showClose: PropTypes.bool, 103 | className: PropTypes.string, 104 | iconClass: PropTypes.string 105 | }; 106 | 107 | Toast.defaultProps = { 108 | type: 'text', 109 | duration: 3000, 110 | showClose: false 111 | }; 112 | 113 | export default Toast; -------------------------------------------------------------------------------- /src/module/mo-time-count/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class TimeCount extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.defaultState = { 8 | day: 0, 9 | hour: 0, 10 | minute: 0, 11 | second: 0, 12 | isfailure: true 13 | } 14 | this.state = this.defaultState 15 | } 16 | componentWillUnmount() { 17 | this.reset() 18 | } 19 | 20 | componentWillReceiveProps(nextProps) { 21 | if(nextProps.gapTime !== this.props.gapTime || nextProps.endDate !== this.props.endDate) { 22 | this.reset() 23 | this.runCount(nextProps); 24 | } 25 | } 26 | 27 | componentDidMount() { 28 | this.runCount(this.props); 29 | } 30 | 31 | reset() { 32 | clearInterval(this.timer) 33 | this.setState({ 34 | ...this.defaultState 35 | }) 36 | } 37 | 38 | runCount(props) { 39 | let { onOver } = props; 40 | let gapTime = this.getGapTime(props); 41 | 42 | if(!gapTime) { 43 | return 44 | } 45 | 46 | this.timer = setInterval(() => { 47 | gapTime = gapTime - 1000; 48 | if(gapTime <= 0) { 49 | clearInterval(this.timer) 50 | this.setState({ 51 | isfailure: true 52 | }) 53 | onOver && onOver() 54 | } 55 | this.renderCount(gapTime) 56 | }, 1000) 57 | } 58 | 59 | getGapTime(props) { 60 | let { endDate, startDate, gapTime } = props; 61 | if(gapTime && gapTime > 0) { 62 | return gapTime; 63 | } else { 64 | if (!endDate || !startDate) { 65 | this.setState({ 66 | isfailure: true 67 | }) 68 | console.warn('missing endDate or startDate props') 69 | return false 70 | } 71 | 72 | if(endDate instanceof Date) { 73 | endDate = Date.parse(endDate); 74 | } 75 | if(startDate instanceof Date) { 76 | startDate = Date.parse(startDate); 77 | } 78 | 79 | return (endDate - startDate) 80 | } 81 | } 82 | 83 | renderCount(temp) { 84 | // 天 85 | var int_day = Math.floor(temp / 86400000) 86 | temp -= int_day * 86400000; 87 | // 时 88 | var int_hour = Math.floor(temp / 3600000) 89 | temp -= int_hour * 3600000; 90 | // 分 91 | var int_minute = Math.floor(temp / 60000) 92 | temp -= int_minute * 60000; 93 | // 秒 94 | var int_second = Math.floor(temp / 1000) 95 | // 时分秒为单数时、前面加零 96 | if (int_day < 10) { 97 | int_day = "0" + int_day; 98 | } 99 | if (int_hour < 10) { 100 | int_hour = "0" + int_hour; 101 | } 102 | if (int_minute < 10) { 103 | int_minute = "0" + int_minute; 104 | } 105 | if (int_second < 10) { 106 | int_second = "0" + int_second; 107 | } 108 | 109 | this.setState({ 110 | day: int_day, 111 | hour: int_hour, 112 | minute: int_minute, 113 | second: int_second, 114 | isfailure: false 115 | }) 116 | } 117 | 118 | renderDefault() { 119 | const { day, hour, minute, second} = this.state; 120 | return `${day} 天 ${hour} 时 ${minute} 分 ${second}秒` 121 | } 122 | 123 | renderWithoutDay() { 124 | let { day, hour, minute, second} = this.state; 125 | hour = +(day * 24 + hour); 126 | hour = hour < 10 ? '0' + hour : hour; 127 | 128 | return `${hour} 时 ${minute} 分 ${second} 秒` 129 | } 130 | 131 | renderWithoutHour() { 132 | let { day, hour, minute, second} = this.state; 133 | minute = +(day * 24 * 60 + hour * 60 + minute); 134 | minute = minute < 10 ? '0' + minute : minute; 135 | 136 | return `${minute} 分 ${second} 秒` 137 | } 138 | 139 | render() { 140 | const { className, showDay, showHour } = this.props; 141 | return ( 142 | 143 | { 144 | this.state.isfailure 145 | ? ' - - ' 146 | : showHour 147 | ? showDay 148 | ? this.renderDefault() 149 | : this.renderWithoutDay() 150 | : this.renderWithoutHour() 151 | } 152 | 153 | ); 154 | } 155 | } 156 | 157 | TimeCount.propTypes = { 158 | className: PropTypes.string, 159 | endDate: PropTypes.any, 160 | startDate: PropTypes.any, 161 | gapTime: PropTypes.number, 162 | showDay: PropTypes.bool, 163 | showHour: PropTypes.bool, 164 | onOver: PropTypes.func 165 | } 166 | 167 | TimeCount.defaultProps = { 168 | endDate: null, 169 | startDate: null, 170 | showHour: true, 171 | showDay: true, 172 | onOver: () => console.warn('coundown timer was end') 173 | } 174 | 175 | export default TimeCount -------------------------------------------------------------------------------- /src/couter.js: -------------------------------------------------------------------------------- 1 | !function () { 2 | var base = { 3 | na: function () { 4 | var ua = window.navigator.userAgent.toLowerCase(); 5 | var isIE = (ua.indexOf("msie") > -1) ? true : false; 6 | var isFirefox = (ua.indexOf("firefox") > -1) ? true : false; 7 | var isChrome = (ua.indexOf("chrome") > -1) ? true : false; 8 | var isSafari = (ua.indexOf("safari") > -1) ? true : false; 9 | var isOpera = (ua.indexOf("opera") > -1) ? true : false; 10 | var isAndroid = (ua.indexOf("android") > -1) ? true : false; 11 | if (isIE) { 12 | return "Microsoft Internet Explorer " + ua.match(/msie.([\d.]+)/)[1] 13 | } else if (isFirefox) { 14 | return "Firefox " + ua.match(/firefox.([\d.]+)/)[1] 15 | } else if (isChrome) { 16 | return "Chrome " + ua.match(/chrome.([\d.]+)/)[1] 17 | } else if (isOpera) { 18 | return "Opera " + ua.match(/opera.([\d.]+)/)[1] 19 | } else if (isSafari && isAndroid) { 20 | return "Android's browser " + ua.match(/version.([\d.]+)/)[1] 21 | } else if (isSafari) { 22 | return "Safari " + ua.match(/version.([\d.]+)/)[1] 23 | } else { 24 | return "unknown" 25 | } 26 | }, 27 | os: function () { 28 | var ua = window.navigator.userAgent.toLowerCase(); 29 | var isWNT = (ua.indexOf("windows nt") > -1) ? true : false; 30 | var isWindows = (ua.indexOf("windows") > -1) ? true : false; 31 | var isWP = (ua.indexOf("windows phone") > -1) ? true : false; 32 | var isMac = (ua.indexOf("mac") > -1) ? true : false; 33 | var isUnix = (ua.indexOf("x11") > -1) ? true : false; 34 | var isIphone = (ua.indexOf("iphone") > -1) ? true : false; 35 | var isIpad = (ua.indexOf("ipad") > -1) ? true : false; 36 | var isIpod = (ua.indexOf("ipod") > -1) ? true : false; 37 | var isAndroid = (ua.indexOf("android") > -1) ? true : false; 38 | if (isWindows) { 39 | var tmp = ""; 40 | if (isWNT) { 41 | switch (ua.match(/windows nt ([\d\.]+)/)[1]) { 42 | case "6.2": 43 | tmp = "Win8"; 44 | break; 45 | case "6.1": 46 | tmp = "Win7"; 47 | break; 48 | case "6.0": 49 | tmp = "WinVista"; 50 | break; 51 | case "5.2": 52 | tmp = "Windows Server 2003"; 53 | break; 54 | case "5.1": 55 | tmp = "WinXp"; 56 | break; 57 | case "5.0": 58 | tmp = "Win2000"; 59 | break; 60 | default: 61 | tmp = "other Windows"; 62 | break 63 | } 64 | return tmp 65 | } else { 66 | if (isWP) { 67 | return "mobile windows" 68 | } else { 69 | return "Lower windows" 70 | } 71 | } 72 | } else if (isIphone || isIpad || isIpod) { 73 | return "Iphone or Ipad or Ipod" 74 | } else if (isMac) { 75 | return "Mac OS X" 76 | } else if (isAndroid) { 77 | return "Android " + ua.match(/android.([\d\.]+)/)[1] 78 | } else if (isUnix) { 79 | return "like Unix" 80 | } else { 81 | return "unknown" 82 | } 83 | }, 84 | size: function () { 85 | return window.screen.width + "*" + window.screen.height 86 | }, 87 | src: document.referrer, 88 | url: document.location.href.replace("#","%23") 89 | }; 90 | 91 | function jsLoad(sUrl, sBianMa, fCallback) { 92 | var _script = document.createElement('script'); 93 | _script.setAttribute('charset', sBianMa); 94 | _script.setAttribute('type', 'text/javascript'); 95 | _script.setAttribute('src', sUrl); 96 | document.getElementsByTagName('head')[0].appendChild(_script); 97 | if (/msie/.test(window.navigator.userAgent.toLowerCase())) { 98 | _script.onreadystatechange = function () { 99 | if (this.readyState == 'loaded' || this.readyState == 'complete') { 100 | _script.parentNode.removeChild(_script); 101 | if (fCallback) fCallback() 102 | } 103 | } 104 | } else if (/gecko/.test(window.navigator.userAgent.toLowerCase()) || /opera/.test(window.navigator.userAgent.toLowerCase())) { 105 | _script.onload = function () { 106 | _script.parentNode.removeChild(_script); 107 | if (fCallback) fCallback() 108 | } 109 | } else { 110 | _script.parentNode.removeChild(_script); 111 | if (fCallback) fCallback() 112 | } 113 | } 114 | window.jsonp = function () { }; 115 | var tmp = "na=" + base.na() + "&os=" + base.os() + "&size=" + base.size() + "&src=" + base.src + "&url=" + base.url + "&type=Mongo&v=" + Math.random(); 116 | jsLoad("https://counter1.1234567.com.cn/?" + tmp + "&callback=jsonp", "utf-8", function () { }) 117 | }.call(this) -------------------------------------------------------------------------------- /src/module/mo-carousel/carousel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { GetSlideDirection, setCss3Style } from './method' 4 | // import styles from './carousel.less' 5 | 6 | var classNames = require('classnames'); 7 | 8 | 9 | class Carousel extends Component { 10 | constructor(props) { 11 | super(props) 12 | 13 | // 正显示的元素位置 14 | this.activeIndex = this.props.activeIndex 15 | // 单个元素宽度 16 | this.scaleW = 0 17 | // 总偏移量 18 | this.totalX= 0 19 | 20 | // 手指按下时的偏移量 21 | this.startX= 0 22 | this.startY= 0 23 | // 手指移动的偏移量 24 | this.offsetX = 0 25 | this.offsetY = 0 26 | 27 | this.startTouch = this.startTouch.bind(this) 28 | this.moveTouch = this.moveTouch.bind(this) 29 | this.endTouch = this.endTouch.bind(this) 30 | 31 | } 32 | 33 | componentDidMount() { 34 | const { activeIndex } = this 35 | this.reset() 36 | this.scrollTo(activeIndex) 37 | } 38 | 39 | componentWillReceiveProps(nextProps) { 40 | const { activeIndex } = this 41 | const { activeIndex: newActiveIndex } = nextProps 42 | if(newActiveIndex !== activeIndex) { 43 | this.scrollTo(newActiveIndex) 44 | } 45 | } 46 | 47 | shouldComponentUpdate(nextProps) { 48 | const { activeIndex } = this 49 | const { activeIndex: newActiveIndex } = nextProps 50 | if(activeIndex !== newActiveIndex) {return true} 51 | if(this.props.children != nextProps.children ) {return true} 52 | 53 | return true 54 | } 55 | 56 | reset() { 57 | requestAnimationFrame(() => { 58 | const {carouselWarp: { clientWidth} , slideList} = this 59 | const { children } = slideList 60 | this.scaleW = clientWidth 61 | slideList.style.width = `${clientWidth * slideList.children.length}px` 62 | slideList.style.display = 'none' 63 | Array.prototype.forEach.call(children, (item)=>{ 64 | item.style.width = clientWidth + 'px' 65 | }) 66 | slideList.style.display = 'flex' 67 | }) 68 | } 69 | 70 | scrollTo(index) { 71 | const { scaleW } = this; 72 | this.totalX = -index * scaleW 73 | this.toggleTransition() 74 | this.moveCarousel(-index * scaleW) 75 | this.activeIndex = index 76 | } 77 | 78 | moveCarousel(distance) { 79 | requestAnimationFrame(()=>{ 80 | setCss3Style(this.slideList, 'transform', 'translateX(' + distance + 'px)') 81 | }) 82 | } 83 | 84 | toggleTransition() { 85 | setCss3Style(this.slideList, 'transition', 'transform .2s cubic-bezier(.645,.045,.355,1)') 86 | setTimeout(()=> { 87 | setCss3Style(this.slideList, 'transition', 'none') 88 | }, 200) 89 | } 90 | 91 | startTouch(event) { 92 | this.startX = event.targetTouches[0].pageX 93 | this.startY = event.targetTouches[0].pageY 94 | this.offsetX = 0 95 | } 96 | 97 | moveTouch(event) { 98 | const { onMove } = this.props 99 | const { pageX, pageY } = event.targetTouches[0] 100 | 101 | if(GetSlideDirection(this.startX, this.startY, pageX, pageY) !== 0){ 102 | event.preventDefault(); 103 | } 104 | 105 | 106 | const { scaleW, startX, totalX } = this 107 | const offsetX = pageX - startX 108 | 109 | // 最多移动 80% 距离 110 | if (Math.abs(offsetX) > scaleW * 0.8) {return} 111 | 112 | this.offsetX = offsetX 113 | const scrollWidth = offsetX + totalX 114 | this.moveCarousel(scrollWidth) 115 | 116 | onMove && onMove(scrollWidth) 117 | } 118 | 119 | endTouch() { 120 | const { activeIndex, scaleW, offsetX } = this 121 | const { children } = this.props 122 | 123 | // 记录滑动边界,用于判定滑动的类型,超过总长 20% 即判定为移动 124 | const boundary = scaleW / 5; 125 | 126 | if (offsetX > boundary) { 127 | // 右滑 128 | if(activeIndex === 0) { 129 | this.setIndex(0) 130 | return 131 | } 132 | this.setIndex(-1); 133 | } else if (offsetX < 0 && offsetX < - boundary) { 134 | // 左滑 135 | if(activeIndex + 1 >= children.length) { 136 | this.setIndex(0) 137 | return 138 | } 139 | this.setIndex(+1); 140 | } else { 141 | this.setIndex(0); 142 | } 143 | } 144 | 145 | setIndex(n) { 146 | const { scaleW } = this 147 | const { onChange } = this.props 148 | const oldActiveIndex = this.activeIndex 149 | const newActiveIndex = oldActiveIndex + n 150 | 151 | this.activeIndex = newActiveIndex 152 | this.totalX = -newActiveIndex * scaleW 153 | 154 | this.toggleTransition() 155 | this.moveCarousel(this.totalX) 156 | 157 | onChange && onChange(oldActiveIndex, newActiveIndex) 158 | } 159 | 160 | render() { 161 | const { className } = this.props; 162 | return ( 163 |
this.carouselWarp = n} 165 | className={classNames('tj-carousel__wrap', className)} 166 | style={{...this.props.style}} 167 | > 168 |
    this.slideList = n} 170 | className="tj-carousel__slide-list" 171 | onTouchStart={this.startTouch} 172 | onTouchMove={this.moveTouch} 173 | onTouchEnd={this.endTouch} 174 | > 175 | { 176 | React.Children.map(this.props.children, (slide, i) => 177 |
  • 178 | {React.cloneElement(slide)} 179 |
  • 180 | ) 181 | } 182 |
183 | 184 |
185 | ) 186 | } 187 | } 188 | 189 | Carousel.propTypes = { 190 | activeIndex: PropTypes.number, 191 | onMove: PropTypes.func, 192 | onChange: PropTypes.func, 193 | className: PropTypes.string, 194 | } 195 | 196 | Carousel.defaultProps = { 197 | activeIndex: 0, 198 | } 199 | 200 | export default Carousel -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 本项目主要作为 react 项目开发的启动模板。使用 webpack v3、react v16、react-router v4,相较旧模板有较大的变化。 2 | 3 | > webpack 的具体使用及优化可参考我的博文 [webpack 使用总结](http://www.ferecord.com/webpack-summary.html) 。 4 | 5 | 8 | 9 | 10 | ## 主要依赖及工具 11 | 12 | ![https://github.com/facebook/react](https://img.shields.io/badge/react-v16.2.0-blue.svg) 13 | ![https://reacttraining.com/react-router](https://img.shields.io/badge/react--router-v4.2.0-blue.svg) 14 | ![https://github.com/reactjs/redux](https://img.shields.io/badge/redux-v3.6.1-blue.svg) 15 | 16 | ![https://github.com/webpack/webpack](https://img.shields.io/badge/webpack-v3.1.0-orange.svg) 17 | ![https://github.com/eslint/eslint](https://img.shields.io/badge/eslint-v4.13.1-orange.svg) 18 | ![https://github.com/babel/babel](https://img.shields.io/badge/babel--loader-v7.0.0-orange.svg) 19 | ![https://github.com/postcss/postcss](https://img.shields.io/badge/postcss--loader-v2.0.9-orange.svg) 20 | 21 | 22 | ## 功能 23 | #### 主要功能 24 | - [x] Hot Module Replacement 热加载 25 | - [x] ESLint 检测 26 | - [x] less、autoprefixer 27 | - [x] 业务组件使用 cssModule,通用组件使用 BEM 命名法 28 | - [x] 小于 8k 图片转为 base64 29 | - [x] svg 图标 30 | - [x] 文件压缩、添加 MD5 31 | - [x] ES6+, Fetch 32 | - [x] 使用 Redux DevTools ([安装浏览器插件](https://github.com/zalmoxisus/redux-devtools-extension)) 33 | 34 | #### 示例动图: 35 | demo 36 | 37 | 38 | 39 | 40 | 41 | ### 组件 42 | 本项目用到的纯组件主要如下: 43 | - 按钮 Button 44 | - 走马灯 Carousel 45 | - 弹框 Dialog 46 | - 图标 Icon 47 | - 无限滚动 Infinite-Scroll 48 | - 列表载入 List-Loading 49 | - 结果页 Result-Card 50 | - 分段器 Segmented 51 | - 菊花图 Spin 52 | - 步骤条 Steps 53 | - 标签页 Tabs 54 | - 倒计时 Time-Count 55 | - 轻提示 Toast 56 | 57 | 这些组件主要展示思路与方法,功能较基础,仅供参考。实际开发生产时请根据业务需求二次开发。 58 | 59 | 74 | 75 | 76 | 77 | 78 | ## 开始使用 79 | 本项目使用`yarn`作为包管理,也可替换为`npm`。两者的差异请参阅[从 npm 客户端迁移](https://yarnpkg.com/zh-Hans/docs/migrating-from-npm)。无论使用哪个都建议将安装源替换为[淘宝镜像](https://npm.taobao.org/)。 80 | 81 | ### 安装 82 | ``` 83 | git clone https://github.com/tumars/boilerplate-webpack-react-es6-cssModule 84 | cd boilerplate-webpack-react-es6-cssModule 85 | yarn install 86 | ``` 87 | 88 | ### 开发 89 | ``` 90 | yarn start 91 | ``` 92 | 93 | 访问 `http://localhost:3000/` 查看页面。 94 | 95 | ![iterm1](./img/iterm1.png) 96 | 97 | ### mock 接口数据 98 | 99 | 打开新命令行窗口,执行: 100 | ``` 101 | yarn run mock 102 | ``` 103 | 104 | 接口将会在本地 3003 端口启动。本项目的接口数据通过 node http 服务建立,配置文件在 /mock 文件夹内。 105 | 106 | ![iterm2](./img/iterm2.png) 107 | 108 | ### 打包 109 | Windows 用户使用: 110 | ``` 111 | yarn run build-win 112 | ``` 113 | 114 | Mac 用户使用: 115 | ``` 116 | yarn run build-mac 117 | ``` 118 | 119 | 文件将会在`./dist`文件夹内生成。 120 | 121 | 122 | 可以使用`anywhere`工具建立本地服务查看页面: 123 | ``` 124 | yarn global add anywhere 或 npm i -g anywhere 125 | cd ./dist 126 | anywhere 127 | ``` 128 | 页面会自动打开。 129 | 130 | 131 | ## 目录文件结构 132 | ![structure](./img/structure.png) 133 | 134 | 135 | ## 其他技术选择 136 | ### REM 与 VW、VH 137 | 之前使用 rem 布局,后来看了[再聊移动端页面的适配](https://www.w3cplus.com/css/vw-for-layout.html),决定使用 vw、vh 布局,配合 [postcss-px-to-viewport](https://github.com/evrone/postcss-px-to-viewport)和 [viewport-units-buggyfill](https://github.com/rodneyrehm/viewport-units-buggyfill) 能通过大部分机型的测试。 138 | 139 | > 经测试发现 viewport-units-buggyfill 在处理 base64 背景图片的 vw vh 时会导致图片出错,请注意。 140 | 141 | 当然使用 rem 布局还是最安全的,提供三个方案: 142 | 143 | 1. 参考本项目 v2 版本使用 js 控制 html 的 font-size 。 144 | 2. 使用 [postcss-pxtorem](https://github.com/cuth/postcss-pxtorem)。 145 | 3. 使用淘宝的 [lib-flexible](https://github.com/amfe/lib-flexible),这个是最推荐的。 146 | 147 | 148 | ### async 函数 149 | 本项目中的获取接口数据处的异步处理使用的是 async 函数,相比 Promise 直观、方便了许多。 150 | 151 | ### Decorator 152 | 本项目的一些组件使用了 Decorator(修饰器)的写法,例如 module/mo-carousel。以及 css module 也是以 Decorator 的方式使用。 153 | 154 | 155 | ## 性能优化 156 | ### Reselect 157 | Reselect 库可以创建可记忆的(Memoized)、可组合的 selector 函数。Reselect selectors 可以用来高效地计算 Redux store 里的衍生数据。 158 | 159 | 使用 Reselect 相当一个缓存,使容器组件传递的 props 输入值不变时输出值不变,以减少显示组件的重复渲染。 160 | 161 | 本项目的 layout/data-list-tabs 文件内使用了 Reselect。 162 | 163 | 更多 Reselect 的访问跟介绍请访问:[https://github.com/reactjs/reselect](https://github.com/reactjs/reselect) 164 | 165 | ### Immutable 166 | Immutable 是指数据不可变。使用 Immutable 在每次操作修改对象时都会生成一个新对象,而不修改原对象。这对 react 有这么两点意义: 167 | 168 | - 1. 保证了 state 的不可被直接更改。 169 | - 2. 便于 shouldComponentUpdate 对比前后对象是否相同。 170 | 171 | 从而一定的程度上的保证安全性和提提高性能。 172 | 173 | 这是 Immutable.js 的文档页面:[https://facebook.github.io/immutable-js/](https://facebook.github.io/immutable-js/) 174 | 175 | ### 其他优化 176 | 177 | #### 列表不要使用 index 作为 key 178 | React Diff 算法中 React 会借助元素的 key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。 179 | 如果使用 index 来作为 key,当数据更新仅仅是数组重新排序或在其中间位置插入新元素,那么视图元素都将重新渲染。 180 | 181 | #### 不要在 componentWillMount 中调用 setState 182 | 183 | 在 componentWillMount 没有意义,所有的 state 初始化应当在 this.state 中定义,而第一次 render 后改变 state 应当在 componentDidMount 中执行。 184 | 185 | 而且该时间周期钩子在 react v17 版本中将会被移除,查看:https://reactjs.org/docs/react-component.html#mounting 186 | 187 | #### 注意动画渲染优化,使用 chrome 调试性能 188 | 页面的图像渲染经过如下五个步骤 189 | 190 | - script (js 计算) 191 | - style (样式计算) 192 | - layout (布局) 193 | - paint (绘制) 194 | - composite (合成) 195 | 196 | 具体的讲解与优化请查看 chrome 开发者文档的说明: https://developers.google.com/web/fundamentals/performance/rendering/?hl=zh-cn 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | ## 主要版本变化 206 | 207 | #### v3 208 | - 主要使用 webpack v3 + react v16 + react-router v4 209 | - 更改文件结构,组件更新 210 | - Promise 改为 async await 211 | - rem 布局改为 vw、vh,使用 postcss-px-to-viewport 配置 212 | - 动画组件依赖更新至 react-transition-group 213 | 214 | 215 | #### [v2](https://github.com/tumars/boilerplate-webpack-react-es6-cssModule/tree/master/webpack1.x) 216 | 主要使用 webpack v2 + react v15 + react-router v4 217 | 218 | #### [v1](https://github.com/tumars/boilerplate-webpack-react-es6-cssModule/tree/master/webpack1.x) 219 | 主要使用 webpack v1 + react v13 + react-router v2 220 | 221 | 222 | ## 联系我 223 | 如有问题请提 issue,或通过以下方式联系到我: 224 | - 邮箱 menghui9898@gmail.com 225 | - 博客 [ferecord.com](http://www.ferecord.com/ "前端记录 ") 226 | - Twitter [@Tumars](https://twitter.com/Tumars) 227 | 228 | 欢迎指教交流 🙆‍ 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /dist/bundle.css: -------------------------------------------------------------------------------- 1 | .-transtion-slide-appear-3mbD1-,.-transtion-slide-enter-3Wn_c-{opacity:0;-webkit-transform:translate(100%);transform:translate(100%)}.-transtion-slide-appear-active-3aPhc-,.-transtion-slide-enter-active-1M9MQ-{opacity:1;-webkit-transform:translate(0);transform:translate(0);-webkit-transition:opacity .5s ease-in,-webkit-transform .5s ease;transition:opacity .5s ease-in,-webkit-transform .5s ease;transition:transform .5s ease,opacity .5s ease-in;transition:transform .5s ease,opacity .5s ease-in,-webkit-transform .5s ease}.-transtion-slide-exit-1x8zm-{opacity:1;-webkit-transform:translate(0);transform:translate(0)}.-transtion-slide-exit-active-2B8R9-{opacity:.5;-webkit-transform:translate(-100%);transform:translate(-100%);-webkit-transition:opacity .5s ease-out,-webkit-transform .5s ease;transition:opacity .5s ease-out,-webkit-transform .5s ease;transition:transform .5s ease,opacity .5s ease-out;transition:transform .5s ease,opacity .5s ease-out,-webkit-transform .5s ease}.-transtion-fade-appear-2uiCU-,.-transtion-fade-enter-3zFq9-{opacity:0}.-transtion-fade-appear-active-2HWoK-,.-transtion-fade-enter-active-102Wm-{-webkit-transition:opacity .3s linear;transition:opacity .3s linear;opacity:1}.-transtion-fade-exit-3qVa8-{-webkit-transition:opacity .2s linear;transition:opacity .2s linear;opacity:1}.-transtion-fade-exit-active-10lmh-{opacity:0}.-transtion-spread-enter-11ARV-{opacity:.01;-webkit-transform:scale(.8);transform:scale(.8)}.-transtion-spread-enter-active-2lGcz-{opacity:1;-webkit-transform:scale(1);transform:scale(1);-webkit-transition:opacity .1s linear,-webkit-transform .1s linear;transition:opacity .1s linear,-webkit-transform .1s linear;transition:transform .1s linear,opacity .1s linear;transition:transform .1s linear,opacity .1s linear,-webkit-transform .1s linear}.-transtion-spread-exit-1g8v8-{opacity:1;-webkit-transform:translate(0);transform:translate(0)}.-transtion-spread-exit-active-3fUCF-{opacity:.01;-webkit-transform:translateY(2rem) scale(.8);transform:translateY(2rem) scale(.8);-webkit-transition:opacity .2s linear,-webkit-transform .2s ease-in;transition:opacity .2s linear,-webkit-transform .2s ease-in;transition:transform .2s ease-in,opacity .2s linear;transition:transform .2s ease-in,opacity .2s linear,-webkit-transform .2s ease-in}.-transtion-pop-enter-2ZhJM-{opacity:.01;-webkit-transform:scale(.8);transform:scale(.8)}.-transtion-pop-enter-active-1tUbq-{opacity:1;-webkit-transform:scale(1);transform:scale(1);-webkit-transition:opacity .1s linear,-webkit-transform .1s linear;transition:opacity .1s linear,-webkit-transform .1s linear;transition:transform .1s linear,opacity .1s linear;transition:transform .1s linear,opacity .1s linear,-webkit-transform .1s linear}.-transtion-pop-exit-3D4r2-{opacity:1;-webkit-transform:translate(0);transform:translate(0)}.-transtion-pop-exit-active-3Jk1C-{opacity:.01;-webkit-transform:translateY(20vw) scale(.8);transform:translateY(20vw) scale(.8);-webkit-transition:opacity .2s linear,-webkit-transform .2s ease-in;transition:opacity .2s linear,-webkit-transform .2s ease-in;transition:transform .2s ease-in,opacity .2s linear;transition:transform .2s ease-in,opacity .2s linear,-webkit-transform .2s ease-in}#root{-webkit-transition:all .15s ease-in;transition:all .15s ease-in}.blur-set{height:100%;-webkit-filter:blur(.4vw) contrast(.8);filter:blur(.4vw) contrast(.8)}.blur-fix{-webkit-filter:none;filter:none;-webkit-transform:none;transform:none}.-modal-dyy-3IuGM-{z-index:10;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.4);-webkit-tap-highlight-color:transparent}.-modal-dyy-3IuGM-,.-modal-wrapper-2oL47-{position:fixed;-webkit-box-sizing:border-box;box-sizing:border-box}.-modal-wrapper-2oL47-{z-index:20;top:13.33333vw;left:50%;right:0;width:93.33333vw;margin-left:-46.66667vw}.-modal-default-1gamp-{width:100%;font-size:4.93333vw;line-height:1.5;text-align:center;line-height:3;-webkit-box-shadow:rgba(0,0,0,.25) 0 3.73333vw 12vw,rgba(0,0,0,.22) 0 2.66667vw 4.8vw;box-shadow:0 3.73333vw 12vw rgba(0,0,0,.25),0 2.66667vw 4.8vw rgba(0,0,0,.22);max-height:106.66667vw;overflow-x:hidden;overflow-y:scroll;-webkit-overflow-scrolling:touch;border-radius:1.06667vw;color:#000;background:#fff}.-modal-default-1gamp-::-webkit-scrollbar{display:none}.-modal-default-big-yVwxe-{z-index:20;position:fixed;top:0;left:50%;width:100vw;margin-left:-50vw}.-modal-default-big-yVwxe- .-modal-close-1wPj7-{top:3vw;right:3vw}.-modal-close-1wPj7-{position:absolute;z-index:30;right:0;top:0;width:8.8vw;height:8.8vw;line-height:11.9vw;font-size:5vw;font-weight:700;text-align:center}.-modal-close-1wPj7- svg{width:6.3vw;height:6.3vw;fill:#999}.-button-btn-1gv-W-{width:77.33333vw;margin:5.33333vw auto;line-height:2.8;font-size:4vw;text-align:center;color:#fff;background:#2196f3;border-radius:.53333vw;-webkit-box-shadow:0 0 .26667vw 0 rgba(0,0,0,.12),0 .26667vw .26667vw 0 rgba(0,0,0,.24);box-shadow:0 0 .26667vw 0 rgba(0,0,0,.12),0 .26667vw .26667vw 0 rgba(0,0,0,.24)}.-button-icon-B8XbU-{display:inline-block;vertical-align:bottom;margin-left:1.86667vw;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAA7CAMAAADRoFNUAAAAn1BMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8Kd3m4AAAANHRSTlMA2fHs+xIM6Muk3oJV7saXfmNcR9KvOzgqBfS7p593cmpOS0NALxwCwLc9JiIWB6KSjG40/cttGgAAAdFJREFUSMe9lNd6ozAQRodmmsEYcMGOe4vjkmSj93+21egjHhksoZvdcyX9HIQ06BuQ+IjPlm376SK/gY48YA/c8EfpTVP2zKBSLEjLsRrr+kocMkGQ7a/HS//bYshs2hYLhrzv6E0HA6fXFD3xxdVTNMdo3jTPmG4a4fxF+IPZGpr4PI3giRGPUmixZZwdSPRwlwdogxUIQWLDg5KOMvGgZskf+CcgYh7Ej/LYLPnd8oRx5Jou+Lz/e0cYUkjFKxoHGlNpOAEIKl+sQbzx+b4eR8J0KkDuTsP84vMhHZeT9AA5uuJrxJrPF2TS/8aCuh4QB1zl9MIc1FsmZnhv2+YVh0uQycMwHJxa5m7Ecw8UkEn8b9MG2GRbEzM64V14MzDfvxiSARSrQmsmTFDCJy6tNIk0Zsiy27Td+g5us77GJKIzbr3qMImRscnGxubK2Iz/kXlY5pfaLNVmBrAXRdgDEulO5CX1v8uxiasZwvoxtgKmoQ/fzIwJ7MxE54YNoAPqaoGJKS70vTQwM0Aqq0Oj7uhFneYBDNXkDjU3X2+m8OCoVwcgqY7O/AMSl5nG/ACZqUKldk+qqzJLaDBRmZ/QUt2OAxHjrrZAFL7dxsd2/hcmLfOXmijBPAAAAABJRU5ErkJggg==);background-size:100% 100%;width:5.46667vw;height:7.86667vw}.-user-info-wrap-dAi6Y-{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2.66667vw 4vw 13.33333vw;width:100%;background:#fff;-webkit-box-shadow:0 0 .26667vw 0 rgba(0,0,0,.12),0 .26667vw .26667vw 0 rgba(0,0,0,.24);box-shadow:0 0 .26667vw 0 rgba(0,0,0,.12),0 .26667vw .26667vw 0 rgba(0,0,0,.24)}.-user-info-title-1WPnP-{width:100%;padding:1.2vw 0 0;margin-bottom:1.6vw;font-size:4.26667vw}.-user-info-tip-3445r-{line-height:2;font-size:3.46667vw;color:#212121}.-user-info-logo-edcg--{position:absolute;display:inline-block;bottom:1.33333vw;right:1.33333vw;width:24vw;height:14vw;background:#ffc400;background:-webkit-gradient(linear,left top,right bottom,color-stop(0,#ffc400),color-stop(65%,#ffc400),color-stop(65%,#ffeb3b),to(#ffeb3b));background:linear-gradient(to bottom right,#ffc400 0,#ffc400 65%,#ffeb3b 0,#ffeb3b)}.-home-wrap-3UMVm-{-webkit-box-sizing:border-box;box-sizing:border-box;padding:2.66667vw}.-home-tip-1cSD5-{font-size:3.2vw;color:#666;text-align:center}.-home-hello-n7ycw-{margin:8vw auto 10.66667vw;padding:.8vw 0;text-align:center;border-bottom:1px solid #0e90d2}.-home-button-2nCTt-{width:53.33333vw;margin:5.33333vw auto;line-height:2.3;color:#fff;text-align:center;border-radius:.66667vw;background:#0e90d2}.-list-list-2LkSt-{padding-top:2.66667vw}.-list-single-3XcdY-{text-align:left;line-height:2.5}.-list-single-3XcdY-:nth-child(odd){background-color:#eee}.-list-single-3XcdY- div{display:inline-block;padding:0 5.33333vw;text-align:center}.-listerr-content-pxNQ6-{width:90%;margin:0 auto;position:relative}.-listerr-errtip-2nQiN-{position:relative;margin-top:2.66667vw}.-listerr-errtip-2nQiN- div{position:relative;display:block;height:2.66667vw;margin:0 0 2.4vw;background:#ddd}.-listerr-errtip-2nQiN- div:before{content:"";opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;background:#eee;-webkit-animation:-listerr-progressactive-1BClP- 1s infinite ease-in-out;animation:-listerr-progressactive-1BClP- 1s infinite ease-in-out}.-listerr-errtip-2nQiN- div:first-child{width:30%;-webkit-animation:-listerr-ball-spin-2pxoQ- .5s .1s infinite ease-in-out;animation:-listerr-ball-spin-2pxoQ- .5s .1s infinite ease-in-out}.-listerr-errtip-2nQiN- div:nth-child(2){width:20%;margin-bottom:2.66667vw}.-listerr-errtip-2nQiN- div:nth-child(2),.-listerr-errtip-2nQiN- div:nth-child(3){-webkit-animation:-listerr-ball-spin-2pxoQ- .5s .3s infinite ease-in-out;animation:-listerr-ball-spin-2pxoQ- .5s .3s infinite ease-in-out}.-listerr-errtip-2nQiN- div:nth-child(3){width:60%}.-listerr-errtip-2nQiN- div:nth-child(4){width:80%}.-listerr-errtip-2nQiN- div:nth-child(4),.-listerr-errtip-2nQiN- div:nth-child(5){-webkit-animation:-listerr-ball-spin-2pxoQ- .5s .1s infinite ease-in-out;animation:-listerr-ball-spin-2pxoQ- .5s .1s infinite ease-in-out}.-listerr-errtip-2nQiN- div:nth-child(5){width:50%}.-listerr-word-3-vMT-{position:absolute;top:0;right:0;color:#333;font-size:1.73333vw;font-weight:700;-webkit-animation:-listerr-fadein-1CDpe- .8s infinite;animation:-listerr-fadein-1CDpe- .8s infinite}@-webkit-keyframes -listerr-fadein-1CDpe-{0%{opacity:1}50%{opacity:.7}to{opacity:1}}@keyframes -listerr-fadein-1CDpe-{0%{opacity:1}50%{opacity:.7}to{opacity:1}}@-webkit-keyframes -listerr-progressactive-1BClP-{0%{opacity:.8;width:0}to{opacity:0;width:100%}}@keyframes -listerr-progressactive-1BClP-{0%{opacity:.8;width:0}to{opacity:0;width:100%}}.-carousel-wrap-2FV_D-{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-sizing:content-box;box-sizing:content-box}.-carousel-slide-2rTec-,.-carousel-wrap-2FV_D-{position:relative;-webkit-transition-property:-webkit-transform;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform}.-carousel-slide-2rTec-{-ms-flex-negative:0;flex-shrink:0;width:100%;height:100%}.-carousel-pagination-wrap-3lF9P-{position:relative;overflow:hidden}.-carousel-pagination-1wS3q-{position:absolute;bottom:1.33333vw;width:100%;height:3.33333vw;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0;line-height:1;text-align:center;overflow:hidden}.-carousel-bullet-1zSis-{background:#d4a143}.-carousel-bullet-1zSis-,.-carousel-bullet-active-3_OnR-{display:inline-block;width:2.66667vw;height:2.66667vw;margin:0 1.33333vw;padding:0;border-radius:50%}.-carousel-bullet-active-3_OnR-{background:#d4a143;background:#a32229}.tj-tabs-content{position:relative;width:100%;overflow:hidden}.tj-tabs-nav-wrap{display:block;padding-left:0;margin:0;-webkit-transition:-webkit-transform .5s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .5s cubic-bezier(.645,.045,.355,1);transition:transform .5s cubic-bezier(.645,.045,.355,1);transition:transform .5s cubic-bezier(.645,.045,.355,1),-webkit-transform .5s cubic-bezier(.645,.045,.355,1);overflow:hidden}.tj-tabs-nav,.tj-tabs-nav-wrap{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;list-style:none}.tj-tabs-nav{display:inline-block;height:8vw;line-height:8vw;font-size:4.26667vw;color:#000;text-align:center;-webkit-tap-highlight-color:rgba(0,0,0,0);cursor:pointer}.tj-tabs-nav-active{color:#fba112}.tj-tabs-activebar{position:absolute;bottom:0;left:0;height:.53333vw;background-color:#fba112;z-index:1;will-change:margin-left;-webkit-transition:margin-left .2s cubic-bezier(.645,.045,.355,1);transition:margin-left .2s cubic-bezier(.645,.045,.355,1);list-style:none}.tj-tabs-pane-wrap{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:100%;margin-top:4vw;will-change:transform}.tj-tabs-pane-wrap__ani{-webkit-transition:-webkit-transform .2s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .2s cubic-bezier(.645,.045,.355,1);transition:transform .2s cubic-bezier(.645,.045,.355,1);transition:transform .2s cubic-bezier(.645,.045,.355,1),-webkit-transform .2s cubic-bezier(.645,.045,.355,1)}.tj-tabs-pane{width:100%;-ms-flex-negative:0;flex-shrink:0;-webkit-transition:opacity .45s;transition:opacity .45s;opacity:1}.tj-tabs-pane-inactive{opacity:0;height:0;padding:0}.-list-tabs-wrap-2YdRi-{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2.66667vw 2.66667vw 13.33333vw;width:100%;background:#fff;-webkit-box-shadow:0 0 .26667vw 0 rgba(0,0,0,.12),0 .26667vw .26667vw 0 rgba(0,0,0,.24);box-shadow:0 0 .26667vw 0 rgba(0,0,0,.12),0 .26667vw .26667vw 0 rgba(0,0,0,.24)}.-list-tabs-button-NeYrU-{width:40vw;margin:5.33333vw auto;line-height:2;color:#fff;text-align:center;border-radius:1.33333vw;background:#0e90d2}.-list-tabs-panel-xK7tm-{position:relative}.-list-tabs-pagina-2Avbv-{display:inline-block;opacity:.8;font-size:.8em}.-list-tabs-more-3a0KU-{margin-top:1.33333vw;color:#0e90d2}.-list-wrap-3ma5A-{-webkit-box-sizing:border-box;box-sizing:border-box;padding:2.66667vw}.-list-button-PuCpH-{width:40vw;margin:5.33333vw auto;line-height:2;color:#fff;text-align:center;border-radius:1.33333vw;background:#0e90d2}.-list-pagina-2jZhg-{margin-top:2.66667vw;color:#666}.-list-more-3e7vs-{margin-top:1.33333vw;color:#0e90d2}.-router-fill-1w_jQ-{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;max-width:100vw;margin:0 auto;font-size:4.26667vw}.-router-notfund-2NkuC-{padding-top:6.66667vw;text-align:center;font-size:3.2vw}blockquote,body,button,dd,dl,dt,fieldset,form,h1,h2,h3,h4,h5,h6,html,input,legend,li,ol,p,td,textarea,th,ul{margin:0;padding:0;border:0;vertical-align:baseline}html{line-height:1}table{border-collapse:collapse;border-spacing:0}ol,ul{list-style:none}img{border:0}b,strong{font-weight:700}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}body{line-height:1.5;font-family:Helvetica,Tahoma,Arial,Microsoft YaHei,\\5FAE\8F6F\96C5\9ED1,Heiti,\\9ED1\4F53,SimSun,\\5B8B\4F53,sans-serif;background:#eee} --------------------------------------------------------------------------------