【心有猛虎】react-pxq

這是一個比較完整的簡單的react移動端項目,提及來頁面少,其實,構思如果精巧,也並不容易作
先放源碼:https://github.com/bailicangdu/react-pxq



接下來咱們簡單的看看代碼
項目有用到react-redux和axios處理數據css

//根index.js引用了一些基本的方法
//定義了渲染組件  <Component />
import React from 'react';
import ReactDOM from 'react-dom';
import Route from './router/';
import FastClick from 'fastclick';
import registerServiceWorker from './registerServiceWorker';
import { AppContainer } from 'react-hot-loader';
import {Provider} from 'react-redux';
import store from '@/store/store';
import './utils/setRem';
import './style/base.css';
//解決tab穿透的問題
FastClick.attach(document.body);

// 監聽state變化
// store.subscribe(() => {
//   console.log('store發生了變化');
// });

const render = Component => {
  ReactDOM.render(
    //綁定redux、熱加載
    <Provider store={store}>
      <AppContainer>
        <Component />
      </AppContainer>
    </Provider>,
    document.getElementById('root'),
  )
}

render(Route);

// Webpack Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./router/', () => {
    render(Route);
  })
}

registerServiceWorker();
//src/utils/setRem.js
//定義的是單位的轉換
(function(psdw){
  var dpr=0 , rem=0 , scale=0;
  var htmlDOM=document.documentElement;
  dpr=window.devicePixelRatio;
  var currentWidth=htmlDOM.clientWidth;
  scale=currentWidth/psdw;
  rem=psdw/10;
  rem=rem*scale;
  htmlDOM.style.fontSize=rem+'px';
  htmlDOM.setAttribute('data-dpr',dpr)
})(750)
//src/store/store.js
//與store相對的就是reducer和action
//這裏用到的是react-thunk
import {createStore, combineReducers, applyMiddleware} from 'redux';
import * as home from './home/reducer';
import * as production from './production/reducer';
import thunk from 'redux-thunk';

let store = createStore(
  combineReducers({...home, ...production}),
  applyMiddleware(thunk)
);

export default store;
//reducer裏面主要是純函數
//src/store/home/reducer.js
import * as home from './action-type';

let defaultState = {
  orderSum: '', //金額
  name: '', //姓名
  phoneNo: '', //手機號
  imgpath: '', //圖片地址
}
// 首頁表單數據
export const formData = (state = defaultState , action = {}) => {
  switch(action.type){
    case home.SAVEFORMDATA:
      return {...state, ...{[action.datatype]: action.value}};
    case home.SAVEIMG:
      return {...state, ...{imgpath: action.path}};
    case home.CLEARDATA:
      return {...state, ...defaultState};
    default:
      return state;
  }
}
//src/store/production/reducer.js
import * as pro from './action-type';
import Immutable from 'immutable';

let defaultState = {
  /**
   * 商品數據
   * @type {Array}
   * example: [{
   *    product_id: 1, 商品ID 
   *    product_name: "PaiBot(2G/32G)", 商品名稱
   *    product_price: 2999, 商品價格
   *    commission: 200, 佣金
   *    selectStatus: false, 是否選擇
   *    selectNum: 0, 選擇數量
   * }]
   */
  dataList: [],
}

