├── .eslintignore ├── mock ├── del.json ├── sample.json ├── buy.json ├── dynamic.js ├── server.conf ├── view.js └── detail.js ├── .gitignore ├── static ├── img │ ├── i_access.png │ ├── i_agree.png │ ├── i_bottom.png │ ├── i_city.png │ ├── i_city3x.png │ ├── i_period.png │ ├── i_rule.png │ ├── i_rule3x.png │ ├── i-loading.png │ ├── i_access3x.png │ ├── i_agree3x.png │ ├── i_bottom3x.png │ ├── i_discount.png │ ├── i_hotsell.png │ ├── i_location.png │ ├── i_period3x.png │ ├── i_selected.png │ ├── i_unagree.png │ ├── i_usercard.png │ ├── pay-failed.png │ ├── bear-nocard.png │ ├── bear-nocard3x.png │ ├── i_discount3x.png │ ├── i_hotsell3x.png │ ├── i_location3x.png │ ├── i_right_gray.png │ ├── i_right_white.png │ ├── i_selected3x.png │ ├── i_unagree3x.png │ ├── i_unselected.png │ ├── i_usercard3x.png │ ├── pay-success.png │ ├── pay-success3x.png │ ├── bear-novipcard.png │ ├── i_right_gray3x.png │ ├── i_right_white3x.png │ ├── i_unselected3x.png │ └── bear-novipcard3x.png ├── github │ └── fis3+react+redux.gif ├── lib │ ├── fastclick-conf.js │ ├── global.js │ ├── wmflex.js │ └── mod.js └── styles │ ├── est │ ├── all.less │ ├── variables.less │ ├── effects.less │ ├── clockhand.less │ ├── reset.less │ ├── grid.less │ ├── shapes.less │ └── layout.less │ ├── common.less │ ├── public.less │ └── animate.less ├── app ├── components │ ├── NotFound │ │ ├── img │ │ │ └── page-404.png │ │ ├── index.jsx │ │ └── style.less │ ├── SwipeCard │ │ ├── img │ │ │ ├── i_right.png │ │ │ ├── bg-expired.png │ │ │ ├── bg-valid.png │ │ │ ├── bg-valid3x.png │ │ │ ├── i_location.png │ │ │ └── bg-expired3x.png │ │ ├── ValidCard │ │ │ └── index.jsx │ │ ├── UnenforcedCard │ │ │ └── index.jsx │ │ ├── UselessCard │ │ │ └── index.jsx │ │ ├── ExpiredCard │ │ │ └── index.jsx │ │ ├── RenewCard │ │ │ └── index.jsx │ │ ├── index.jsx │ │ └── index.less │ ├── TitleBar │ │ ├── img │ │ │ ├── tip-temp.png │ │ │ ├── tip_opencity.png │ │ │ ├── tip_nocitycard.png │ │ │ ├── tip_nousercard.png │ │ │ └── tip_opencity3x.png │ │ ├── index.jsx │ │ └── index.less │ ├── Header │ │ ├── style.less │ │ └── index.jsx │ ├── CardList │ │ ├── MessageTip │ │ │ ├── index.less │ │ │ └── index.jsx │ │ ├── index.jsx │ │ └── SellCard │ │ │ ├── index.less │ │ │ └── index.jsx │ ├── RadioList │ │ ├── index.less │ │ ├── RadioItem │ │ │ ├── index.jsx │ │ │ └── index.less │ │ └── index.jsx │ ├── Access │ │ ├── index.less │ │ └── index.jsx │ ├── Agree │ │ ├── index.jsx │ │ └── index.less │ ├── ImgTip │ │ ├── index.less │ │ └── index.jsx │ ├── DialogModal │ │ ├── index.less │ │ └── index.jsx │ └── Loading │ │ ├── index.jsx │ │ └── index.less ├── util │ ├── authService.js │ ├── cookie.js │ ├── localStorage.js │ ├── page.js │ └── util.js ├── actions │ ├── globalVal.js │ └── card.js ├── reducers │ ├── index.js │ ├── baseinfo.js │ ├── globalVal.js │ ├── confirm.js │ ├── card.js │ └── detail.js ├── containers │ ├── 404.jsx │ ├── Detail │ │ ├── AccessInfo │ │ │ ├── index.less │ │ │ └── index.jsx │ │ ├── DiscountList │ │ │ ├── DiscountItem │ │ │ │ └── index.jsx │ │ │ ├── index.less │ │ │ └── index.jsx │ │ ├── index.less │ │ └── index.jsx │ ├── Rule │ │ ├── index.less │ │ └── index.jsx │ ├── App.jsx │ ├── Home │ │ ├── Onsell │ │ │ └── index.jsx │ │ ├── Mime │ │ │ └── index.jsx │ │ ├── index.less │ │ └── index.jsx │ └── Confirm │ │ ├── index.less │ │ └── index.jsx ├── store │ └── configureStore.js ├── constants │ └── types.js ├── index.jsx ├── fetch │ ├── promiseMiddleware.js │ └── request.js └── router.jsx ├── index.html ├── .eslintrc.json ├── package.json ├── README.md └── fis-conf.js /.eslintignore: -------------------------------------------------------------------------------- 1 | **/bdNativeShare.js 2 | -------------------------------------------------------------------------------- /mock/del.json: -------------------------------------------------------------------------------- 1 | { 2 | "error_no": 0, 3 | "error_msg": "", 4 | "result": 1 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | release/* 3 | issue.txt 4 | npm-debug.log 5 | **/.DS_Store 6 | 7 | -------------------------------------------------------------------------------- /static/img/i_access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_access.png -------------------------------------------------------------------------------- /static/img/i_agree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_agree.png -------------------------------------------------------------------------------- /static/img/i_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_bottom.png -------------------------------------------------------------------------------- /static/img/i_city.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_city.png -------------------------------------------------------------------------------- /static/img/i_city3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_city3x.png -------------------------------------------------------------------------------- /static/img/i_period.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_period.png -------------------------------------------------------------------------------- /static/img/i_rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_rule.png -------------------------------------------------------------------------------- /static/img/i_rule3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_rule3x.png -------------------------------------------------------------------------------- /static/img/i-loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i-loading.png -------------------------------------------------------------------------------- /static/img/i_access3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_access3x.png -------------------------------------------------------------------------------- /static/img/i_agree3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_agree3x.png -------------------------------------------------------------------------------- /static/img/i_bottom3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_bottom3x.png -------------------------------------------------------------------------------- /static/img/i_discount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_discount.png -------------------------------------------------------------------------------- /static/img/i_hotsell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_hotsell.png -------------------------------------------------------------------------------- /static/img/i_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_location.png -------------------------------------------------------------------------------- /static/img/i_period3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_period3x.png -------------------------------------------------------------------------------- /static/img/i_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_selected.png -------------------------------------------------------------------------------- /static/img/i_unagree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_unagree.png -------------------------------------------------------------------------------- /static/img/i_usercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_usercard.png -------------------------------------------------------------------------------- /static/img/pay-failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/pay-failed.png -------------------------------------------------------------------------------- /static/img/bear-nocard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/bear-nocard.png -------------------------------------------------------------------------------- /static/img/bear-nocard3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/bear-nocard3x.png -------------------------------------------------------------------------------- /static/img/i_discount3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_discount3x.png -------------------------------------------------------------------------------- /static/img/i_hotsell3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_hotsell3x.png -------------------------------------------------------------------------------- /static/img/i_location3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_location3x.png -------------------------------------------------------------------------------- /static/img/i_right_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_right_gray.png -------------------------------------------------------------------------------- /static/img/i_right_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_right_white.png -------------------------------------------------------------------------------- /static/img/i_selected3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_selected3x.png -------------------------------------------------------------------------------- /static/img/i_unagree3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_unagree3x.png -------------------------------------------------------------------------------- /static/img/i_unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_unselected.png -------------------------------------------------------------------------------- /static/img/i_usercard3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_usercard3x.png -------------------------------------------------------------------------------- /static/img/pay-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/pay-success.png -------------------------------------------------------------------------------- /static/img/pay-success3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/pay-success3x.png -------------------------------------------------------------------------------- /static/img/bear-novipcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/bear-novipcard.png -------------------------------------------------------------------------------- /static/img/i_right_gray3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_right_gray3x.png -------------------------------------------------------------------------------- /static/img/i_right_white3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_right_white3x.png -------------------------------------------------------------------------------- /static/img/i_unselected3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/i_unselected3x.png -------------------------------------------------------------------------------- /static/img/bear-novipcard3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/img/bear-novipcard3x.png -------------------------------------------------------------------------------- /static/github/fis3+react+redux.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/static/github/fis3+react+redux.gif -------------------------------------------------------------------------------- /app/components/NotFound/img/page-404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/NotFound/img/page-404.png -------------------------------------------------------------------------------- /app/components/SwipeCard/img/i_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/SwipeCard/img/i_right.png -------------------------------------------------------------------------------- /app/components/TitleBar/img/tip-temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/TitleBar/img/tip-temp.png -------------------------------------------------------------------------------- /app/components/SwipeCard/img/bg-expired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/SwipeCard/img/bg-expired.png -------------------------------------------------------------------------------- /app/components/SwipeCard/img/bg-valid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/SwipeCard/img/bg-valid.png -------------------------------------------------------------------------------- /app/components/SwipeCard/img/bg-valid3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/SwipeCard/img/bg-valid3x.png -------------------------------------------------------------------------------- /app/components/SwipeCard/img/i_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/SwipeCard/img/i_location.png -------------------------------------------------------------------------------- /app/components/SwipeCard/img/bg-expired3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/SwipeCard/img/bg-expired3x.png -------------------------------------------------------------------------------- /app/components/TitleBar/img/tip_opencity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/TitleBar/img/tip_opencity.png -------------------------------------------------------------------------------- /app/components/TitleBar/img/tip_nocitycard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/TitleBar/img/tip_nocitycard.png -------------------------------------------------------------------------------- /app/components/TitleBar/img/tip_nousercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/TitleBar/img/tip_nousercard.png -------------------------------------------------------------------------------- /app/components/TitleBar/img/tip_opencity3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/younth/react-router-redux-mobile-spa/HEAD/app/components/TitleBar/img/tip_opencity3x.png -------------------------------------------------------------------------------- /mock/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": 0, 3 | "message": "ok", 4 | "data": { 5 | "username": "younth", 6 | "uid": 1, 7 | "age": 25, 8 | "company": "waimai" 9 | } 10 | } -------------------------------------------------------------------------------- /mock/buy.json: -------------------------------------------------------------------------------- 1 | { 2 | "error_no": 0, 3 | "error_msg": "", 4 | "result": { 5 | "pay_params": "", 6 | "pay_type": "1", 7 | "order_id": "" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /mock/dynamic.js: -------------------------------------------------------------------------------- 1 | module.exports = function(req, res, next) { 2 | 3 | res.write('Hello world '); 4 | 5 | // set custom header. 6 | // res.setHeader('xxxx', 'xxx'); 7 | 8 | res.end('The time is ' + Date.now()); 9 | }; -------------------------------------------------------------------------------- /static/lib/fastclick-conf.js: -------------------------------------------------------------------------------- 1 | // 配置fastclick 2 | if ('addEventListener' in document) { 3 | document.addEventListener('DOMContentLoaded', function() { 4 | FastClick.attach(document.body); 5 | }, false); 6 | } 7 | -------------------------------------------------------------------------------- /app/components/Header/style.less: -------------------------------------------------------------------------------- 1 | header{ 2 | min-width: 320px; 3 | height: 48px; 4 | background-color: #3e98f0; 5 | text-align: center; 6 | font-weight: normal; 7 | color: #fff; 8 | overflow: hidden; 9 | } -------------------------------------------------------------------------------- /app/components/CardList/MessageTip/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../../static/styles/public.less'; 2 | @shallow-gray: #808080; // 文字浅灰色(活动规则) 3 | 4 | .sellcard-item.tip { 5 | border-bottom: none; 6 | font-size: 28px; 7 | color: @shallow-gray; 8 | } 9 | -------------------------------------------------------------------------------- /app/util/authService.js: -------------------------------------------------------------------------------- 1 | // auth 信息 2 | 3 | export function redirectToBack(nextState, replace) { 4 | //已经登录则不进入 5 | replace(null, '/') 6 | } 7 | export function redirectToLogin(nextState, replace) { 8 | // redirect a new url 9 | console.log('enter'); 10 | // replace(null, '/') 11 | } -------------------------------------------------------------------------------- /static/styles/est/all.less: -------------------------------------------------------------------------------- 1 | @import "./variables.less"; 2 | @import "./compatibility.less"; 3 | @import "./grid.less"; 4 | @import "./layout.less"; 5 | @import "./normalize.less"; 6 | @import "./reset.less"; 7 | @import "./typography.less"; 8 | @import "./util.less"; 9 | @import "./effects.less"; 10 | @import "./shapes.less"; 11 | @import "./clockhand.less"; 12 | -------------------------------------------------------------------------------- /app/components/RadioList/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | @black: #333; 3 | @white: #fff; 4 | @font-gray: #808080; 5 | 6 | .radio-list { 7 | background-color: @white; 8 | width: 720px; 9 | margin: 50px auto; 10 | border-radius: 25px; 11 | .radio-wrap { 12 | width: 650px; 13 | margin-left: 60px; 14 | } 15 | } -------------------------------------------------------------------------------- /app/actions/globalVal.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../constants/types' 2 | 3 | // 一. 公共接口数据 4 | 5 | // 更新address信息,自动触发store 6 | export const addressUpdate = data => { 7 | return { 8 | type: actionTypes.ADRESS_UPDATE, 9 | data 10 | } 11 | } 12 | 13 | // 二. 首页与提单页 提单页成功支付后调到首页 传递状态值 14 | export const savePayResult = data => { 15 | return { 16 | type: actionTypes.SAVE_PAYRESULT, 17 | data 18 | } 19 | } -------------------------------------------------------------------------------- /app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | import globalVal from './globalVal' 4 | import baseInfo from './baseInfo' 5 | import card from './card' 6 | import confirm from './confirm' 7 | import detail from './detail' 8 | 9 | // 聚合reducer 10 | const rootReducer = combineReducers({ 11 | card, 12 | globalVal, 13 | baseInfo, 14 | confirm, 15 | detail 16 | }) 17 | 18 | export default rootReducer -------------------------------------------------------------------------------- /app/reducers/baseinfo.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../constants/types' 2 | 3 | const initialState = { 4 | payResult: '' 5 | } 6 | 7 | export default function baseInfo(state = initialState, action) { 8 | switch (action.type) { 9 | case actionTypes.SAVE_PAYRESULT: 10 | return { 11 | ...state, 12 | payResult: action.payResult || action.data.payResult 13 | } 14 | default: 15 | return state 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /app/util/cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本地储存封装 3 | * author: younthxg@gmail.com 4 | */ 5 | 6 | // 操作 cookie 7 | 8 | import cookie from 'react-cookie' 9 | import { CookieDomain } from '../config' 10 | 11 | let cookieConfig = {} 12 | if(CookieDomain !== ''){ 13 | cookieConfig = { domain: '' } 14 | } 15 | 16 | export default { 17 | saveCookie(name, value) { 18 | cookie.save(name, value, cookieConfig) 19 | }, 20 | 21 | getCookie(name) { 22 | return cookie.load(name) 23 | }, 24 | 25 | removeCookie(name) { 26 | cookie.remove(name, cookieConfig) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mock/server.conf: -------------------------------------------------------------------------------- 1 | # 动态假数据 2 | #rewrite ^\/api\/dynamic\/time$ /mock/dynamic.js 3 | 4 | # 静态假数据 5 | #rewrite ^\/api\/user$ /mock/sample.json 6 | 7 | # 首页 获取用户卡片及城市卡片 8 | rewrite ^\/wmall\/privilege\/center$ /mock/center.js 9 | # 提单页 获取提单页卡片详情 10 | rewrite ^\/wmall\/privilege\/view$ /mock/view.js 11 | # 使用详情页 获取卡片使用详情 12 | rewrite ^\/wmall\/privilege\/promotiondetail$ /mock/detail.js 13 | # 提单页 购买卡片接口 14 | rewrite ^\/wmall\/privilege\/buy$ /mock/buy.json 15 | # 首页 删除过期卡接口 16 | rewrite ^\/wmall\/privilege\/del$ /mock/del.json 17 | 18 | #proxy ^\/wmall\/privilege\/(.*)$ http://10.19.161.92:8059/wmall/privilege/$1 -------------------------------------------------------------------------------- /static/lib/global.js: -------------------------------------------------------------------------------- 1 | (function(root) { 2 | root.isWMApp = (/wmapp/.test(navigator.userAgent.toLowerCase())) ? true : false 3 | 4 | // wmapp ready 5 | root.WMAppReady = function(readyCallback) { 6 | if (readyCallback && typeof readyCallback == 'function') { 7 | if (window.WMApp && typeof window.WMApp == 'object') { 8 | readyCallback() 9 | } else { 10 | document.addEventListener('WMAppReady', function() { 11 | readyCallback() 12 | }, false) 13 | } 14 | } 15 | } 16 | 17 | })(window) -------------------------------------------------------------------------------- /app/components/CardList/MessageTip/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页热卖商品卡片组件 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | 7 | import './index.less' 8 | 9 | // 组装 MessageTip 组件 10 | class MessageTip extends Component { 11 | constructor(props, context) { 12 | super(props, context); 13 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 14 | } 15 | render() { 16 | return ( 17 |
{this.props.tip}
18 | ) 19 | } 20 | } 21 | 22 | export default MessageTip 23 | -------------------------------------------------------------------------------- /app/containers/404.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as PureRenderMixin from 'react-addons-pure-render-mixin' 3 | 4 | import Header from '../components/Header' 5 | import NotFound from '../components/NotFound' 6 | 7 | class NotFoundPage extends React.Component { 8 | constructor(props, context) { 9 | super(props, context); 10 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 11 | } 12 | render() { 13 | return ( 14 |
15 |
16 | 17 |
18 | ) 19 | } 20 | } 21 | 22 | export default NotFoundPage 23 | -------------------------------------------------------------------------------- /app/components/Access/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | @black: #333; 3 | .setBG(@url, @w, @h) { 4 | background: url(@url) no-repeat; 5 | width: @w; 6 | height: @h; 7 | background-size: 100% 100%; 8 | } 9 | 10 | .access-wrap { 11 | display: flex; 12 | width: 100%; 13 | height: 130px; 14 | color: @black; 15 | font-size: 24px; 16 | text-align: center; 17 | align-items: center; 18 | .access-item { 19 | flex: 1; 20 | .info { 21 | margin-bottom: 16px; 22 | .num { 23 | font-size: 59px; 24 | margin-right: 6px; 25 | } 26 | } 27 | .desc {} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/containers/Detail/AccessInfo/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../../static/styles/public.less'; 2 | @gray2: #a9a9a9; 3 | .point-before(@size: 6px) { 4 | &:before { 5 | content: ''; 6 | width: @size; 7 | height: @size; 8 | border-radius: @size; 9 | display: block; 10 | margin-right: 16px; 11 | background: @gray2; 12 | } 13 | } 14 | 15 | .access-rule { 16 | width: 660px; 17 | margin: 0 auto; 18 | margin-top: 15px; 19 | p { 20 | font-size: 24px; 21 | display: flex; 22 | align-items: center; 23 | .point-before(); 24 | [data-dpr='3'] & { 25 | .point-before(9px); 26 | } 27 | color: @gray2; 28 | margin-bottom: 30px; 29 | width: 50%; 30 | float: left; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware } from 'redux' 2 | import thunkMiddleware from 'redux-thunk' 3 | import rootReducer from '../reducers' 4 | 5 | import promiseMiddleware from '../fetch/promiseMiddleware' 6 | 7 | export default function configureStore(initialState, history) { 8 | 9 | let middleware = [thunkMiddleware, promiseMiddleware]; 10 | // 不同环境下构造不同的 store构成函数 11 | let finalCreateStore; 12 | 13 | // 触发 redux-devtools 14 | finalCreateStore = window.devToolsExtension ? 15 | compose(applyMiddleware(...middleware), window.devToolsExtension()) : 16 | compose(applyMiddleware(...middleware)) 17 | // 创建 store,第二个参数是可选的, 用于设置 state 初始状态 18 | const store = finalCreateStore(createStore)(rootReducer, initialState) 19 | 20 | return store 21 | } -------------------------------------------------------------------------------- /app/containers/Detail/DiscountList/DiscountItem/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 详情页订单展示组件 DiscountItem 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | 7 | // 组装 DiscountItem 组件 8 | class DiscountItem extends Component { 9 | constructor(props, context) { 10 | super(props, context) 11 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 12 | } 13 | render() { 14 | let data = this.props.data 15 | return ( 16 |
17 |
{data.shop_name}
18 |
节省配送费 ¥{data.save_money}
19 |
20 | ) 21 | } 22 | } 23 | 24 | export default DiscountItem 25 | -------------------------------------------------------------------------------- /app/components/Header/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react' 2 | import * as PureRenderMixin from 'react-addons-pure-render-mixin' 3 | import { bindActionCreators } from 'redux' 4 | import { connect } from 'react-redux' 5 | import { hashHistory, Link } from 'react-router' 6 | 7 | import './style.less' 8 | 9 | class Header extends Component { 10 | constructor(props, context) { 11 | super(props, context); 12 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 13 | } 14 | render() { 15 | 16 | return ( 17 |
18 | header 19 |
20 | ) 21 | } 22 | 23 | toBack () { 24 | history.back(); 25 | } 26 | } 27 | 28 | export default Header 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | fis3+react+redux单页面应用实践 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/reducers/globalVal.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../constants/types' 2 | 3 | const initialState = { 4 | lat: '', 5 | lng: '', 6 | city_id: '', 7 | from: '', 8 | app_ver: '' 9 | } 10 | 11 | export default function globalVal(state = initialState, action) { 12 | switch (action.type) { 13 | case actionTypes.ADRESS_UPDATE: 14 | return { 15 | ...state, 16 | lat: action.lat || action.data.lat || state.lat, 17 | lng: action.lng || action.data.lng || state.lng, 18 | city_id: action.city_id || action.data.city_id || state.city_id, 19 | from: action.from || action.data.from || state.from, 20 | app_ver: action.app_ver || action.data.app_ver || state.app_ver 21 | } 22 | default: 23 | return state 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/containers/Detail/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | @gray: #f6f6f6; // 提单页背景 3 | @red: #ff2d4b; // 外卖红 4 | @gray2: #a9a9a9; 5 | @black: #333; 6 | @white: #fff; 7 | @white2: #fffefe; 8 | .setBG(@url, @w, @h) { 9 | background: url(@url) no-repeat; 10 | width: @w; 11 | height: @h; 12 | background-size: 100% 100%; 13 | } 14 | 15 | .detail-page { 16 | // background-color: @gray; 17 | .titlebar-wrap { 18 | margin: 0; 19 | padding: 35px 0 35px 30px; 20 | clear: left; 21 | } 22 | .access-wrap { 23 | height: auto; 24 | padding: 90px 0 45px 0; 25 | font-size: 24px; 26 | .access-item { 27 | .info { 28 | color: @red; 29 | .num { 30 | font-size: 60px; 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/containers/Rule/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | @gray: #808080; 3 | @black: #333; // 文字浅灰色(活动规则,tip,小度商城规则) 4 | 5 | #rule { 6 | font-size: 26px; 7 | color: @gray; 8 | padding: 40px; 9 | padding-top: 0; 10 | color: @gray; 11 | overflow: auto; 12 | word-break: break-all; 13 | word-wrap: break-word; 14 | h2 { 15 | color: @black; 16 | padding: 40px 0; 17 | font-size: 32px; /*px*/ 18 | } 19 | p { 20 | line-height: 48px; 21 | &.info { 22 | margin-top: 20px; 23 | } 24 | &>span { 25 | display: block; 26 | margin-bottom: 12px; 27 | line-height: 44px; 28 | } 29 | } 30 | .bold { 31 | color: @black; 32 | } 33 | li { 34 | margin-left: 20px; 35 | line-height: 40px; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mock/view.js: -------------------------------------------------------------------------------- 1 | module.exports = function(req, res, next) { 2 | res.json({ 3 | error_no: 0, 4 | error_msg: "", 5 | result: { 6 | city_id: '132', 7 | city_name: "上海市", 8 | last_city_id: 131, 9 | last_city_name: "北京市", 10 | privilege_no: "131201611000001", 11 | stock: 1030, 12 | privilege_rule: { 13 | max_discount: 4, 14 | day_limit: 4, 15 | month_limit: 20 16 | }, 17 | prices: [{ 18 | period: 31, 19 | period_str: "一个月", 20 | price: 10, 21 | newuser_price: 9 22 | }, { 23 | period: 93, 24 | period_str: "三个月", 25 | price: 30, 26 | newuser_price: 20 27 | }] 28 | } 29 | }) 30 | }; 31 | -------------------------------------------------------------------------------- /app/util/localStorage.js: -------------------------------------------------------------------------------- 1 | // 操作 localstorage 2 | 3 | export default { 4 | getItem (key) { 5 | let value 6 | try { 7 | value = localStorage.getItem(key) 8 | } catch (ex) { 9 | console.error('localStorage.getItem报错, ', ex.message) 10 | } finally { 11 | return value 12 | } 13 | }, 14 | setItem (key, value) { 15 | try { 16 | // ios safari 无痕模式下,直接使用 localStorage.setItem 会报错 17 | localStorage.setItem(key, value) 18 | } catch (ex) { 19 | console.error('localStorage.setItem报错, ', ex.message) 20 | } 21 | }, 22 | delItem (key) { 23 | try { 24 | // ios safari 无痕模式下,直接使用 localStorage.removeItem 会报错 25 | localStorage.removeItem(key) 26 | } catch (ex) { 27 | console.error('localStorage.delItem报错, ', ex.message) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "ecmaFeatures": { 11 | "experimentalObjectRestSpread": true, 12 | "jsx": true, 13 | "classes": true 14 | }, 15 | "sourceType": "module" 16 | }, 17 | "plugins": [ 18 | "react" 19 | ], 20 | "globals": { 21 | "WMAppReady": true, 22 | "WMApp": true 23 | }, 24 | "rules": { 25 | "strict": 0, 26 | "indent": 0, 27 | "linebreak-style": 0, 28 | "quotes": 0, 29 | "no-extra-semi": 0, 30 | "no-unused-expressions": 0, 31 | "no-unused-vars": 0, 32 | "no-console": 0, 33 | "no-mixed-spaces-and-tabs": 0, 34 | "no-cond-assign": 0 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/components/Agree/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 提单页底部同意购买协议 Agree 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import { Link } from 'react-router' 7 | import classNames from 'classnames'; 8 | import './index.less' 9 | 10 | // 组装 Agree 组件 11 | class Agree extends Component { 12 | constructor(props, context) { 13 | super(props, context); 14 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 15 | } 16 | render() { 17 | return ( 18 |
19 |
20 | 同意并接受《外卖购卡协议》 21 |
22 | ) 23 | } 24 | } 25 | 26 | export default Agree 27 | -------------------------------------------------------------------------------- /app/constants/types.js: -------------------------------------------------------------------------------- 1 | // 各种请求状态常量 2 | 3 | // 获取首页卡片信息 4 | export const GET_HOMECARD = 'GET_HOMECARD' 5 | export const GET_HOMECARD_REQUEST = 'GET_HOMECARD_REQUEST' 6 | export const GET_HOMECARD_SUCCESS = 'GET_HOMECARD_SUCCESS' 7 | export const GET_HOMECARD_FAILURE = 'GET_HOMECARD_FAILURE' 8 | 9 | // 获取提单页权益卡选择信息 10 | export const GET_CONFIRMINFO = 'GET_CONFIRMINFO' 11 | export const GET_CONFIRMINFO_REQUEST = 'GET_CONFIRMINFO_REQUEST' 12 | export const GET_CONFIRMINFO_SUCCESS = 'GET_CONFIRMINFO_SUCCESS' 13 | export const GET_CONFIRMINFO_FAILURE = 'GET_CONFIRMINFO_FAILURE' 14 | 15 | // 获取权益卡片使用详情 16 | export const GET_DISCOUNTDETAIL = 'GET_DISCOUNTDETAIL' 17 | export const GET_DISCOUNTDETAIL_REQUEST = 'GET_DISCOUNTDETAIL_REQUEST' 18 | export const GET_DISCOUNTDETAIL_SUCCESS = 'GET_DISCOUNTDETAIL_SUCCESS' 19 | export const GET_DISCOUNTDETAIL_FAILURE = 'GET_DISCOUNTDETAIL_FAILURE' 20 | 21 | // 全局数据 22 | export const ADRESS_UPDATE = 'ADRESS_UPDATE' 23 | export const SAVE_PAYRESULT = 'SAVE_PAYRESULT' -------------------------------------------------------------------------------- /app/containers/Detail/AccessInfo/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 详情页权益规则展示组件 AccessInfo 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | 7 | import './index.less' 8 | 9 | // 组装 AccessInfo 组件 10 | class AccessInfo extends Component { 11 | constructor(props, context) { 12 | super(props, context) 13 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 14 | } 15 | 16 | render() { 17 | let accessList = this.props.accessList 18 | return ( 19 |
20 |

