这是一个比较完整的简单的react移动端项目,说起来页面少,其实,构思若是精巧,也并不容易做

先放源码:https://github.com/bailicangdu/react-pxq







接下来我们简单的看看代码

项目有用到react-redux和axios处理数据

//根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和alert

//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;

【心有猛虎】react-pxq的更多相关文章

  1. react组件的生命周期

    写在前面: 阅读了多遍文章之后,自己总结了一个.一遍加强记忆,和日后回顾. 一.实例化(初始化) var Button = React.createClass({ getInitialState: f ...

  2. 十分钟介绍mobx与react

    原文地址:https://mobxjs.github.io/mobx/getting-started.html 写在前面:本人英语水平有限,主要是写给自己看的,若有哪位同学看到了有问题的地方,请为我指 ...

  3. RxJS + Redux + React = Amazing!(译一)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: https:/ ...

  4. React 入门教程

    React 起源于Facebook内部项目,是一个用来构建用户界面的 javascript 库,相当于MVC架构中的V层框架,与市面上其他框架不同的是,React 把每一个组件当成了一个状态机,组件内 ...

  5. 通往全栈工程师的捷径 —— react

    腾讯Bugly特约作者: 左明 首先,我们来看看 React 在世界范围的热度趋势,下图是关键词“房价”和 “React” 在 Google Trends 上的搜索量对比,蓝色的是 React,红色的 ...

  6. 2017-1-5 天气雨 React 学习笔记

    官方example 中basic-click-counter <script type="text/babel"> var Counter = React.create ...

  7. RxJS + Redux + React = Amazing!(译二)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>的后半部分翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: ht ...

  8. React在开发中的常用结构以及功能详解

    一.React什么算法,什么虚拟DOM,什么核心内容网上一大堆,请自行google. 但是能把算法说清楚,虚拟DOM说清楚的聊聊无几.对开发又没卵用,还不如来点干货看看咋用. 二.结构如下: impo ...

  9. React的使用与JSX的转换

    前置技能:Chrome浏览器   一.拿糖:React的使用 React v0.14 RC 发布,主要更新项目: 两个包: React 和 React DOM DOM node refs 无状态的功能 ...

  10. Vue.js 2.0 和 React、Augular等其他框架的全方位对比

    引言 这个页面无疑是最难编写的,但也是非常重要的.或许你遇到了一些问题并且先前用其他的框架解决了.来这里的目的是看看Vue是否有更好的解决方案.那么你就来对了. 客观来说,作为核心团队成员,显然我们会 ...

随机推荐

  1. 解决IDEA中,maven依赖不自动补全的问题

    转载: 作者:七个榴莲链接:https://www.jianshu.com/p/46a423bdde31来源:简书 遇到的问题:Maven依赖不自动补全 在idea上使用maven插件时,发现在pom ...

  2. JZOJ5894【NOIP2018模拟10.5】同余方程

    题目 Description

  3. 【python之路45】tornado的用法 (三)

    参考:https://www.cnblogs.com/sunshuhai/articles/6253815.html 一.cookie用法补充 1.cookie的应用场景 浏览器端保存的键值对,每次访 ...

  4. Tuxera ntfs软件如何删除干净

    sudo /Library/Filesystems/fusefs_txantfs.fs/Contents/Resources/Support/uninstall-package.sh

  5. SQL有意思的面试题

    1.中软国际 SQL行转列  变成   --数据准备create table t_test( year int, month int, sale int, primary key (year, mon ...

  6. Asp.Net 应用程序在IIS发布后无法连接oracle数据库问题的解决方法

    asp.net程序编写完成后,发布到IIS,经常出现的一个问题是连接不上Oracle数据库,具体表现为Oracle的本地NET服务配置成功:用 pl/sql 等工具也可以连接上数据库,但是通过浏览器中 ...

  7. BM线性递推

    #include<bits/stdc++.h> using namespace std; #define rep(i,a,n) for (int i=a;i<n;i++) #defi ...

  8. java 用户注册登陆Demo

    一个用户注册登陆的系统,用到了MD5加密处理密码,实现了一个简单的数据库连接池connectionPool, 实现了注册,登陆,登陆之后修改用户信息等功能,非常适合初学者 一.准备工作 数据库:MyS ...

  9. Hibernate命名策略

    hibernate的命名策略,可以减少对数据库标识符命名的维护,进一步减少这部份命名的重复性代码量,以提高维护. hibernate的命名方式,有两类,一类是显式命名,一类是隐式命名. 显式命名:在映 ...

  10. sqlserver数据往别的数据库迁移

    背景:sqlserver数据库数据是服务于一个.net项目.后来由于去.net 要将数据前往高斯数据库 或者Oracle. 问题是 当sql Name Age Gender from student ...