export const proData = (state = defaultState, action) => {
  let imuDataList;
  let imuItem;
  switch(action.type){
    case pro.GETPRODUCTION: 
      return {...state, ...action}
    case pro.TOGGLESELECT:
      //避免引用類型數據,使用immutable進行數據轉換 
      imuDataList = Immutable.List(state.dataList);
      imuItem = Immutable.Map(state.dataList[action.index]);
      imuItem = imuItem.set('selectStatus', !imuItem.get('selectStatus'));
      imuDataList = imuDataList.set(action.index, imuItem);
      // redux必須返回一個新的state
      return {...state, ...{dataList: imuDataList.toJS()}};
    case pro.EDITPRODUCTION:
      //避免引用類型數據,使用immutable進行數據轉換 
      imuDataList = Immutable.List(state.dataList);
      imuItem = Immutable.Map(state.dataList[action.index]);
      imuItem = imuItem.set('selectNum', action.selectNum);
      imuDataList = imuDataList.set(action.index, imuItem);
      // redux必須返回一個新的state
      return {...state, ...{dataList: imuDataList.toJS()}};
    // 清空數據
    case pro.CLEARSELECTED:
      imuDataList = Immutable.fromJS(state.dataList);
      for (let i = 0; i < state.dataList.length; i++) {
        imuDataList = imuDataList.update(i, item => {
          item = item.set('selectStatus', false);
          item = item.set('selectNum', 0);
          return item
        })
      }
      return {...state, ...{dataList: imuDataList.toJS()}};
    default: 
      return state;
  }
}
//action-type裏面是對象
//src/store/production/action-type.js
// 保存商品數據
export const GETPRODUCTION = 'GETPRODUCTION';
// 選擇商品
export const TOGGLESELECT = 'TOGGLESELECT';
// 編輯商品
export const EDITPRODUCTION = 'EDITPRODUCTION';
// 清空選擇
export const CLEARSELECTED = 'CLEARSELECTED';
//action 裏面經過dispatch把數據發送給其餘數據
import * as pro from './action-type';
import API from '@/api/api';

// 初始化獲取商品數據,保存至redux
export const getProData = () => {
  // 返回函數,異步dispatch
  return async dispatch => {
    try{
      let result = await API.getProduction();
      result.map(item => {
        item.selectStatus = true;
        item.selectNum = 0;
        return item;
      })
      dispatch({
        type: pro.GETPRODUCTION,
        dataList: result,
      })
    }catch(err){
      console.error(err);
    }
  }
}

// 選擇商品
export const togSelectPro = index => {
  return {
    type: pro.TOGGLESELECT,
    index,
  }
}

// 編輯商品
export const editPro = (index, selectNum) => {
  return {
    type: pro.EDITPRODUCTION,
    index,
    selectNum,
  }
}

// 清空選擇
export const clearSelected = () => {
  return {
    type: pro.CLEARSELECTED,
  }
}
//src/store/home/action-type.js
// 保存表單數據
export const SAVEFORMDATA = 'SAVEFORMDATA';
// 保存圖片
export const SAVEIMG = 'SAVEIMG';
// 清空數據
export const CLEARDATA = 'CLEARDATA';
//src/store/home/action.js
import * as home from './action-type';

// 保存表單數據
export const saveFormData = (value, datatype) => {
  return {
    type: home.SAVEFORMDATA,
    value,
    datatype,
  }
}

// 保存圖片地址
export const saveImg = path => {
  return {
    type: home.SAVEIMG,
    path,
  }
}

// 保存圖片地址
export const clearData = () => {
  return {
    type: home.CLEARDATA,
  }
}
//工具函數中定義了異步組件
//src/utils/asyncComponent.jsx
import React, { Component } from "react";

export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    constructor(props) {
      super(props);

      this.state = {
        component: null
      };
    }

    async componentDidMount() {
      const { default: component } = await importComponent();

      this.setState({component});
    }

    render() {
      const C = this.state.component;

      return C ? <C {...this.props} /> : null;
    }
  }

  return AsyncComponent;
}
//路由部門,定義首頁就home頁面
//src/router/index.js
//路由配置是異步加載的
import React, { Component } from 'react';
import { HashRouter, Switch, Route, Redirect } from 'react-router-dom';
import asyncComponent from '@/utils/asyncComponent';

import home from "@/pages/home/home";
const record = asyncComponent(() => import("@/pages/record/record"));
const helpcenter = asyncComponent(() => import("@/pages/helpcenter/helpcenter"));
const production = asyncComponent(() => import("@/pages/production/production"));
const balance = asyncComponent(() => import("@/pages/balance/balance"));