权益只在开通城市有效。

21 |

仅支持专送。

22 |

每单最高减免{accessList.max_discount}元配送费。

23 |

每天最多可减免{accessList.day_limit}单。

24 | {accessList.month_limit ?

每月最多可减免{accessList.month_limit}单。

: ''} 25 |
26 | ) 27 | } 28 | } 29 | 30 | export default AccessInfo 31 | -------------------------------------------------------------------------------- /app/containers/App.jsx: -------------------------------------------------------------------------------- 1 | // 相当于layout 2 | import React, { PropTypes, Component } from 'react' 3 | import * as PureRenderMixin from 'react-addons-pure-render-mixin' 4 | import { bindActionCreators } from 'redux' 5 | import { connect } from 'react-redux' 6 | import { hashHistory } from 'react-router' 7 | 8 | class App extends Component { 9 | 10 | constructor(props, context) { 11 | super(props, context); 12 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 13 | this.state = {} 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 | {this.props.children} 20 |
21 | ) 22 | } 23 | 24 | componentDidMount() { 25 | // 只会执行一次,合适公共数据请求 26 | // console.log('layout Didmount'); 27 | } 28 | 29 | // 路由切换时会触发 30 | componentDidUpdate() { 31 | // 记录路由切换完成、组件都渲染完成之后,页面的hash(包含了路由信息) 32 | window.webappLocationFrom = location.hash; 33 | } 34 | } 35 | 36 | export default App -------------------------------------------------------------------------------- /app/index.jsx: -------------------------------------------------------------------------------- 1 | // 入口文件 2 | import * as React from 'react' 3 | import { render } from 'react-dom' 4 | import { Provider } from 'react-redux' 5 | import { hashHistory } from 'react-router' 6 | import configureStore from './store/configureStore' 7 | 8 | // 性能测试 9 | import * as Perf from 'react-addons-perf' 10 | window.Perf = Perf 11 | 12 | import Utils from './util/util'; 13 | import '../static/styles/common.less' 14 | // 创建 Redux 的 store 对象 15 | const store = configureStore() 16 | 17 | // 获取 route 配置 18 | import Router from './router' 19 | 20 | const renderPage = function () { 21 | WMAppReady(function () { 22 | render( 23 | 24 | 25 | , 26 | document.getElementById('root') 27 | ) 28 | }) 29 | } 30 | 31 | renderPage(); 32 | // debug模式 url: debug=1 方便线上在浏览器打开调试 33 | if(Utils.getCurrentParam('debug') || !window.isWMApp) { 34 | require.async('./util/page.js', function () { 35 | renderPage(); 36 | }); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /app/components/RadioList/RadioItem/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 公共组件-单选框 RadioItem 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames'; 7 | 8 | import './index.less' 9 | 10 | // 组装 RadioItem 组件 11 | class RadioItem extends Component { 12 | constructor(props, context) { 13 | super(props, context) 14 | } 15 | 16 | render() { 17 | let radio = this.props.radio 18 | if (this.props.isNew && (radio.price !== radio.newuser_price)) { 19 | radio.price = radio.newuser_price 20 | } 21 | return ( 22 |
23 |
{radio.period_str}
24 |
25 |
26 | ) 27 | } 28 | } 29 | 30 | export default RadioItem 31 | -------------------------------------------------------------------------------- /app/components/RadioList/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 公共-单选列表 RadioList 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | 7 | import RadioItem from './RadioItem'; 8 | 9 | import './index.less' 10 | 11 | // 组装 RadioList 组件 12 | class RadioList extends Component { 13 | constructor(props, context) { 14 | super(props, context) 15 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 16 | } 17 | render() { 18 | let radioList = this.props.radioList 19 | return ( 20 |
21 |
22 | { 23 | radioList.map((item, index) => ) 24 | } 25 |
26 |
27 | ) 28 | } 29 | } 30 | 31 | export default RadioList 32 | -------------------------------------------------------------------------------- /app/components/Agree/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | @black: #333; 3 | @font-gray2: #a9a9a9; 4 | 5 | // 宽高与可自由设置,大于图片本身宽度即可 6 | .setBG(@url, @w, @h, @imgW, @imgH: auto) { 7 | background: url(@url) no-repeat; 8 | width: @w; 9 | height: @h; 10 | background-size: @imgW @imgH; 11 | background-position: center center; 12 | } 13 | 14 | .agree-wrap { 15 | display: flex; 16 | align-items: center; 17 | color: @font-gray2; 18 | margin-left: 30px; 19 | .radio { 20 | margin-right: 19px; 21 | .setBG('../../../static/img/i_unagree.png', 39px, 59px, 29px); 22 | [data-dpr=3]& { 23 | .setBG('../../../static/img/i_unagree3x.png', 54px, 74px, 44px); 24 | } 25 | } 26 | &.selected .radio{ 27 | .setBG('../../../static/img/i_agree.png', 39px, 59px, 29px); 28 | [data-dpr=3]& { 29 | .setBG('../../../static/img/i_agree3x.png', 54px, 74px, 44px); 30 | } 31 | } 32 | a { 33 | color: @font-gray2; 34 | display: block; 35 | .v-height(59px); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/components/TitleBar/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页区域title TitleBar 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames' 7 | 8 | import './index.less' 9 | 10 | // 组装 TitleBar 组件 11 | class TitleBar extends Component { 12 | constructor(props, context) { 13 | super(props, context); 14 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 15 | this.state = { 16 | curCard: 0 17 | } 18 | } 19 | render() { 20 | return ( 21 |
22 |
23 |
{this.props.title}
24 |
{this.props.value}
25 |
26 |
27 | {this.props.children} 28 |
29 |
30 | ) 31 | } 32 | } 33 | 34 | export default TitleBar 35 | -------------------------------------------------------------------------------- /app/components/NotFound/index.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as PureRenderMixin from 'react-addons-pure-render-mixin' 3 | import { Link } from 'react-router' 4 | 5 | import './style.less' 6 | 7 | class NotFound extends React.Component { 8 | constructor(props, context) { 9 | super(props, context); 10 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 11 | } 12 | render() { 13 | return ( 14 |
15 |
16 |
17 |

哎呀迷路了...

18 |
19 |

可能的原因:

20 | 24 |
25 |
26 | 返回首页 27 |
28 |
29 | ) 30 | } 31 | } 32 | 33 | export default NotFound 34 | -------------------------------------------------------------------------------- /app/components/RadioList/RadioItem/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../../static/styles/public.less'; 2 | @black: #333; 3 | @font-gray: #808080; 4 | .setBG(@url, @w, @h) { 5 | background: url(@url) no-repeat; 6 | width: @w; 7 | height: @h; 8 | background-size: 100% 100%; 9 | } 10 | 11 | .radio-item { 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | .v-height(110px); 16 | border-bottom: 1px solid #e1e1e1;/*no*/ 17 | .text { 18 | font-size: 32px; 19 | color: @black; 20 | margin-left: 5px; 21 | } 22 | .radio { 23 | margin-right: 24px; 24 | .setBG('../../../../static/img/i_unselected.png', 44px, 44px); 25 | [data-dpr=3]& { 26 | .setBG('../../../../static/img/i_unselected3x.png', 66px, 66px); 27 | } 28 | } 29 | &.selected { 30 | .radio { 31 | .setBG('../../../../static/img/i_selected.png', 44px, 44px); 32 | [data-dpr=3]& { 33 | .setBG('../../../../static/img/i_selected3x.png', 66px, 66px); 34 | } 35 | } 36 | } 37 | &:last-child { 38 | border: none; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/containers/Rule/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file Rule 组件, 展示配送折扣卡规则 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | 7 | import Utils from '../../util/util.js'; 8 | 9 | import './index.less' 10 | 11 | class Rule extends Component { 12 | constructor(props, context) { 13 | super(props, context); 14 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 15 | } 16 | 17 | componentWillMount() { 18 | Utils.setTitleBar({ 19 | titleText: '规则详解' 20 | }) 21 | Utils.loading(0) 22 | Utils.setBack() 23 | return false 24 | } 25 | 26 | componentDidMount() { 27 | // 添加活动规则页面统计 28 | } 29 | 30 | render() { 31 | return ( 32 |
33 |

