【心有猛虎】react-pxq
这是一个比较完整的简单的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}   {item.customers_phone}</p>
<p><span>商 品:</span>{item.product[0].product_name}</p>
<p><span>金 额:</span>{item.sales_money}   佣金:{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的更多相关文章
- react组件的生命周期
写在前面: 阅读了多遍文章之后,自己总结了一个.一遍加强记忆,和日后回顾. 一.实例化(初始化) var Button = React.createClass({ getInitialState: f ...
- 十分钟介绍mobx与react
原文地址:https://mobxjs.github.io/mobx/getting-started.html 写在前面:本人英语水平有限,主要是写给自己看的,若有哪位同学看到了有问题的地方,请为我指 ...
- RxJS + Redux + React = Amazing!(译一)
今天,我将Youtube上的<RxJS + Redux + React = Amazing!>翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: https:/ ...
- React 入门教程
React 起源于Facebook内部项目,是一个用来构建用户界面的 javascript 库,相当于MVC架构中的V层框架,与市面上其他框架不同的是,React 把每一个组件当成了一个状态机,组件内 ...
- 通往全栈工程师的捷径 —— react
腾讯Bugly特约作者: 左明 首先,我们来看看 React 在世界范围的热度趋势,下图是关键词“房价”和 “React” 在 Google Trends 上的搜索量对比,蓝色的是 React,红色的 ...
- 2017-1-5 天气雨 React 学习笔记
官方example 中basic-click-counter <script type="text/babel"> var Counter = React.create ...
- RxJS + Redux + React = Amazing!(译二)
今天,我将Youtube上的<RxJS + Redux + React = Amazing!>的后半部分翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: ht ...
- React在开发中的常用结构以及功能详解
一.React什么算法,什么虚拟DOM,什么核心内容网上一大堆,请自行google. 但是能把算法说清楚,虚拟DOM说清楚的聊聊无几.对开发又没卵用,还不如来点干货看看咋用. 二.结构如下: impo ...
- React的使用与JSX的转换
前置技能:Chrome浏览器 一.拿糖:React的使用 React v0.14 RC 发布,主要更新项目: 两个包: React 和 React DOM DOM node refs 无状态的功能 ...
- Vue.js 2.0 和 React、Augular等其他框架的全方位对比
引言 这个页面无疑是最难编写的,但也是非常重要的.或许你遇到了一些问题并且先前用其他的框架解决了.来这里的目的是看看Vue是否有更好的解决方案.那么你就来对了. 客观来说,作为核心团队成员,显然我们会 ...
随机推荐
- 自动化运维工具Ansible工具
目录 一.初识Ansible 二.Ansible的架构 三.Ansible基础使用 安装 主机清单 管理主机 四.Ansible用脚本管理主机 五.Ansible模块Module 六.Ansible常 ...
- 吴恩达《机器学习》课程总结(5)_logistic回归
Q1分类问题 回归问题的输出可能是很大的数,而在分类问题中,比如二分类,希望输出的值是0或1,如何将回归输出的值转换成分类的输出0,1成为关键.注意logistics回归又称 逻辑回归,但他是分类问题 ...
- python—时间与时间戳之间的转换
python-时间与时间戳之间的转换 对于时间数据,如2016-05-05 20:28:54,有时需要与时间戳进行相互的运算,此时就需要对两种形式进行转换,在Python中,转换时需要用到time模块 ...
- Docker镜像之commit
利用 commit 理解镜像构成 基础知识 镜像是容器的基础,每次执行 docker run 的时候都会指定哪个镜像作为容器运行的基础.在之前的例子中,我们所使用的都是来自于 Docker Hub 的 ...
- python多进程,进程池,数据共享,进程通信,分布式进程
一.操作系统中相关进程的知识 Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊.普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前 ...
- Luogu P1730 最小密度路径(最短路径+dp)
P1730 最小密度路径 题面 题目描述 给出一张有 \(N\) 个点 \(M\) 条边的加权有向无环图,接下来有 \(Q\) 个询问,每个询问包括 \(2\) 个节点 \(X\) 和 \(Y\) , ...
- Codeforces 360A(找性质)
反思 写一写可以发现上限不断更新 一直在想怎么判断NO,刻板拘泥于错误的模型,想要像往常一样贪心地.读入当前值就能判断会不会NO,实际上只要构造完以后,最后把所有操作重新跑一遍看会不会冲突即可判断NO ...
- linux上源码安装python
Linux安装Python2.7 以下例子基于python 2.7.9,其他版本同理.# 1.下载python# wget https://www.python.org/ftp/python/2.7. ...
- js校验文本框只能输入数字(包括小数)
form表单 <form method="POST" action=""> <input type="text" id=& ...
- Django运行错误常见问题及解决方法1
如果不是在JetBrains PyCharm 2017.2里创建的想在JetBrains PyCharm 2017.2里运行.可以在 编辑结构 进行配置正常使用