// react-router4 再也不推薦將全部路由規則放在同一個地方集中式路由,子路由應該由父組件動態配置,組件在哪裏匹配就在哪裏渲染,更加靈活
export default class RouteConfig extends Component{
  render(){
    return(
      <HashRouter>
        <Switch>
          <Route path="/" exact component={home} />
          <Route path="/record" component={record} />
          <Route path="/helpcenter" component={helpcenter} />
          <Route path="/production" component={production} />
          <Route path="/balance" component={balance} />
          <Redirect to="/" />
        </Switch>
      </HashRouter>
    )
  }
}

有公共組件header和alerthtml

//header組件
//src/components/header/header.jsx
import React, { Component } from 'react';
import { is, fromJS } from 'immutable';
import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import './header.less';

export default class PublicHeader extends Component{
  static propTypes = {
    record: PropTypes.any,
    title: PropTypes.string.isRequired,
    confirm: PropTypes.any,
  }

  state = {
    navState: false, //導航欄是否顯示
  };
  
  // 切換左側導航欄狀態
  toggleNav = () => {
    this.setState({navState: !this.state.navState});
  }
  // css動畫組件設置爲目標組件
  FirstChild = props => {
    const childrenArray = React.Children.toArray(props.children);
    return childrenArray[0] || null;
  }
  shouldComponentUpdate(nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps))|| !is(fromJS(this.state),fromJS(nextState))
  }

  render(){
    return(
      <header className="header-container">
        <span className="header-slide-icon icon-catalog" onClick={this.toggleNav}></span>
        <span className="header-title">{this.props.title}</span>
        {
          this.props.record&&<NavLink to="/record" exact className="header-link icon-jilu"></NavLink>
        }
        {
          this.props.confirm&&<NavLink to="/" exact className="header-link header-link-confim">肯定</NavLink>
        }
        <ReactCSSTransitionGroup
          component={this.FirstChild}
          transitionName="nav"
          transitionEnterTimeout={300}
          transitionLeaveTimeout={300}>
            {
              this.state.navState && <aside key='nav-slide' className="nav-slide-list" onClick={this.toggleNav}>
                <NavLink to="/" exact className="nav-link icon-jiantou-copy-copy">首頁</NavLink>
                <NavLink to="/balance" exact className="nav-link icon-jiantou-copy-copy">提現</NavLink>
                <NavLink to="/helpcenter" exact className="nav-link icon-jiantou-copy-copy">幫助中心</NavLink>
              </aside>
            }
        </ReactCSSTransitionGroup>
        
      </header>
    );
  }
}
//src/components/alert/alert.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { is, fromJS } from 'immutable';
import TouchableOpacity from '@/components/TouchableOpacity/TouchableOpacity';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import './alert.less';

export default class Alert extends Component{
  static propTypes = {
    closeAlert: PropTypes.func.isRequired,
    alertTip: PropTypes.string.isRequired,
    alertStatus: PropTypes.bool.isRequired,
  }
  // css動畫組件設置爲目標組件
  FirstChild = props => {
    const childrenArray = React.Children.toArray(props.children);
    return childrenArray[0] || null;
  }
  // 關閉彈框
  confirm = () => {
    this.props.closeAlert();
  }
  
  shouldComponentUpdate(nextProps, nextState){
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  
  render(){
    return (
      <ReactCSSTransitionGroup
        component={this.FirstChild}
        transitionName="alert"
        transitionEnterTimeout={300}
        transitionLeaveTimeout={300}>
        {
          this.props.alertStatus&&<div className="alert-con">
            <div className="alert-context">
              <div className="alert-content-detail">{this.props.alertTip}</div>
              <TouchableOpacity className="confirm-btn" clickCallBack={this.confirm}/>
            </div>
          </div>
        }
      </ReactCSSTransitionGroup>
    );
  }
}
//src/pages/home/home.jsx
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { is, fromJS } from 'immutable';
import PropTypes from 'prop-types';
import API from '@/api/api';
import envconfig from '@/envconfig/envconfig';
import { saveFormData, saveImg, clearData } from '@/store/home/action';
import { clearSelected } from '@/store/production/action';
import PublicHeader from '@/components/header/header';
import PublicAlert from '@/components/alert/alert';
import TouchableOpacity from '@/components/TouchableOpacity/TouchableOpacity';
import mixin, { padStr } from '@/utils/mixin';
import './home.less';

@mixin({padStr})
class Home extends Component {
  static propTypes = {
    formData: PropTypes.object.isRequired,
    saveFormData: PropTypes.func.isRequired,
    saveImg: PropTypes.func.isRequired,
    clearData: PropTypes.func.isRequired,
    clearSelected: PropTypes.func.isRequired,
  }