外卖折扣配送卡规则

34 |

35 | 请您务必认真阅读本规则, 特别是加粗的内容, 对您有重要影响, 请确保您理解并认同本 规则所有条款, 如不能理解任何条款请致电外卖客服10105777, 您点击同意本规则, 则表示您已完全理解与认可本规则, 并愿意完全接受本规则的约束。 36 |

37 |

折扣配送卡介绍

38 |
39 | ); 40 | } 41 | } 42 | 43 | export default Rule 44 | -------------------------------------------------------------------------------- /app/components/CardList/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页热卖商品卡片列表组件CardList 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import { bindActionCreators } from 'redux' 7 | import { connect } from 'react-redux' 8 | import { Link } from 'react-router' 9 | 10 | import SellCard from './SellCard'; 11 | import MessageTip from './MessageTip'; 12 | 13 | // 组装 CardList 组件 14 | class CardList extends Component { 15 | constructor(props, context) { 16 | super(props, context); 17 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 18 | } 19 | render() { 20 | let length = this.props.cardList && this.props.cardList.length || 0 21 | return ( 22 |
23 | { 24 | length && this.props.cardList.map((item, index) => 25 | ) 26 | } 27 | 28 |
29 | ) 30 | } 31 | } 32 | 33 | export default CardList 34 | -------------------------------------------------------------------------------- /app/reducers/confirm.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../constants/types' 2 | 3 | const initialState = { 4 | loading: true, 5 | data: {}, 6 | accessList: {}, 7 | radioList: [] 8 | } 9 | 10 | export default function confirm(state = initialState, action) { 11 | switch (action.type) { 12 | case actionTypes.GET_CONFIRMINFO_REQUEST: 13 | return { 14 | ...state, 15 | loading: true 16 | } 17 | case actionTypes.GET_CONFIRMINFO_SUCCESS: 18 | let result = action.json.result 19 | return { 20 | ...state, 21 | loading: false, 22 | data: result, 23 | accessList: result.privilege_rule, 24 | radioList: result.prices, 25 | isNew: result.is_new 26 | } 27 | case actionTypes.GET_CONFIRMINFO_FAILURE: 28 | // error_no 不等于0 29 | return { 30 | ...state, 31 | loading: false, 32 | errno: action.json.error_no, 33 | errmsg: action.json.error_msg 34 | } 35 | default: 36 | return state 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/reducers/card.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../constants/types' 2 | 3 | const initialState = { 4 | loading: true 5 | } 6 | 7 | export default function card(state = initialState, action) { 8 | switch (action.type) { 9 | case actionTypes.GET_HOMECARD_REQUEST: 10 | return { 11 | ...state, 12 | loading: true 13 | } 14 | case actionTypes.GET_HOMECARD_SUCCESS: 15 | let result = action.json.result 16 | return { 17 | ...state, 18 | loading: false, 19 | userPrivileges: result.user_privileges, 20 | cityPrivileges: result.city_privileges, 21 | cityName: result.city_name, 22 | isVip: result.is_vip, 23 | isLogin: result.is_login, 24 | isNew: result.is_new 25 | } 26 | case actionTypes.GET_HOMECARD_FAILURE: 27 | // error_no 不等于0 28 | return { 29 | ...state, 30 | loading: false, 31 | errno: action.json.error_no, 32 | errmsg: action.json.error_msg 33 | } 34 | default: 35 | return state 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/components/ImgTip/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | @gray: #808080; 3 | .setBG(@url, @w, @h) { 4 | background: url(@url) no-repeat; 5 | width: @w; 6 | height: @h; 7 | background-size: 100% 100%; 8 | } 9 | 10 | .tip-wrap { 11 | margin-top: 40px; 12 | margin-bottom: 60px; 13 | text-align: center; 14 | .tip-img { 15 | margin: 0 auto; 16 | margin-bottom: 20px; 17 | } 18 | .tip-msg { 19 | font-size: 28px; 20 | color: @gray; 21 | p { 22 | margin-top: 10px; 23 | } 24 | } 25 | &.novipcard { 26 | .tip-img { 27 | .setBG('../../../static/img/bear-novipcard.png', 180px, 180px); 28 | [data-dpr='3'] & { 29 | .setBG('../../../static/img/bear-novipcard3x.png', 270px, 270px); 30 | } 31 | } 32 | } 33 | &.nousercard { 34 | .tip-img { 35 | .setBG('../../../static/img/bear-nocard.png', 180px, 180px); 36 | [data-dpr='3'] & { 37 | .setBG('../../../static/img/bear-nocard3x.png', 270px, 270px); 38 | } 39 | } 40 | } 41 | &.nocitycard { 42 | margin-top: 80px; 43 | margin-bottom: 130px; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/containers/Home/Onsell/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页热卖商品区组件 Onsell 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | 7 | import CardList from '../../../components/CardList' 8 | import TitleBar from '../../../components/TitleBar' 9 | import ImgTip from '../../../components/ImgTip' 10 | 11 | // 组装 Onsell 组件 12 | class Onsell extends Component { 13 | 14 | constructor(props, context) { 15 | super(props, context) 16 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 17 | } 18 | 19 | render() { 20 | let cardList = this.props.cardList 21 | let cityName = this.props.cityName 22 | let title = cityName === '' ? "热卖中" : `热卖中(${cityName})` 23 | return ( 24 |
25 | { cardList.length ? : '' } 26 | { 27 | cardList.length 28 | ? 29 | : 30 | } 31 |
32 | ) 33 | } 34 | } 35 | 36 | export default Onsell 37 | -------------------------------------------------------------------------------- /static/styles/est/variables.less: -------------------------------------------------------------------------------- 1 | // global flags 2 | @support-html5: true; 3 | @support-ie-version: 7; 4 | @support-old-ie: ~`@{support-ie-version} < 8`; // [deprecated] use `@support-ie-version` instead 5 | @use-autoprefixer: true; 6 | 7 | // default typographic settings 8 | @default-font-size: 16; 9 | @default-text-color: #666; 10 | @default-input-placeholder-color: #999; 11 | 12 | @default-base-font-family: "Helvetica Neue", Arial, sans-serif; 13 | @default-heading-font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; 14 | @default-code-font-family: Monaco, Consolas, monospace; 15 | 16 | // * IE6/7下如果已经找到字体,不会再按字符进行fallback,故必须将雅黑置首位,写上Arial是为了在没有雅黑的情况下显示更好的英文字体 17 | @default-old-ie-base-font-family: @default-base-font-family; 18 | @default-old-ie-heading-font-family: "Microsoft YaHei", Arial, sans-serif; 19 | @default-old-ie-code-font-family: @default-code-font-family; 20 | 21 | // default visual settings 22 | @default-border-radius: 5px; 23 | @default-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); 24 | 25 | // z-index settings (compatible with BootStrap, ESUI) 26 | @header-z-index: 1000; 27 | @footer-z-index: 1000; 28 | @modal-z-index: 1050; 29 | 30 | // default column gutter width 31 | @default-column-gutter: 3%; 32 | @column-justify-content: space-between; 33 | -------------------------------------------------------------------------------- /static/styles/common.less: -------------------------------------------------------------------------------- 1 | @import 'est/all.less'; 2 | @import './public.less'; 3 | @import './animate.less'; 4 | .global-reset(); 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | html, 10 | body { 11 | width: 100%; 12 | font-family: 'Microsoft YaHei', arial, helvetica, sans-serif; 13 | // 安卓下面动画无法滚动 必须设置这个 14 | .user-select(none); 15 | -webkit-tap-highlight-color: transparent; 16 | overflow-x: hidden; 17 | } 18 | 19 | 20 | /*去除黑色阴影*/ 21 | 22 | a, 23 | input { 24 | -webkit-tap-highlight-color: transparent; 25 | -webkit-appearance: none; 26 | } 27 | 28 | //页面上的文字或者图片不被用户选择时候亦或者禁止长按保存图片 29 | a, 30 | img { 31 | -webkit-touch-callout: none; 32 | /* 禁止长按链接与图片弹出菜单 */ 33 | } 34 | 35 | i { 36 | font-style: normal; 37 | } 38 | 39 | a { 40 | text-decoration: none; 41 | } 42 | 43 | input { 44 | .reset-focus(); 45 | } 46 | 47 | .item-transition-enter { 48 | opacity: 0; 49 | transition: all .3s ease-in; 50 | } 51 | 52 | .item-transition-enter.item-transition-enter-active { 53 | opacity: 1; 54 | } 55 | // router转场动画效果 56 | .animate-slide-left-appear { 57 | transform: translate(100%, 0); 58 | } 59 | .animate-slide-right-appear { 60 | transform: translate(-100%, 0); 61 | } 62 | .animate-slide-left-appear.animate-slide-left-appear-active, 63 | .animate-slide-right-appear.animate-slide-right-appear-active { 64 | transform: translate(0, 0); 65 | transition: transform .2s ease-in; 66 | } 67 | -------------------------------------------------------------------------------- /app/fetch/promiseMiddleware.js: -------------------------------------------------------------------------------- 1 | /** 2 | * promise中间件, 让action返回promise,把action与reducer联系起来 3 | * author: younthxg@gmail.com 4 | */ 5 | require('es6-promise').polyfill() 6 | 7 | export default function promiseMiddleware() { 8 | return next => action => { 9 | // rest是action对象剩下的变量集合 10 | const { promise, type, ...rest } = action 11 | // 非promise直接返回 12 | if (!promise) return next(action) 13 | // promise对下各种状态类型 14 | const SUCCESS = type + '_SUCCESS' 15 | const REQUEST = type + '_REQUEST' 16 | const FAILURE = type + '_FAILURE' 17 | // 开始请求 18 | next({...rest, type: REQUEST }) 19 | 20 | return promise 21 | .then(res => res.json()) 22 | .then(json => { 23 | // success 结果包裹在json中 24 | if (Number(json.error_no) === 0) { 25 | next({...rest, json, type: SUCCESS }) 26 | } else { 27 | // error_no 不等于当失败处理 28 | next({...rest, json, type: FAILURE }) 29 | } 30 | return true 31 | }) 32 | .then(undefined, error => { 33 | // error 34 | let json = { 35 | error_msg: String(error), 36 | error_no: 10086 37 | } 38 | next({...rest, json, type: FAILURE }) 39 | return false 40 | }) 41 | } 42 | } -------------------------------------------------------------------------------- /app/components/NotFound/style.less: -------------------------------------------------------------------------------- 1 | .notfoud-container { 2 | .img-404 { 3 | height: 155px; 4 | background: url(./img/page-404.png) center center no-repeat; 5 | -webkit-background-size: 150px auto; 6 | margin-top: 40px; 7 | margin-bottom: 20px; 8 | } 9 | 10 | .notfound-p { 11 | line-height: 22px; 12 | font-size: 17px; 13 | padding-bottom: 15px; 14 | border-bottom: 1px solid #f6f6f6; 15 | text-align: center; 16 | color: #262b31; 17 | } 18 | 19 | .notfound-reason { 20 | color: #9ca4ac; 21 | font-size: 13px; 22 | line-height: 13px; 23 | text-align: left; 24 | width: 210px; 25 | margin: 0 auto; 26 | 27 | p { 28 | margin-top: 13px; 29 | } 30 | 31 | ul { 32 | li { 33 | margin-top: 10px; 34 | margin-left: 36px; 35 | } 36 | } 37 | } 38 | 39 | .notfound-btn-container { 40 | margin: 40px auto 0; 41 | text-align: center; 42 | 43 | .notfound-btn { 44 | display: inline-block; 45 | border: 1px solid #ebedef; 46 | background-color: #239bf0; 47 | color: #fff; 48 | font-size: 15px; 49 | border-radius: 5px; 50 | text-align: center; 51 | padding: 10px; 52 | line-height: 16px; 53 | white-space: nowrap; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/reducers/detail.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../constants/types' 2 | 3 | const initialState = { 4 | loading: true, 5 | accessList: {}, 6 | privilege_info: {}, 7 | valid_date: '', 8 | city_name: '' 9 | // list: [] 10 | } 11 | 12 | export default function detail(state = initialState, action) { 13 | switch (action.type) { 14 | case actionTypes.GET_DISCOUNTDETAIL_REQUEST: 15 | return { 16 | ...state, 17 | loading: true 18 | } 19 | case actionTypes.GET_DISCOUNTDETAIL_SUCCESS: 20 | let result = action.json.result 21 | return { 22 | ...state, 23 | loading: false, 24 | accessList: { 25 | time_days: result.time_days, 26 | total_save: result.total_save, 27 | delivery_times: result.delivery_times 28 | }, 29 | // list: result.list, 30 | privilege_info: result.privilege_info, 31 | valid_date: `${result.date_start} - ${result.date_end}`, 32 | city_name: result.city_name 33 | } 34 | case actionTypes.GET_DISCOUNTDETAIL_FAILURE: 35 | // error_no 不等于0 36 | return { 37 | ...state, 38 | loading: false, 39 | errno: action.json.error_no, 40 | errmsg: action.json.error_msg 41 | } 42 | default: 43 | return state 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/containers/Detail/DiscountList/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../../static/styles/public.less'; 2 | @gray2: #a9a9a9; 3 | @black: #333; 4 | .discount-wrap { 5 | text-align: center; 6 | font-size: 26px; 7 | color: #808080; 8 | .discount-item { 9 | display: flex; 10 | justify-content: space-between; 11 | width: 680px; 12 | padding: 40px 0; 13 | margin-left: 70px; 14 | border-top: 1px solid #e1e1e1; /*no*/ 15 | &:first-child { 16 | border-top: none; 17 | } 18 | .name { 19 | font-size: 28px; 20 | color: @black; 21 | } 22 | .desc { 23 | font-size: 24px; 24 | color: @gray2; 25 | margin-right: 76px; 26 | } 27 | } 28 | .msg-tip { 29 | padding: 20px; 30 | color: @gray2; 31 | } 32 | .loading-more, 33 | .no-more { 34 | padding-bottom: 40px; 35 | } 36 | .loading-more { 37 | vertical-align: middle; 38 | i { 39 | display: inline-block; 40 | vertical-align: middle; 41 | margin: 8px; 42 | width: 30px; 43 | height: 30px; 44 | background-image: url(/static/img/i-loading.png?__inline); 45 | background-repeat: no-repeat; 46 | background-size: 100%; 47 | transform: translateY(10px) rotate(360deg); 48 | animation: spin 1.5s linear infinite; 49 | } 50 | } 51 | } 52 | 53 | @keyframes spin { 54 | 0% { 55 | transform: rotate(0deg) 56 | } 57 | 100% { 58 | transform: rotate(360deg) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/containers/Confirm/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | @gray: #f6f6f6; // 提单页背景 3 | @red: #ff2d4b; // 外卖红 4 | @font-gray2: #a9a9a9; 5 | @black: #333; 6 | @white2: #fffefe; 7 | 8 | .confirm-page { 9 | background-color: @gray; 10 | .buy-card { 11 | width: 700px; 12 | .v-height(90px); 13 | text-align: center; 14 | color: @white2; 15 | font-size: 32px; 16 | background-color: @red; 17 | border-radius: 6px; 18 | margin: 0 auto; 19 | margin-top: 56px; 20 | } 21 | .city-tip-dialog { 22 | .tipmsg-wrap { 23 | padding: 20px 0 30px 0; 24 | p { 25 | color: @black; 26 | text-align: center; 27 | margin-bottom: 16px; 28 | span { 29 | color: @red; 30 | } 31 | } 32 | } 33 | footer { 34 | display: flex; 35 | justify-content: center; 36 | a { 37 | display: block; 38 | // anroid 39 | border: 1px solid @font-gray2; /*no*/ 40 | // ios 41 | [data-dpr=2]& { 42 | border: 2px solid @font-gray2; 43 | } 44 | [data-dpr=3]& { 45 | border: 2px solid @font-gray2; 46 | } 47 | border-radius: 10px; 48 | color: @black; 49 | width: 200px; 50 | .v-height(80px); 51 | text-align: center; 52 | margin: 0 20px; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/containers/Home/Mime/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页我的权益区组件 Mime 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import { Link } from 'react-router' 7 | 8 | import SwipeCard from '../../../components/SwipeCard' 9 | import TitleBar from '../../../components/TitleBar' 10 | import ImgTip from '../../../components/ImgTip' 11 | 12 | import './index.less' 13 | 14 | // 组装 Mime 组件 15 | class Mime extends Component { 16 | constructor(props, context) { 17 | super(props, context) 18 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 19 | } 20 | componentWillMount() { 21 | } 22 | componentDidMount() { 23 | } 24 | componentDidUpdate() { 25 | } 26 | getCardNum(cardList) { 27 | let valid = cardList && cardList.valid || [] 28 | let expired = cardList && cardList.expired || [] 29 | let num = (valid && valid.length) + (expired && expired.length) 30 | return num 31 | } 32 | render() { 33 | let num = this.getCardNum(this.props.cardList) 34 | return ( 35 |
36 | { 37 | num ? 38 | 39 | : '' 40 | } 41 | { 42 | num ? 43 | : this.props.isVip 44 | ? 45 | : 46 | } 47 |
48 | ) 49 | } 50 | } 51 | export default Mime 52 | 53 | -------------------------------------------------------------------------------- /static/styles/public.less: -------------------------------------------------------------------------------- 1 | //无侵入方式的公用类,不调用不会产生多余的代码 2 | @import 'est/all.less'; 3 | @use-autoprefixer: false; 4 | @default-font-size: 24px; 5 | @support-ie-version: 9; 6 | @support-old-ie: false; 7 | @support-html5: false; 8 | //变量 9 | @com-color: #ee2b4e; 10 | @yellow: #fff56c; 11 | @red: #ff2d4b; // 外卖红 12 | //公用方法 13 | .center() { 14 | position: absolute; 15 | left: 50%; 16 | top: 50%; 17 | transform: translate(-50%, -50%); 18 | } 19 | 20 | .all-center() { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | 26 | .x-center() { 27 | position: absolute; 28 | left: 50%; 29 | transform: translateX(-50%); 30 | } 31 | 32 | .y-center() { 33 | position: absolute; 34 | top: 50%; 35 | transform: translateY(-50%); 36 | } 37 | 38 | .v-height(@height) { 39 | height: @height; 40 | line-height: @height; 41 | } 42 | 43 | .hide { 44 | display: none!important 45 | } 46 | 47 | //动画方法 48 | .animate(@type, @duration: .5s, @delay: 0s, @ease: linear, @count: 1) { 49 | animation: @type @duration @delay @ease; 50 | animation-fill-mode: both; 51 | animation-iteration-count: @count; 52 | } 53 | 54 | // 通用按钮 透明有边距 55 | .btn(@color: @red) { 56 | width: 110px; 57 | border-radius: 25px; 58 | text-align: center; 59 | font-size: 28px; 60 | border: 2px solid @color; /*no*/ 61 | height: 50px; 62 | line-height: 48px; 63 | color: @color; 64 | [data-dpr='1'] & { 65 | border: 1px solid @color; /*no*/ 66 | line-height: 49px; 67 | } 68 | [data-dpr='3'] & { 69 | border: 3px solid @color; /*no*/ 70 | line-height: 47px; 71 | } 72 | } 73 | 74 | .pixel-border(@color) { 75 | border: 1px solid @color; /*px*/ 76 | } 77 | -------------------------------------------------------------------------------- /app/components/ImgTip/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页图片提示组件 ImgTip 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames' 7 | 8 | import './index.less' 9 | 10 | // 组装 ImgTip 组件 11 | class ImgTip extends Component { 12 | constructor(props, context) { 13 | super(props, context) 14 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 15 | } 16 | render() { 17 | return ( 18 |
19 | { 20 |
21 |
22 | { 23 | this.props.type === 'novipcard' ? 24 |
25 |

您的全免配送费权益尚未过期

26 |

暂时无法购买新的卡片

27 |
28 | : this.props.type === 'nousercard' ? 29 |
30 |

还没有购卡哦

31 |

请从下方购买

32 |
33 | : this.props.type === 'nocitycard' ? 34 |
35 |

{this.props.cityName ? this.props.cityName + '的' : ''}商品正在紧锣密鼓的筹备中

36 |

敬请期待...

37 |
38 | : '' 39 | } 40 | 41 |
42 | } 43 |
44 | ) 45 | } 46 | } 47 | 48 | export default ImgTip 49 | -------------------------------------------------------------------------------- /app/containers/Home/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | @black: #333; 3 | @shallow-gray2: #a9a9a9; // 文字浅灰色(活动规则,tip,小度商城规则) 4 | .setBG(@url, @w, @h) { 5 | background: url(@url) no-repeat; 6 | width: @w; 7 | height: @h; 8 | background-size: 100% 100%; 9 | } 10 | 11 | .icon-before(@bg, @w, @h) { 12 | &:before { 13 | content: ''; 14 | display: block; 15 | .setBG(@bg, @w, @h); 16 | margin-right: 10px; 17 | } 18 | } 19 | 20 | .to-rule { 21 | font-size: 24px; 22 | color: @shallow-gray2; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | .icon-before(@bg: '../../../static/img/i_rule.png', @w: 28px, @h: 28px); 27 | [data-dpr=3]& { 28 | .icon-before(@bg: '../../../static/img/i_rule3x.png', @w: 42px, @h: 42px) 29 | } 30 | margin: 30px 0; 31 | } 32 | 33 | // 支付成功弹框 34 | .pay-success-dialog { 35 | width: auto; 36 | .pay-success-img { 37 | .setBG('../../../static/img/pay-success.png', 565px, 297px); 38 | [data-dpr=3]& { 39 | .setBG('../../../static/img/pay-success3x.png', 848px, 446px); 40 | } 41 | } 42 | .pay-success-msg { 43 | text-align: center; 44 | margin: 20px 0; 45 | color: @black; 46 | position: absolute; 47 | left: 50%; 48 | transform: translateX(-50%); 49 | top: 245px; 50 | } 51 | footer { 52 | display: flex; 53 | justify-content: center; 54 | a { 55 | display: block; 56 | border: 1px solid @shallow-gray2; /*no*/ 57 | border-radius: 10px; 58 | color: #808080; 59 | width: 150px; 60 | .v-height(58px); 61 | text-align: center; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/actions/card.js: -------------------------------------------------------------------------------- 1 | import * as actionTypes from '../constants/types' 2 | import { get, post } from '../fetch/request' 3 | 4 | //获取首页全部卡片信息(包括我的权益与在售卡片),promise形式 5 | export function getHomeCard(params) { 6 | // 获取state属性/state下面的值,action通过dispatch触发reduce 7 | return (dispatch, getState) => { 8 | // action执行的时候,会传递dispatch getState参数,属于store方法 9 | let globalParams = getState().globalVal 10 | params = { 11 | ...globalParams, 12 | ...params 13 | } 14 | return dispatch({ 15 | type: actionTypes.GET_HOMECARD, 16 | promise: post('/wmall/privilege/center', params) 17 | }) 18 | } 19 | } 20 | //获取提单页 权益卡详细信息,需要传入 privilege_no 21 | export function getConfirmInfo(params) { 22 | // 获取state属性/state下面的值,要用dispatch主动触发 23 | return (dispatch, getState) => { 24 | // action执行的时候,会传递dispatch getState参数,属于store方法 25 | let globalParams = getState().globalVal 26 | params = { 27 | ...globalParams, 28 | ...params 29 | } 30 | return dispatch({ 31 | type: actionTypes.GET_CONFIRMINFO, 32 | promise: post('/wmall/privilege/view', params) 33 | }) 34 | } 35 | } 36 | 37 | //获取详情页 权益卡使用详情,需要传入 page(页码) limit(每页限制数量) 38 | export function getDiscountDetail(params) { 39 | // 获取state属性/state下面的值,要用dispatch主动触发 40 | return (dispatch, getState) => { 41 | // action执行的时候,会传递dispatch getState参数,属于store方法 42 | let globalParams = getState().globalVal 43 | params = { 44 | ...globalParams, 45 | ...params 46 | } 47 | return dispatch({ 48 | type: actionTypes.GET_DISCOUNTDETAIL, 49 | promise: post('/wmall/privilege/promotiondetail', params) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/components/SwipeCard/ValidCard/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页已购卡片-有效卡组件 ValidCard 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames'; 7 | 8 | // 组装 ValidCard 组件 9 | class ValidCard extends Component { 10 | constructor(props, context) { 11 | super(props, context); 12 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 13 | } 14 | render() { 15 | let card = this.props.card 16 | return ( 17 |
18 |
19 |
20 |
{card.city_name}
21 |
查看详情
22 |
23 |
24 |
25 |
{card.discount_rate}
26 |
27 |
折 {card.privilege_name}
28 |
仅支持专送
29 |
30 |
31 |
32 |
33 |
34 | 有效期至 {card.end_time} 35 |
36 |
37 |
38 |
39 | ) 40 | } 41 | } 42 | 43 | export default ValidCard 44 | -------------------------------------------------------------------------------- /app/components/DialogModal/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../static/styles/public.less'; 2 | .modal { 3 | position: fixed; 4 | top: 0; 5 | right: 0; 6 | bottom: 0; 7 | left: 0; 8 | background: rgba(0, 0, 0, 0.5); 9 | zIndex: 99999; 10 | transition: opacity 200ms ease-in; 11 | pointerEvents: auto; 12 | overflowY: auto; 13 | } 14 | 15 | .modal-wrapper { 16 | position: absolute; 17 | left: 50%; 18 | top: 50%; 19 | transform: translate(-50%, -50%); 20 | padding: 1px; /*no*/ 21 | background: #fff; 22 | border-radius: 12px; 23 | width: 650px; 24 | .modal-header { 25 | height: 80px; 26 | line-height: 80px; 27 | text-align: center; 28 | font-size: 30px; 29 | border-bottom: 1px solid #e5e5e5; /*no*/ 30 | color: #303030; 31 | } 32 | .modal-cont { 33 | padding: 20px; 34 | padding-bottom: 30px; 35 | position: relative; 36 | font-size: 28px; 37 | h2 { 38 | color: #333; 39 | font-size: 32px; 40 | text-align: center; 41 | margin-top: 60px; 42 | } 43 | input { 44 | border-radius: 2px; 45 | padding: 5px; 46 | height: 4em; 47 | width: 100%; 48 | } 49 | } 50 | .modal-footer { 51 | display: -webkit-box; 52 | button { 53 | display: block; 54 | -webkit-box-flex: 1; 55 | text-align: center; 56 | border: none; 57 | background: #fff; 58 | height: 80px; 59 | font-weight: normal; 60 | } 61 | button[data-type=cancel] { 62 | border-bottom-left-radius: 6px; 63 | } 64 | button[data-type=confirm] { 65 | border-bottom-right-radius: 6px; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/router.jsx: -------------------------------------------------------------------------------- 1 | // 路由配置 2 | import React, { PropTypes, Component } from 'react' 3 | import * as PureRenderMixin from 'react-addons-pure-render-mixin' 4 | import { Router, Route, IndexRoute } from 'react-router' 5 | 6 | import { redirectToLogin } from './util/authService' 7 | 8 | // App 入口 9 | import App from './containers/App' 10 | 11 | // Home 首页 12 | import Home from './containers/Home' 13 | // Confirm 提交订单页 14 | import Confirm from './containers/Confirm' 15 | // Detail 使用详情 16 | import Detail from './containers/Detail' 17 | // Rule 使用详情 18 | import Rule from './containers/Rule' 19 | 20 | // 404 21 | import NotFound from './containers/404' 22 | 23 | // 配置 router 24 | export default class RouteMap extends Component { 25 | 26 | constructor(props, context) { 27 | super(props, context); 28 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 29 | this.updateHandle = this.updateHandle.bind(this) 30 | } 31 | 32 | updateHandle() { 33 | } 34 | 35 | render() { 36 | return ( 37 | 38 | {/* 先加载app组件 */} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {/* 404 */} 47 | 48 | 49 | 50 | 51 | ) 52 | } 53 | } -------------------------------------------------------------------------------- /app/components/SwipeCard/UnenforcedCard/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页已购卡片-未生效卡组件 UnenforcedCard 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames'; 7 | 8 | // 组装 UnenforcedCard 组件 9 | class UnenforcedCard extends Component { 10 | constructor(props, context) { 11 | super(props, context); 12 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 13 | } 14 | render() { 15 | let card = this.props.card 16 | return ( 17 |
18 |
19 |
20 |
{card.city_name}
21 |
查看详情
22 |
23 |
24 |
25 |
{card.discount_rate}
26 |
27 |
折 {card.privilege_name}
28 |
仅支持专送
29 |
30 |
31 |
32 |
33 |
34 | 有效期 35 |
{card.start_time} ~ {card.end_time}
36 |
37 |
38 |
39 |
40 | ) 41 | } 42 | } 43 | 44 | export default UnenforcedCard 45 | -------------------------------------------------------------------------------- /app/fetch/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 请求封装:get post jsonp 3 | * 利用 fetch API 低版本浏览器通过es6-promise 4 | * jsonp在跨域的情况下才使用,正常不建议打开注释 5 | */ 6 | import 'whatwg-fetch' 7 | import 'es6-promise' 8 | // import fetchJsonp from 'fetch-jsonp' 9 | 10 | // 将对象拼接成 key1=val1&key2=val2&key3=val3 的字符串形式 11 | function obj2params(obj) { 12 | let result = ''; 13 | for (let item in obj) { 14 | result += '&' + item + '=' + encodeURIComponent(obj[item]); 15 | } 16 | if (result) { 17 | result = result.slice(1); 18 | } 19 | return result; 20 | } 21 | 22 | function paramsPrefilter(params) { 23 | // params = Object.assign(params, {display: 'json'}) 24 | params['display'] = 'json' 25 | return params; 26 | } 27 | 28 | export function get(url, params) { 29 | // 处理get 参数 30 | let data = obj2params(paramsPrefilter(params)); 31 | if (data) { 32 | url += (url.indexOf('?') === -1 ? '?' : '&') + data; 33 | } 34 | let result = fetch(url, { 35 | credentials: 'include',// 请求默认带 cookie 36 | headers: { 37 | 'Accept': 'application/json, text/plain, */*' 38 | }, 39 | mode: 'no-cors' 40 | }); 41 | 42 | return result; 43 | } 44 | 45 | // 普通post请求 46 | export function post(url, paramsObj) { 47 | 48 | var result = fetch(url, { 49 | method: 'POST', 50 | credentials: 'include', 51 | headers: { 52 | 'Accept': 'application/json, text/plain, */*', 53 | 'Content-Type': 'application/x-www-form-urlencoded'// 默认表单提交 54 | }, 55 | body: obj2params(paramsPrefilter(paramsObj)) 56 | }); 57 | 58 | return result; 59 | } 60 | 61 | // jsonp保持与fetch一致的API 62 | // export function getJsonp (url, data) { 63 | // data = obj2params(data); 64 | // if (data) { 65 | // url += (url.indexOf('?') === -1 ? '?' : '&') + data; 66 | // } 67 | // let result = fetchJsonp(url, { 68 | // // jsonpCallback: 'jsoncallback', 69 | // timeout: 3000 70 | // }); 71 | // return result; 72 | // } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fis3-react-redux-spa", 3 | "version": "1.0.1", 4 | "description": "基于 fis3+react+redux+react-router移动端单页面应用", 5 | "main": "index.js", 6 | "dependencies": {}, 7 | "devDependencies": {}, 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/younth/fis3-react-redux-spa.git" 14 | }, 15 | "keywords": [ 16 | "react", 17 | "redux", 18 | "fis3" 19 | ], 20 | "author": "younthxg@gmail.com", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/younth/fis3-react-redux-spa/issues" 24 | }, 25 | "homepage": "https://github.com/younth/fis3-react-redux-spa#readme", 26 | "devDependencies": { 27 | "babel-eslint": "^7.0.0", 28 | "eslint": "^2.10.2", 29 | "eslint-plugin-react": "^5.1.1", 30 | "fis-parser-babel-5.x": "^1.4.0", 31 | "fis-parser-less": "^0.1.3", 32 | "fis-postprocessor-autoprefixer": "0.0.5", 33 | "fis-postprocessor-px2rem": "^1.0.4", 34 | "fis3-deploy-http-push": "^1.0.3", 35 | "fis3-hook-commonjs": "^0.1.15", 36 | "fis3-hook-node_modules": "^2.2.7", 37 | "fis3-packager-deps-pack": "^0.0.2", 38 | "fis3-parser-typescript": "^0.2.3", 39 | "fis3-postpackager-loader": "^1.3.17", 40 | "fis3-preprocessor-js-require-css": "^0.1.0", 41 | "fis3-preprocessor-js-require-file": "^0.1.0", 42 | "process": "^0.11.2" 43 | }, 44 | "dependencies": { 45 | "classnames": "^2.2.5", 46 | "es6-promise": "^3.1.2", 47 | "fetch-jsonp": "^1.0.0", 48 | "react": "^15.3.1", 49 | "react-addons-css-transition-group": "^15.3.1", 50 | "react-addons-perf": "^15.3.1", 51 | "react-addons-pure-render-mixin": "^15.3.1", 52 | "react-addons-update": "^15.3.1", 53 | "react-dom": "^15.3.1", 54 | "react-redux": "^4.4.4", 55 | "react-router": "^2.2.2", 56 | "react-swipes": "^1.0.4", 57 | "redux": "^3.4.0", 58 | "redux-thunk": "^2.1.0", 59 | "whatwg-fetch": "^1.0.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /static/styles/est/effects.less: -------------------------------------------------------------------------------- 1 | // 2 | // Effects 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // ### Usage: 6 | // 提供各种视觉效果的封装。 7 | // 8 | 9 | @import "variables.less"; 10 | @import "compatibility.less"; 11 | @import "util.less"; 12 | 13 | // ## 文字效果 14 | 15 | // 16 | // 文字浮凸效果 17 | 18 | .embossed-text(@bg-color) { 19 | color: lighten(@bg-color, 3%); 20 | } 21 | .embossed-text(@bg-color, @fg-color) { 22 | color: @fg-color; 23 | } 24 | .embossed-text(...) { 25 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.5), 0 -1px 0 rgba(255, 255, 255, 0.5); 26 | } 27 | 28 | // 29 | // 文字下陷效果 30 | 31 | .debossed-text(@bg-color) { 32 | @color: darken(@bg-color, 3%); 33 | color: rgba(red(@color), green(@color), blue(@color), 0.8); 34 | } 35 | .debossed-text(@bg-color, @fg-color) { 36 | @color: darken(@fg-color, 3%); 37 | color: rgba(red(@color), green(@color), blue(@color), 0.8); 38 | } 39 | .debossed-text(@bg-color, ...) { 40 | text-shadow: 0 1px 1px @bg-color, 0 0 0 #000, 0 1px 0 rgba(255, 255, 255, 0.8); 41 | } 42 | 43 | // 44 | // 3D文本效果 45 | 46 | .3d-text(@color) { 47 | color: @color; 48 | text-shadow: 0 1px 0 @color - #272727, 49 | 0 2px 0 @color - #303030, 50 | 0 3px 0 @color - #393939, 51 | 0 4px 0 @color - #424242, 52 | 0 5px 0 @color - #5b5b5b, 53 | 0 6px 1px rgba(0, 0, 0, 0.1), 54 | 0 0 5px rgba(0, 0, 0, 0.1), 55 | 1px 1px 3px rgba(0, 0, 0, 0.3), 56 | 3px 3px 5px rgba(0, 0, 0, 0.2), 57 | 5px 5px 10px rgba(0, 0, 0, 0.25), 58 | 0 10px 10px rgba(0, 0, 0, 0.2), 59 | 0 20px 20px rgba(0, 0, 0, 0.15); 60 | } 61 | 62 | // 63 | // 文本发光效果 64 | 65 | .glow-text(@radius: 5px) when (isnumber(@radius)) { 66 | text-shadow: 0 0 @radius; 67 | } 68 | .glow-text(@color, @radius: 5px) when (iscolor(@color)) { 69 | text-shadow: 0 0 @radius @color; 70 | } 71 | 72 | // 73 | // 文本模糊效果 74 | 75 | .blurry-text(@color, @radius: 0.15em) { 76 | color: transparent; 77 | text-shadow: 0 0 @radius @color; 78 | // 0.1px of spread distance made the shadow visible under IE10 79 | text-shadow: 0 0 @radius 0.1px @color; 80 | } 81 | -------------------------------------------------------------------------------- /app/components/SwipeCard/UselessCard/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页已购卡片-无用可删除卡组件 UselessCard 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames' 7 | 8 | // 组装 UselessCard 组件 9 | class UselessCard extends Component { 10 | constructor(props, context) { 11 | super(props, context) 12 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 13 | } 14 | clickBtn(type, privilege_no, event) { 15 | if (event) { 16 | if (!event.currentTarget.classList.contains('card-item')) { 17 | event.stopPropagation(); 18 | } 19 | this.props.clickBtn(type, privilege_no) 20 | } 21 | } 22 | render() { 23 | let card = this.props.card 24 | return ( 25 |
26 |
27 |
28 |
{card.city_name}
29 |
查看详情
30 |
31 |
32 |
33 |
{card.discount_rate}
34 |
35 |
折 {card.privilege_name}
36 |
仅支持专送
37 |
38 |
39 |
已下架
40 |
41 |
42 |
43 |
删除
44 |
45 |
46 |
47 |
48 | ) 49 | } 50 | } 51 | 52 | export default UselessCard 53 | -------------------------------------------------------------------------------- /app/util/page.js: -------------------------------------------------------------------------------- 1 | window.WMApp = {} 2 | window.WMApp.page = {} 3 | window.WMApp.entry = {} 4 | window.WMApp.nui = {} 5 | window.WMApp.account = {} 6 | window.WMApp.network = {} 7 | window.WMApp.pay = {} 8 | window.WMApp.share = {} 9 | window.WMApp.location = {} 10 | window.WMApp.device = {} 11 | window.WMApp.address = {} 12 | window.WMAppReady = function(readyCallback) { 13 | if (readyCallback && typeof readyCallback == 'function') { 14 | if (window.WMApp && typeof window.WMApp === 'object') { 15 | readyCallback() 16 | } else { 17 | document.addEventListener('WMAppReady', function() { 18 | readyCallback() 19 | }, false) 20 | } 21 | } 22 | } 23 | 24 | window.WMApp.page.openPageRefresh = function() { 25 | 26 | } 27 | 28 | window.WMApp.page.changePage = function() { 29 | console.log('link') 30 | } 31 | 32 | window.WMApp.entry.setPageAction = function() { 33 | 34 | } 35 | 36 | window.WMApp.page.hidePageRefresh = function() { 37 | 38 | } 39 | window.WMApp.page.setTitleBar = function(cfg) { 40 | document.title = cfg.titleText 41 | } 42 | 43 | window.WMApp.nui.loading = function(type = 1) { 44 | console.log(type) 45 | } 46 | 47 | window.WMApp.nui.toast = function(cfg) { 48 | console.log(cfg.text) 49 | } 50 | window.WMApp.account.getUserInfo = function() { 51 | 52 | } 53 | window.WMApp.account.login = () => { 54 | console.log('请登录') 55 | } 56 | window.WMApp.network.getNetwork = function(callback) { 57 | callback({ 58 | status: 1 59 | }) 60 | } 61 | window.WMApp.pay.doPay = function(param, callback) { 62 | callback({ 63 | status: 1 64 | }) 65 | } 66 | window.WMApp.share.share = function(params) { 67 | console.log(params) 68 | } 69 | window.WMApp.network.getNetwork = function(callback) { 70 | callback({ 71 | status: 1, 72 | result: { 73 | network: 'mobile' 74 | } 75 | }) 76 | } 77 | window.WMApp.nui.dialog = function(params, callback) { 78 | callback({ 79 | status: 1 80 | }) 81 | } 82 | window.WMApp.location.getLng = window.WMApp.location.getLat = window.WMApp.location.getCityId = function() { 83 | return null 84 | } 85 | window.WMApp.device.getAppVersion = function() { 86 | return '3.9.0' 87 | } 88 | window.WMApp.device.getFrom = function() { 89 | return 'na-iphone' 90 | } 91 | window.WMApp.address.selectAddress = function(params, callback) { 92 | 93 | } -------------------------------------------------------------------------------- /app/components/Loading/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file Loaading组件 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import './index.less' 7 | // 组装 Loading 组件 8 | class Loading extends Component { 9 | constructor(props, context) { 10 | super(props, context) 11 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 12 | } 13 | render() { 14 | return ( 15 |
16 | { 17 | this.props.loading 18 | ?
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |

加载中

37 |
38 |
39 |
40 |
41 | : '' 42 | } 43 |
44 | ) 45 | } 46 | } 47 | export default Loading -------------------------------------------------------------------------------- /app/components/SwipeCard/ExpiredCard/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页已购卡片-过期卡在售可续费卡组件 ExpiredCard 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames'; 7 | 8 | // 组装 ExpiredCard 组件 9 | class ExpiredCard extends Component { 10 | constructor(props, context) { 11 | super(props, context) 12 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 13 | } 14 | clickBtn(type, privilege_no, event) { 15 | if (event) { 16 | if (!event.currentTarget.classList.contains('card-item')) { 17 | event.stopPropagation(); 18 | } 19 | this.props.clickBtn(type, privilege_no) 20 | } 21 | } 22 | render() { 23 | let card = this.props.card 24 | return ( 25 |
26 |
27 |
28 |
{card.city_name}
29 |
查看详情
30 |
31 |
32 |
33 |
{card.discount_rate}
34 |
35 |
折 {card.privilege_name}
36 |
仅支持专送
37 |
38 |
39 |
已过期
40 |
41 | { 42 | card.notRenew ? '' : 43 |
44 |
45 |
续费
46 |
47 |
48 | } 49 |
50 |
51 | ) 52 | } 53 | } 54 | 55 | export default ExpiredCard 56 | -------------------------------------------------------------------------------- /app/components/DialogModal/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 公共弹出层组件 DialogModal 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames' 7 | 8 | import './index.less' 9 | 10 | const transitionSpeed = 100 11 | 12 | // 组装 DialogModal 组件 13 | class DialogModal extends Component { 14 | constructor(props, context) { 15 | super(props, context) 16 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this) 17 | if (this.props.show) { 18 | this.state = { 19 | opacity: 1, 20 | display: 'block', 21 | visibility: 'visible', 22 | show: this.props.show 23 | } 24 | } else { 25 | this.state = { 26 | opacity: 0, 27 | display: 'block', 28 | visibility: 'hidden', 29 | show: this.props.show 30 | } 31 | } 32 | } 33 | 34 | componentWillReceiveProps(props){ 35 | if (this.props.show != props.show) { 36 | props.show ? this.fadeIn() : this.fadeOut() 37 | } 38 | } 39 | 40 | fadeIn(){ 41 | this.setState({ 42 | display: 'block', 43 | visibility: 'visible', 44 | show: true 45 | }, () => { 46 | setTimeout(()=> { 47 | this.setState({opacity: 1}) 48 | }, 10) 49 | }) 50 | } 51 | 52 | fadeOut(){ 53 | this.setState({opacity: 0}, () => { 54 | setTimeout(() => { 55 | this.setState({show: false}) 56 | }, transitionSpeed) 57 | }) 58 | } 59 | render() { 60 | if (!this.state.show) { 61 | return null; 62 | } 63 | // todo 64 | let modalStyle, 65 | { opacity, display, visibility } = this.state 66 | modalStyle = { 67 | opacity: opacity, 68 | display: display, 69 | visibility: visibility 70 | } 71 | 72 | return ( 73 |
74 |
75 | { this.props.title ?
{ this.props.title }
: ''} 76 |
77 | {this.props.children} 78 |
79 |
80 |
81 | ) 82 | } 83 | } 84 | 85 | export default DialogModal 86 | 87 | -------------------------------------------------------------------------------- /app/components/Access/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 提单页顶部权益展示组件 Access 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | 7 | import './index.less' 8 | 9 | // 组装 Access 组件 10 | class Access extends Component { 11 | constructor(props, context) { 12 | super(props, context); 13 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 14 | this.state = { 15 | curCard: 0 16 | } 17 | } 18 | render() { 19 | let accessList = this.props.accessList 20 | let type = this.props.type 21 | return ( 22 |
23 | { 24 | (type === 'privilege-detail') ? 25 |
26 |
27 |
{accessList.max_discount || 0 }
28 |
每单最高减免
29 |
30 |
31 |
{accessList.day_limit || 0 }
32 |
每天减免
33 |
34 | { 35 | accessList.month_limit ? 36 |
37 |
{accessList.month_limit || 0 }
38 |
每月最多减免
39 |
40 | : '' 41 | } 42 |
43 | : (type === 'discount-detail') ? 44 |
45 |
46 |
{accessList.time_days || 0 }
47 |
权益时间
48 |
49 |
50 |
{accessList.total_save || 0 }
51 |
为您节省
52 |
53 |
54 |
{accessList.delivery_times || 0 }
55 |
折扣配送
56 |
57 |
58 | : '' 59 | } 60 |
61 | ) 62 | } 63 | } 64 | 65 | export default Access 66 | -------------------------------------------------------------------------------- /app/components/SwipeCard/RenewCard/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @file 首页已购卡片-续费卡组件 RenewCard 3 | */ 4 | import React, { PropTypes, Component } from 'react' 5 | import PureRenderMixin from 'react-addons-pure-render-mixin' 6 | import classNames from 'classnames'; 7 | 8 | // 组装 RenewCard 组件 9 | class RenewCard extends Component { 10 | constructor(props, context) { 11 | super(props, context); 12 | this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); 13 | } 14 | clickBtn(type, privilege_no, event) { 15 | if (event) { 16 | if (!event.currentTarget.classList.contains('card-item')) { 17 | event.stopPropagation(); 18 | } 19 | this.props.clickBtn(type, privilege_no) 20 | } 21 | } 22 | render() { 23 | let card = this.props.card 24 | return ( 25 |
26 |
27 |
28 |
{card.city_name}
29 |
查看详情
30 |
31 |
32 |
33 |
{card.discount_rate}
34 |
35 |
折 {card.privilege_name}
36 |
仅支持专送
37 |
38 |
39 |
40 | { 41 | card.expired_in === 0 ?
今天到期
42 | : card.expired_in === 1 ?
明天到期
43 | : card.expired_in === 2 ?
后天到期
44 | :
{card.expired_in}
天后到期
45 | } 46 |
47 |
48 | { 49 | card.notRenew ? '' : 50 |
51 |
52 |
续费
53 |
54 |
55 | } 56 |
57 |
58 | ) 59 | } 60 | } 61 | 62 | export default RenewCard 63 | -------------------------------------------------------------------------------- /static/styles/est/clockhand.less: -------------------------------------------------------------------------------- 1 | // 2 | // Clockhand 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // ### Usage: 6 | // Helpers for writing properties in clockhand manner. 7 | 8 | .clockhand(@values, @prefix: ~"", @suffix: ~"", @collapse: false) { 9 | @l: length(@values); 10 | @pre: ~`'@{prefix}' ? '@{prefix}-' : ''`; 11 | @suf: ~`'@{suffix}' ? '-@{suffix}' : ''`; 12 | 13 | .map(1) { 14 | @top: extract(@values, 1); 15 | @right: @top; 16 | @bottom: @top; 17 | @left: @top; 18 | } 19 | .map(2) { 20 | @top: extract(@values, 1); 21 | @right: extract(@values, 2); 22 | @bottom: @top; 23 | @left: @right; 24 | } 25 | .map(3) { 26 | @top: extract(@values, 1); 27 | @right: extract(@values, 2); 28 | @bottom: extract(@values, 3); 29 | @left: @right; 30 | } 31 | .map(4) { 32 | @top: extract(@values, 1); 33 | @right: extract(@values, 2); 34 | @bottom: extract(@values, 3); 35 | @left: extract(@values, 4); 36 | } 37 | .map(@l); 38 | 39 | .reduce() when (@collapse) and not (@top = _) and not (@right = _) 40 | and not (@bottom = _) and not (@left = _) { 41 | .output() { 42 | @shorthand: @top; 43 | } 44 | .output() when not (@right = @top) { 45 | @shorthand: @top @right; 46 | } 47 | .output() when not (@bottom = @top) { 48 | @shorthand: @top @right @bottom; 49 | } 50 | .output() when not (@left = @right) { 51 | @shorthand: @top @right @bottom @left; 52 | } 53 | .output(); 54 | @{prefix}@{suf}: @shorthand; 55 | } 56 | .reduce() when (default()) { 57 | .output() when not (@top = _) { 58 | @{pre}top@{suf}: @top; 59 | } 60 | .output() when not (@right = _) { 61 | @{pre}right@{suf}: @right; 62 | } 63 | .output() when not (@bottom = _) { 64 | @{pre}bottom@{suf}: @bottom; 65 | } 66 | .output() when not (@left = _) { 67 | @{pre}left@{suf}: @left; 68 | } 69 | .output(); 70 | } 71 | 72 | .reduce(); 73 | } 74 | 75 | .absolute(...) { 76 | position: absolute; 77 | .clockhand(@arguments); 78 | } 79 | 80 | .fixed(...) { 81 | position: fixed; 82 | .clockhand(@arguments); 83 | } 84 | 85 | .relative(...) { 86 | position: relative; 87 | .clockhand(@arguments); 88 | } 89 | 90 | .padding(...) { 91 | .clockhand(@arguments, padding, ~"", true); 92 | } 93 | 94 | .margin(...) { 95 | .clockhand(@arguments, margin, ~"", true); 96 | } 97 | 98 | .border-color(...) { 99 | .clockhand(@arguments, border, color, true); 100 | } 101 | 102 | .border-style(...) { 103 | .clockhand(@arguments, border, style, true); 104 | } 105 | 106 | .border-width(...) { 107 | .clockhand(@arguments, border, width, true); 108 | } 109 | -------------------------------------------------------------------------------- /static/styles/est/reset.less: -------------------------------------------------------------------------------- 1 | // 2 | // Reset 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // ### Usage: 6 | // 在整站中推荐使用`normalize`来初始化样式,`reset`仅建议在第三方页面局部使用。 7 | // 8 | // ### Example: 9 | // .ec-zx-edu { 10 | // .reset-box-model; 11 | // ol, ul { 12 | // .reset-list-style; 13 | // } 14 | // } 15 | // 16 | // ### Reference: 17 | // * http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/ 18 | // * http://compass-style.org/reference/compass/reset/utilities/ 19 | 20 | @import "variables.less"; 21 | @import "compatibility.less"; 22 | 23 | .global-reset() { 24 | html, body, div, span, applet, object, iframe, 25 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 26 | a, abbr, acronym, address, big, cite, code, 27 | del, dfn, em, img, ins, kbd, q, s, samp, 28 | small, strike, strong, sub, sup, tt, var, 29 | dl, dt, dd, ol, ul, li, 30 | fieldset, form, label, legend, 31 | table, caption, tbody, tfoot, thead, tr, th, td { 32 | .reset-box-model(); 33 | .reset-focus(); 34 | .reset-font(); 35 | } 36 | body { 37 | .reset-body(); 38 | } 39 | ol, ul { 40 | .reset-list-style(); 41 | } 42 | table { 43 | .reset-table(); 44 | } 45 | caption, th, td { 46 | .reset-table-cell(); 47 | } 48 | a img { 49 | border: none; 50 | } 51 | } 52 | .global-reset() when (@support-html5) { 53 | .reset-html5(); 54 | } 55 | 56 | .nested-reset() { 57 | div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, 58 | pre, a, abbr, acronym, address, code, del, dfn, em, img, 59 | dl, dt, dd, ol, ul, li, fieldset, form, label, 60 | legend, caption, tbody, tfoot, thead, tr { 61 | .reset-box-model(); 62 | .reset-focus(); 63 | .reset-font(); 64 | } 65 | table { 66 | .reset-table(); 67 | } 68 | caption, th, td { 69 | .reset-table-cell(); 70 | } 71 | a img { 72 | border: none; 73 | } 74 | } 75 | .nested-reset() when (@support-html5) { 76 | .reset-html5(); 77 | } 78 | 79 | .reset-box-model() { 80 | padding: 0; 81 | margin: 0; 82 | border: 0; 83 | } 84 | 85 | .reset-focus() { 86 | outline: 0; 87 | } 88 | 89 | .reset-font() { 90 | font-weight: inherit; 91 | font-style: inherit; 92 | font-family: inherit; 93 | font-size: 100%; 94 | vertical-align: baseline; 95 | } 96 | 97 | .reset-body() { 98 | line-height: 1; 99 | } 100 | 101 | .reset-table() { 102 | border-collapse: separate; 103 | border-spacing: 0; 104 | vertical-align: middle; 105 | } 106 | 107 | .reset-table-cell() { 108 | text-align: left; 109 | font-weight: normal; 110 | vertical-align: middle; 111 | } 112 | 113 | .reset-list-style() { 114 | list-style: none; 115 | } 116 | 117 | .reset-html5() when (@support-html5) { 118 | article, aside, details, figcaption, 119 | figure, footer, header, hgroup, menu, nav, 120 | section, summary, main { 121 | display: block; 122 | .reset-box-model(); 123 | .reset-focus(); 124 | .reset-font(); 125 | } 126 | audio, canvas, video { 127 | .inline-block(); 128 | } 129 | audio:not([controls]),[hidden] { 130 | display: none; 131 | } 132 | } 133 | .reset-html5() {} 134 | -------------------------------------------------------------------------------- /app/util/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | 工具类 3 | */ 4 | 5 | var Utils = { 6 | 7 | // 获取url中所有的参数 8 | getParams(url) { 9 | var vars = {}, 10 | hash, hashes, i 11 | 12 | url = url || window.location.href 13 | 14 | // 没有参数的情况 15 | if (url.indexOf('?') == -1) { 16 | return vars 17 | } 18 | 19 | hashes = url.slice(url.indexOf('?') + 1).split('&') 20 | 21 | for (i = 0; i < hashes.length; i++) { 22 | if (!hashes[i] || hashes[i].indexOf('=') == -1) { 23 | continue 24 | } 25 | hash = hashes[i].split('=') 26 | if (hash[1]) { 27 | vars[hash[0]] = (hash[1].indexOf("#") != -1) ? hash[1].slice(0, hash[1].indexOf("#")) : hash[1] 28 | } 29 | } 30 | return vars 31 | }, 32 | 33 | // 获取指定name的参数 34 | getParam(name, url) { 35 | return this.getParams(url)[name] 36 | }, 37 | 38 | getCurrentParam(name) { 39 | return this.getParam(name, location.href) 40 | }, 41 | 42 | // 设置头部信息及右上角按钮事件 43 | setTitleBar(cfg = {}) { 44 | window.WMAppReady(function() { 45 | window.WMApp.page.setTitleBar({ 46 | titleText: cfg.titleText, // 标题 47 | actionText: cfg.actionText || ' ', // 右边操作文案, 48 | actionClickAble: cfg.actionClickAble || 0 // 是否可点击,0不可点,1可点,默认0 49 | }) 50 | // 设置右侧按钮时,设置分享文案或者跳转链接 51 | cfg.actionClickAble && window.WMApp.entry.setPageAction('onActionClick', function() { 52 | cfg.shareParams ? window.WMApp.share.share(cfg.shareParams) : (location.href = cfg.url || 'javascript:;') 53 | }) 54 | }) 55 | }, 56 | 57 | // 设置返回按钮操作 58 | setBack(cfg = {}) { 59 | window.WMAppReady(function() { 60 | cfg.type = cfg.type || 3 61 | window.WMApp.entry.setPageAction('onBack', function() { 62 | if (cfg.type === 1) { 63 | // 关闭 64 | return 1 65 | } else if (cfg.type === 3) { 66 | // 返回 67 | history.back() 68 | return 0 69 | } else if (cfg.type === 2) { 70 | // 跳转url 71 | location.href = cfg.url 72 | return 0 73 | } 74 | }) 75 | }) 76 | }, 77 | 78 | openPage(linkUrl, pageData = {}, pageName = 'webview') { 79 | // 如果用NA的切壳,需要重新加载js,失去了单页面的溢出了 80 | let params = { 81 | pageName: pageName || 'home', 82 | pageParams: { 83 | url: encodeURIComponent(location.origin + linkUrl), 84 | header: 1, 85 | pageData: pageData 86 | } 87 | } 88 | window.WMApp.page.changePage(params) 89 | }, 90 | 91 | // 显示loading 1(显示) 0(隐藏) 92 | loading(type = 1) { 93 | window.WMAppReady(function() { 94 | window.WMApp.nui.loading({ 95 | show: type 96 | }) 97 | }) 98 | }, 99 | 100 | // 封装toast方法 101 | showToast(text, speed = 'short') { 102 | window.WMApp.nui.toast({ 103 | text: text, 104 | duration: speed 105 | }) 106 | }, 107 | 108 | alert(obj) { 109 | alert(JSON.stringify(obj)) 110 | } 111 | 112 | } 113 | 114 | export default Utils -------------------------------------------------------------------------------- /static/styles/est/grid.less: -------------------------------------------------------------------------------- 1 | // 2 | // Grid 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // ### Usage: 6 | // Flexible grid layout inspired by Jeet. 7 | // Use `.make-row()` to create rows and use `.make-column()` to create columns. 8 | // 9 | 10 | @import "variables.less"; 11 | @import "util.less"; 12 | 13 | .make-row() { 14 | .clearfix(); 15 | } 16 | 17 | .make-column(...) { 18 | display: block; 19 | float: left; 20 | } 21 | .make-column(@ratio, @gutter: @default-column-gutter, @offset: 0) 22 | when (ispercentage(@gutter)), (unit(@gutter) = 0) and (isnumber(@offset)) { 23 | @gutter-value: unit(@gutter); 24 | @width-and-gutter: ~`(function () { var ratios = @{ratio}; var gutter = @{gutter-value}; if (Object.prototype.toString.apply(ratios) !== '[object Array]') { ratios = [ratios]; } var width = 100; for (var i = ratios.length - 1; i >= 0; i--) { var ratio = ratios[i]; gutter = gutter / width * 100; width = 100 * ratio + ('@{column-justify-content}' === 'space-between' ? -1 : 1) * (1 - ratio) * gutter; } return width + ' ' + gutter; })()`; 25 | @w: ~`Number('@{width-and-gutter}'.split(' ')[0])`; 26 | @g: ~`Number('@{width-and-gutter}'.split(' ')[1])`; 27 | @o: ~`(function () { var ratios = @{offset}; var gutter = @{g}; if (Object.prototype.toString.apply(ratios) !== '[object Array]') { ratios = [ratios]; } var width = 100; for (var i = ratios.length - 1; i >= 0; i--) { var ratio = ratios[i]; gutter = gutter / width * 100; width = 100 * ratio + ('@{column-justify-content}' === 'space-between' ? -1 : 1) * (1 - ratio) * gutter; } return width; })()`; 28 | width: unit(@w, %); 29 | margin-left: unit(@o + @g * 2, %); 30 | 31 | &:first-child { 32 | margin-left: unit(@o + @g, %); 33 | } 34 | 35 | // .uncycle() when not (@uncycle = 0) { 36 | // @u: ~`@{uncycle} === 1 ? '' : @{uncycle}`; 37 | // @u-rule: ~"@{u}n+1"; 38 | // &:nth-of-type(@{u-rule}) { 39 | // clear: none; 40 | // margin-left: unit(@o + @g * 2, %); 41 | // } 42 | // } 43 | // .uncycle(); 44 | 45 | // .cycle() when not (@cycle = 0) { 46 | // @c: ~`@{cycle} === 1 ? '' : @{cycle}`; 47 | // @c-rule: ~"@{c}n+1"; 48 | // &:nth-of-type(@{c-rule}) { 49 | // clear: left; 50 | // margin-left: unit(@o + @g, %); 51 | // } 52 | // } 53 | // .cycle(); 54 | } 55 | .make-column(@ratio, @gutter, @total, @offset: 0) when not (ispercentage(@gutter)) { 56 | .calc() when (@column-justify-content = space-around) { 57 | @w: @total * @ratio + @gutter - @ratio * @gutter; 58 | @o: @total * @offset + @gutter - @offset * @gutter; 59 | } 60 | .calc() when (default()) { 61 | @w: @total * @ratio - @gutter + @ratio * @gutter; 62 | @o: @total * @offset - @gutter + @offset * @gutter; 63 | } 64 | .calc(); 65 | width: @w; 66 | @unit: get-unit(@gutter); 67 | margin-left: @o + @gutter * 2; 68 | 69 | &:first-child { 70 | margin-left: @o + @gutter; 71 | } 72 | 73 | // .uncycle() when not (@uncycle = 0) { 74 | // @u: ~`@{uncycle} === 1 ? '' : @{uncycle}`; 75 | // @u-rule: ~"@{u}n+1"; 76 | // &:nth-of-type(@{u-rule}) { 77 | // clear: none; 78 | // margin-left: @o + @gutter * 2; 79 | // } 80 | // } 81 | // .uncycle(); 82 | 83 | // .cycle() when not (@cycle = 0) { 84 | // @c: ~`@{cycle} === 1 ? '' : @{cycle}`; 85 | // @c-rule: ~"@{c}n+1"; 86 | // &:nth-of-type(@{c-rule}) { 87 | // clear: left; 88 | // margin-left: @o + @gutter; 89 | // } 90 | // } 91 | // .cycle(); 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## fis3+react+react-router+redux单页面应用实践 2 | 3 | 基于 [fis3+react+redux+react-router](https://github.com/younth/fis3-react-redux-spa) 的单页面应用,目前应用在多个移动端业务线。 4 | 5 | ***注意:*** 6 | 7 | - 当前分支基于`fis3`构建,**如果你使用的是`webpack`,直接切到该项目的`webpack`分支**。 8 | - 本项目为剥离业务代码后的架子,只保留了基础UI样式及交互,demo中的数据全部是mock出来的。 9 | - 喜欢的话请`star`支持下~ 10 | 11 | 单页面效果: 12 | 13 | ![react-spa](static/github/fis3+react+redux.gif) 14 | 15 | ### 安装启动 16 | 17 | 本项目构建基于`fis3`,不熟悉`fis3`的可以先去学习下。[传送门](http://fis.baidu.com/) 18 | 19 | - 安装依赖包 `npm i --registry=https://registry.npm.taobao.org`,指定淘宝源可加速按照依赖 20 | - 启动fis3 server服务 fis3 server start 21 | - 将代码推到fis3 server www文件中 fis3 release -cwL 修改代码可以自动刷新浏览器 22 | 23 | ### 技术栈 24 | 25 | - fis3 26 | - react 27 | - redux 28 | - react-router 29 | - es6 30 | - fetch 31 | 32 | ### 代码检测 33 | 34 | npm run lint 35 | 36 | ### 项目目录结构 37 | 38 | ``` 39 | project 40 | ├─ node_modules (npm模块) 41 | ├─ app (工程模块) 42 | │ ├─ actions (获取数据并流向stores) 43 | │ │ └─ more 44 | │ ├─ components (组件) 45 | │ │ └─ more 46 | │ ├─ reducers (每个store) 47 | │ │ ├─ index 48 | │ │ └─ detail 49 | │ └─ containers (页面) 50 | │ ├─ header 51 | │ └─ footer 52 | │ └─ router (路由) 53 | │ ├─ cardDetail 54 | ├─ static (非业务相关资源) 55 | │ ├─ lib 56 | │ ├─ img 57 | │ ├─ js 58 | │ ├─ css 59 | ├─ config (配置) 60 | └─ server.conf (mock数据) 61 | ├─ fis-conf.js (fis3编译配置) 62 | ├─ .eslintrc.json (eslint配置) 63 | ├─ index.html (入口文件) 64 | ... 65 | 66 | ``` 67 | 68 | ### 组件化设计 69 | 70 | 要降低系统设计的复杂度,前端目前最好的方式就是组件化。将系统划分成若干个页面,然后将每个页面都划分成若干个组件,还要抽象出多个页面中都会用到的通用组件,这是第一步。接下来要看组件和组件之间如何传递数据,即数据管理和状态管理该如何做。 71 | 72 | ![page](http://wangfupeng.coding.me/imgs/138012-20160810160249527-576807060.png) 73 | 74 | 但是最后在开发过程中忽略了一个问题,就是一些复杂页面,光这种“页面 - 组件”的形式是不够的,应该在加一个subpage层,这样就扩展性更好一些了。如下图: 75 | 76 | ![subpage](http://wangfupeng.coding.me/imgs/138012-20160810161424090-557619220.png) 77 | 78 | 总结:一个项目总会遇到个别的比较复杂的页面,因此这种page - subpage - component会更加合理一些,其中的subpage在不需要的时候省略掉就是了。 79 | 80 | ### 数据请求管理 81 | 82 | #### 直接请求 83 | 84 | 这种形式,直接在业务的请求回调中处理。 85 | 86 | - 系统数据,通过action的方法修改store的值 87 | - 业务数据,更改业务组件的state值 88 | 89 | 90 | ``` 91 | getData().then(res => { 92 | return res.json() 93 | }).then(json => { 94 | if (json.errno !== 0) { 95 | console.error('errno:' + json.errno); 96 | return; 97 | } 98 | var data = json.data; 99 | // 通过回调,更新系统数据 100 | this.props.collectlistActions.update(news) 101 | // 更改业务数据 102 | this.setState({hotnews: json.data.slice(1, 4)}) 103 | }) 104 | 105 | ``` 106 | 107 | #### redux-thunk 中间件形式 108 | 109 | 通过`configureStore.js`去关联action与store 110 | 111 | ``` 112 | // 调用 113 | actions.getCardList() 114 | 115 | //获取标签列表. 116 | export const getCardList = () =>{ 117 | return { 118 | type: types.Card_LIST, 119 | promise: api.getTagList() 120 | } 121 | } 122 | 123 | // cardList reducer 124 | import * as actionTypes from '../constants/types' 125 | 126 | export default function card(state = initialState, action) { 127 | switch (action.type) { 128 | case actionTypes.GET_HOMECARD_REQUEST: 129 | return { 130 | ...state, 131 | loading: true 132 | } 133 | case actionTypes.GET_HOMECARD_SUCCESS: 134 | let result = action.json.result 135 | return { 136 | ...state, 137 | } 138 | case actionTypes.GET_HOMECARD_FAILURE: 139 | // error_no 不等于0 140 | return { 141 | ...state, 142 | } 143 | default: 144 | return state 145 | } 146 | } 147 | 148 | ``` 149 | -------------------------------------------------------------------------------- /fis-conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fis配置文件 3 | */ 4 | 5 | // 按需编译,根据入口index.html寻找依赖编译,按需加载,智能打包 6 | fis.set('project.files', ['/index.html', '/mock/**']); 7 | 8 | // mock数据,必须放在mock下面才生效,并且不编译 9 | fis.match('/mock/**', { 10 | useCompile: false 11 | }) 12 | 13 | // 采用 commonjs 模块化方案。需要 npm install fis3-hook-commonjs --save-dev 14 | fis.hook('commonjs', { 15 | baseUrl: './app', // 默认为 . 即项目根目录。用来配置模块查找根目录 16 | extList: ['.js', '.jsx'] // 当引用模块时没有指定后缀,该插件会尝试这些后缀 17 | }); 18 | 19 | fis.match('{/app/**.js,*.jsx}', { 20 | parser: fis.plugin('babel-5.x', { 21 | blacklist: ['regenerator'], 22 | optional: ["es7.decorators", "es7.classProperties"], 23 | stage: 2, // 为了支持解构赋值 24 | sourceMaps: false 25 | }), 26 | rExt: '.js' 27 | }); 28 | 29 | fis.match('*.{css,less}', { 30 | postprocessor: fis.plugin('autoprefixer', { 31 | browsers: ['android 4', 'ios 6', 'last 1 Chrome versions', 'last 2 Safari versions'], 32 | 'cascade': true 33 | }) 34 | }); 35 | 36 | fis.match('*.less', { 37 | parser: fis.plugin('less'), 38 | postprocessor: fis.plugin('px2rem', { 39 | remUnit: 75 40 | }, 'append'), 41 | rExt: '.css' 42 | }); 43 | 44 | // fis3 中预设的是 fis-components,这里使用 fis3-hook-node_modules,所以先关了。 45 | fis.unhook('components'); 46 | fis.hook('node_modules'); 47 | 48 | // 设置成是模块化 js 49 | fis.match('/{node_modules,app}/**.{js,jsx}', { 50 | isMod: true 51 | }); 52 | 53 | fis.match('*.{js,es,es6,jsx,ts,tsx}', { 54 | // 支持直接 require 'xxx.css' 直接 require 'xxx.png'等文件 55 | preprocessor: [ 56 | fis.plugin('js-require-file'), 57 | fis.plugin('js-require-css') 58 | ] 59 | }); 60 | 61 | fis.match('::package', { 62 | // 需要 npm install fis3-packager-deps-pack --save-dev 63 | // 测试环境发布相对目录,线上走CDN 64 | packager: fis.plugin('deps-pack', { 65 | // 将 /node_module 中的依赖项,打包成 static/pkg/webappreact/third.js 66 | 'static/pkg/third.js': [ 67 | // 将 /app/index.js 的依赖项加入队列,包含了 /app 中的依赖项 和 /node_modules 中的依赖项 68 | '/app/index.jsx:deps', 69 | // 移除 /app/** 只保留 /node_module 中的依赖项 70 | '!/app/**' 71 | ], 72 | 73 | // 将几个直接以