├── .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 |
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 |
21 | - 原来的页面不存在了
22 | - 我们的服务器被外星人劫持了
23 |
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 |
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 |
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 | :
45 | }
46 |
47 |
48 | {
49 | card.notRenew ? '' :
50 |
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 | 
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 | 
73 |
74 | 但是最后在开发过程中忽略了一个问题,就是一些复杂页面,光这种“页面 - 组件”的形式是不够的,应该在加一个subpage层,这样就扩展性更好一些了。如下图:
75 |
76 | 
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 | // 将几个直接以