  state = {
    alertStatus: false, //彈框狀態
    alertTip: '', //彈框提示文字
  }
  /**
   * 已選擇的商品數據
   * @type {Array}
   */
  selectedProList = []; 

  /**
   * 將表單數據保存至redux,保留狀態
   * @param  {string} type  數據類型 orderSum||name||phoneNo
   * @param  {object} event 事件對象
   */
  handleInput = (type, event) => {
    let value = event.target.value;
    switch(type){
      case 'orderSum':
        value = value.replace(/\D/g, '');
      break;
      case 'name':
      break;
      case 'phoneNo':
        value = this.padStr(value.replace(/\D/g, ''), [3, 7], ' ', event.target);
      break;
      default:;
    }
    this.props.saveFormData(value, type);
  }
  
  /*
  上傳圖片,並將圖片地址存到redux,保留狀態
   */
  uploadImg = async event => {
    try{
      let formdata = new FormData();
      formdata.append('file', event.target.files[0]);
      let result = await API.uploadImg({data: formdata});
      this.props.saveImg(envconfig.imgUrl + result.image_path);
      console.log(result);
    }catch(err){
      console.error(err);
    }
  }

  // 提交表單
  sumitForm = () => {
    const {orderSum, name, phoneNo} = this.props.formData;
    let alertTip = '';
    if(!orderSum.toString().length){
      alertTip = '請填寫金額';
    }else if(!name.toString().length){
      alertTip = '請填寫姓名';
    }else if(!phoneNo.toString().length){
      alertTip = '請填寫正確的手機號';
    }else{
      alertTip = '添加數據成功';
      this.props.clearSelected();
      this.props.clearData();
    }
    this.setState({
      alertStatus: true,
      alertTip,
    })
  }
  
  // 關閉彈款
  closeAlert = () => {
    this.setState({
      alertStatus: false,
      alertTip: '',
    })
  }
  
  // 初始化數據,獲取已選擇的商品
  initData = props => {
    this.selectedProList = [];
    props.proData.dataList.forEach(item => {
      if(item.selectStatus && item.selectNum){
        this.selectedProList.push(item);
      }
    })
  }

  componentWillReceiveProps(nextProps){
    if(!is(fromJS(this.props.proData), fromJS(nextProps.proData))){
      this.initData(nextProps);
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
  }

  componentWillMount(){
    this.initData(this.props);
  }
  

  render() {
    
    return (
      <main className="home-container">
        <PublicHeader title='首頁' record />
        <p className="common-title">請錄入您的信息</p>
        <form className="home-form">
          <div className="home-form-tiem">
            <span>銷售金額:</span>
            <input type="text" placeholder="請輸入訂單金額" value={this.props.formData.orderSum} onChange={this.handleInput.bind(this, 'orderSum')}/>
          </div>
          <div className="home-form-tiem">
            <span>客戶姓名:</span>
            <input type="text" placeholder="請輸入客戶姓名" value={this.props.formData.name} onChange={this.handleInput.bind(this, 'name')}/>
          </div>
          <div className="home-form-tiem">
            <span>客戶電話:</span>
            <input type="text" maxLength="13" placeholder="請輸入客戶電話" value={this.props.formData.phoneNo} onChange={this.handleInput.bind(this, 'phoneNo')}/>
          </div>
        </form>
        <div>
          <p className="common-title">請選擇銷售的產品</p>
          <Link to="/production" className="common-select-btn">
            {
              this.selectedProList.length ? <ul className="selected-pro-list">
                {
                  this.selectedProList.map((item, index) => {
                    return <li key={index} className="selected-pro-item ellipsis">{item.product_name}x{item.selectNum}</li>
                  })
                }
              </ul>:'選擇產品'
            }
          </Link>
        </div>
        <div className="upload-img-con">
          <p className="common-title">請上傳發票憑證</p>
          <div className="file-lable">
            <span className="common-select-btn">上傳圖片</span>
            <input type="file" onChange={this.uploadImg}/>
          </div>
          <img src={this.props.formData.imgpath} className="select-img" alt=""/>
        </div>
        <TouchableOpacity className="submit-btn" clickCallBack={this.sumitForm} text="提交" />
        <PublicAlert closeAlert={this.closeAlert} alertTip={this.state.alertTip} alertStatus={this.state.alertStatus} />
      </main>
    );
  }
}

export default connect(state => ({
  formData: state.formData,
  proData: state.proData,
}), {
  saveFormData, 
  saveImg,
  clearData,
  clearSelected,
})(Home);
//src/utils/mixin.js
export default methods => {
  return target => {
    Object.assign(target.prototype, methods);
  }
}

/**
 * 字符串填充函數
 * @param  {string} value      目標字符串
 * @param  {array} position 須要填充的位置
 * @param  {string} padstr   填充字符串
 * @return {string}          返回目標字符串
 */
export const padStr = (value, position, padstr, inputElement) => {
  position.forEach((item, index) => {
    if (value.length > item + index) {
      value = value.substring(0, item + index) + padstr + value.substring(item + index)
    }
  })
  value = value.trim();
  // 解決安卓部分瀏覽器插入空格後光標錯位問題
  requestAnimationFrame(() => {
    inputElement.setSelectionRange(value.length, value.length); 
  })
  return value;
}
//幫助中心
//src/pages/helpcenter/helpcenter.jsx
import React, { Component } from 'react';
import PublicHeader from '@/components/header/header'; 
import { is, fromJS } from 'immutable';
import './helpcenter.less';

export default class HelpCenter extends Component {

  shouldComponentUpdate(nextProps, nextState){
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }

  render(){
    return (
      <main>
        <PublicHeader title="幫助中心" record />
        <article className="context-con">
          <h2>介紹</h2>
          <p>本項目主要用於理解 react 和 redux 的編譯方式,以及 react + redux 之間的配合方式</p>
          <h2>技術要點</h2>
          <p>react:v16.2</p>
          <p>redux:v3.7</p>
          <p>webpack:v3.8</p>
          <p>react-router:v4.2</p>
          <p>ES 6/7/8</p>
          <p>code split</p>
          <p>hot loader</p>
          <p>axios:v0.17</p>
          <p>less:v2.7</p>
          <p>immutable:v3.8</p>
          <p>項目地址 <a href="https://github.com/bailicangdu/react-pxq">github</a></p>
        </article>
      </main>
    )
  }
}
//src/pages/production/production.jsx
import React, { Component } from 'react';
import { is, fromJS } from 'immutable';
import { connect } from 'react-redux';
import { getProData, togSelectPro, editPro } from '@/store/production/action';
import PropTypes from 'prop-types';
import PublicHeader from '@/components/header/header';
import './production.less';

class Production extends Component{
  static propTypes = {
    proData: PropTypes.object.isRequired,
    getProData: PropTypes.func.isRequired,
    togSelectPro: PropTypes.func.isRequired,
    editPro: PropTypes.func.isRequired,
  }
  
  /**
   * 添加或刪減商品,交由redux進行數據處理,做爲全局變量
   * @param  {int} index 編輯的商品索引
   * @param  {int} num   添加||刪減的商品數量
   */
  handleEdit = (index, num) => {
    let currentNum = this.props.proData.dataList[index].selectNum + num;
    if(currentNum < 0){
      return
    }
    this.props.editPro(index, currentNum);
  }
  
  // 選擇商品,交由redux進行數據處理,做爲全局變量
  togSelect = index => {
    this.props.togSelectPro(index);
  }

  shouldComponentUpdate(nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  
  componentDidMount(){
    if(!this.props.proData.dataList.length){
      this.props.getProData();
    }
  }

  render(){
    return (
      <main className="common-con-top">
        <PublicHeader title='首頁' confirm />
        <section className="pro-list-con">
          <ul className="pro-list-ul">
            {
              this.props.proData.dataList.map((item, index) => {
                return <li className="pro-item" key={index}>
                  <div className="pro-item-select" onClick={this.togSelect.bind(this, index)}>
                    <span className={`icon-xuanze1 pro-select-status ${item.selectStatus? 'pro-selected': ''}`}></span>
                    <span className="pro-name">{item.product_name}</span>
                  </div>
                  <div className="pro-item-edit">
                    <span className={`icon-jian ${item.selectNum > 0? 'edit-active':''}`} onClick={this.handleEdit.bind(this, index, -1)}></span>
                    <span className="pro-num">{item.selectNum}</span>
                    <span className={`icon-jia`} onClick={this.handleEdit.bind(this, index, 1)}></span>
                  </div>
                </li>
              })
            }
          </ul>
        </section>
      </main>
    )
  }
}


export default connect(state => ({
  proData: state.proData,
}), {
  getProData, 
  togSelectPro, 
  editPro
})(Production);

//src/pages/balance/balance.jsx
import React, { Component } from 'react';
import { is, fromJS } from 'immutable';
import PublicHeader from '@/components/header/header';
import TouchableOpacity from '@/components/TouchableOpacity/TouchableOpacity';
import PublicAlert from '@/components/alert/alert';
import API from '@/api/api';
import './balance.less';

class BrokeRage extends Component{
  state = {
    applyNum: '', //輸入值
    alertStatus: false, //彈框狀態
    alertTip: '', //彈框提示文字
    balance: {  //可提現金額
      balance: 0, 
    },
  }
  // 初始化數據
  initData = async () => {
    try{
      let result = await API.getBalance();
      console.log(result);
      this.setState({balance: result});
    }catch(err){
      console.error(err);
    }
  }
  
  /**
   * 格式化輸入數據
   * 格式爲微信紅包格式:最大 200.00
   * @param  {object} event 事件對象
   */
  handleInput = event => {
    let value = event.target.value;
    if((/^\d*?\.?\d{0,2}?$/gi).test(value)){
      if((/^0+[1-9]+/).test(value)) {
        value = value.replace(/^0+/,'');
      }
      if((/^0{2}\./).test(value)) {
        value = value.replace(/^0+/,'0');
      }
      value = value.replace(/^\./gi,'0.');
      if(parseFloat(value) > 200){
        value = '200.00';
      }
      this.setState({applyNum: value});
    }
  }
  
  /**
   * 提交判斷條件
   */
  sumitForm = () => {
    let alertTip;
    if(!this.state.applyNum){
      alertTip = '請輸入提現金額';
    }else if(parseFloat(this.state.applyNum) > this.state.balance.balance){
      alertTip = '申請提現金額不能大於餘額';
    }else{
      alertTip = '申請提現成功';
    }

    this.setState({
      alertStatus: true,
      alertTip,
      applyNum: '',
    })
  }
  
  /*
  關閉彈框
   */
  closeAlert = () => {
    this.setState({
      alertStatus: false,
      alertTip: '',
    })
  }

  shouldComponentUpdate(nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state),fromJS(nextState))
  }

  componentDidMount(){
    this.initData();
  }

  render(){
    return (
      <main className="home-container">
        <PublicHeader title='提現' record />
        <section className="broke-main-content">
          <p className="broke-header">您的可提現金額爲:¥ {this.state.balance.balance}</p>
          <form className="broke-form">
            <p>請輸入提現金額(元)</p>
            <p>¥ <input type="text" value={this.state.applyNum} placeholder="0.00" onInput={this.handleInput} maxLength="5" /></p>
          </form>
          <TouchableOpacity className="submit-btn" clickCallBack={this.sumitForm} text="申請提現" />
        </section>
        <PublicAlert closeAlert={this.closeAlert} alertTip={this.state.alertTip} alertStatus={this.state.alertStatus} />
      </main>
    );
  }
}

export default BrokeRage;

//src/pages/record/components/recordList.jsx
import React, { Component } from 'react';
import { is, fromJS } from 'immutable';
import API from '@/api/api';
import './recordList.less';

class RecordList extends Component{
  
  state = {
    recordData: [],
  }
  
  /**
   * 初始化獲取數據
   * @param  {string} type 數據類型
   */
  getRecord = async type => {
    try{
      let result = await API.getRecord({type});
      this.setState({recordData: result.data||[]})
    }catch(err){
      console.error(err);
    }
  }

  componentWillReceiveProps(nextProps){
    // 判斷類型是否重複
    let currenType = this.props.location.pathname.split('/')[2];
    let type = nextProps.location.pathname.split('/')[2];
    if(currenType !== type){
      this.getRecord(type);
    }
  }

  shouldComponentUpdate(nextProps, nextState){
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }

  componentWillMount(){
    let type = this.props.location.pathname.split('/')[2];
    this.getRecord(type);
  }

  render(){
    return (
      <div>
        {/* 這個記錄頁面與數據的渲染造成總體的組件 */}
        <ul className="record-list-con">
        {
          this.state.recordData.map((item, index) => {
            return <li className="record-item" key={index}>
              <section className="record-item-header">
                <span>建立時間:{item.created_at}</span>
                <span>{item.type_name}</span>
              </section>
              <section className="record-item-content">
                <p><span>用戶名:</span>{item.customers_name} &emsp; {item.customers_phone}</p>
                <p><span>商&emsp;品:</span>{item.product[0].product_name}</p>
                <p><span>金&emsp;額:</span>{item.sales_money} &emsp; 佣金:{item.commission}</p>
              </section>
              <p className="record-item-footer">等待管理員審覈,審覈經過後,佣金將結算至帳戶</p>
            </li>
          })
        }
        </ul>
      </div>
    );
  }

}

export default RecordList;

//src/pages/record/record.jsx
import React, { Component } from 'react';
import { is, fromJS } from 'immutable';
import { NavLink, Switch, Route, Redirect } from 'react-router-dom';
import PublicHeader from '@/components/header/header';
import RecordList from './components/recordList';
import './record.less';

class Record extends Component {
  state = {
    flagBarPos: '17%',
  }
  /**
   * 設置頭部底部標籤位置
   * @param  {string} type 數據類型
   */
  setFlagBarPos = type => {
    let flagBarPos;
    switch(type){
      case 'passed':
        flagBarPos = '17%';
      break;
      case 'audited':
        flagBarPos = '50%';
      break;
      case 'failed':
        flagBarPos = '83%';
      break;
      default: 
        flagBarPos = '17%';
    }
    this.setState({flagBarPos})
  }

  componentWillReceiveProps(nextProps){
    // 屬性變化時設置頭部底部標籤位置
    let currenType = this.props.location.pathname.split('/')[2];
    let type = nextProps.location.pathname.split('/')[2];
    if(currenType !== type){
      this.setFlagBarPos(type);
    }
  }

  shouldComponentUpdate(nextProps, nextState){
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  
  componentWillMount(){
    // 初始化設置頭部底部標籤位置
    let type = this.props.location.pathname.split('/')[2];
    this.setFlagBarPos(type);
  }
  render() {
    return (
      <main className="common-con-top">
        <PublicHeader title='記錄' />
        <section className="record-nav-con">
          <nav className="record-nav">
            <NavLink to={`${this.props.match.path}/passed`} className="nav-link">已經過</NavLink>
            <NavLink to={`${this.props.match.path}/audited`} className="nav-link">待審覈</NavLink>
            <NavLink to={`${this.props.match.path}/failed`} className="nav-link">未經過</NavLink>
          </nav>
          <i className="nav-flag-bar" style={{left: this.state.flagBarPos}}></i>
        </section>
        {/* 子路由在父級配置,react-router4新特性,更加靈活 */}
        <Switch>
          <Route path={`${this.props.match.path}/:type`} component={RecordList} />
          <Redirect from={`${this.props.match.path}`} to={`${this.props.match.path}/passed`} exact component={RecordList} />
        </Switch>
      </main>
    );
  }
}

export default Record;
相關文章
相關標籤/搜索