0.1.18 • Published 6 years ago

gsp-react v0.1.18

Weekly downloads
1
License
ISC
Repository
github
Last release
6 years ago

React组件使用说明

Build Status

TOC

Release notes

  • 0.1.18

    改进了下拉刷新和上划加载组件

Install and start

👉 组件包Demo http://45.63.37.8:8080/

  • 安装组件包
npm install gsp-react --save
  • 组件包演示程序
git clone https://github.com/xuyl0104/React-Framework.git
npm install
npm start
http://localhost:3000

Layout

页面布局基于Bootstrap v4,采用Flex布局排版技术。

Container

  • Container组件设定了flex排列方式。

    <div className="w-100 d-flex flex-column" style={{height: '100vh'}}>
        {this.renderChildren(this.props)}
    </div>
  • Container组件需要包裹页面中的其他元素(当使用页面切换效果组件PageTransition时,Container须位于切换组件内部)。

Content

Content组件包裹页面中主体内容部分(即Header、Footer之外的部分)。

属性描述默认值类型
padding内边距0, 0, 0, 0top, right, bottom, left[]
bgColor背景颜色'#f8f9fa'string
<PageTransition>
	<Container>
		<Header></Header>
		<Content>
			...
		</Content>
		<Footer></Footer>
	</Container>
</PageTransition>

详情请在演示程序中点击进入PageTemplate页面查看。

Components

PageTransition

  • PageTransition页面切换过渡动画需要与第三方组件react-router-page-transition结合使用

    npm install react-router-page-transition --save
  • 页面切换实现步骤

    1. 添加react-router-page-transition到Router,设定timeoutlocation;须使用React-router-dom中的Switch组件。

      <Router>
          <Route
              render={({location}) => (
                  <PageTransition timeout={500}>
                      <Switch location={location}>
                            <Route exact path="/" component={ComponentList}/>
                            <Route path="/PageTransitionDeatils" component={PageTransitionDeatils}/>
                            <Route path="/test" component={Test}/>
                            <Route path="/test2" component={Test2}/>
                            <Route path="/test3" component={Test3}/>
                      </Switch>
                  </PageTransition>
           )}/>
      </Router>
    2. 编写动画切换效果

      CSS/transition-main.css (list-page、detail-page等类名可以自定义)

      第一个页面ComponentList设定为list-page ,之后的页面设定为detail-page

    3. 每个页面添加我们编写的PageTransition组件

      设定PageTransition的transitionClassdirection属性

      <PageTransition transitionClass={"detail-page"} direction={this.state.className}>
          <Container>
              <Header name="PageTransition" 
                  onLeftArrowClick={this.onLeftArrowClick.bind(this)}>
              </Header>
              <Content>
                  <Row>
                      <Button style={"primary"} size="lg" text={"点击测试翻页效果"} col={12} onClick={this.goToSeeDetails.bind(this)} />
                  </Row>
              </Content>
          </Container>
      </PageTransition>
      属性描述默认值类型
      transitionClass本页面的CSS类名string
      direction动画方向""string
    4. 编写React生命周期函数,实现动画方向的正确设定

      假如有四个界面A、B、C、D

      A <—> B <—> C <—>D

      B、C作为中间界面,需要编写生命周期函数进行方向调整:

      /**
       * 该方法用于中间页面中(如A->B->C->D 时,用于B,C页面),用于判断中间页面的appear动画方向
       * 当该中间页面是因为路由POP操作出现,则执行detail-page-reverse的appear;
       * 否则(被PUSH进路由history),执行detail-page的appear。
       */
      componentWillMount() {
          let middle = this.props.history.action === "POP" ? "-reverse" : "";
          this.setState({
              className: middle
          });
      }
      /**
       * 根据当前页面的路由动作,设定当前页面执行的leave动画方向
       * POP:detail-page的leave方向
       * REPLACE:detail-page的leave方向
       * PUSH:detail-page-reverse的leave方向
       * @param {*} nextProps 
       */
      componentWillReceiveProps(nextProps) {
          // 后退的时候,直接pop最上面的page
          if (nextProps.history.action === 'POP') {
              this.setState({
                  className: ""
              });
          } else {
              if (nextProps.history.action === 'REPLACE') {
                  this.setState({
                      className: ""
                  });
              }
              // 跳转新页面的时候,push
              this.setState({
                  className: "-reverse"
              });
          }
      }

      npm.io

Header

  • Header组件包含左侧返回按钮、中间标题、右侧按钮
  • Header组件根据右侧按钮的种类,分为右侧无按钮、右侧一个按钮、右侧多个按钮
  • 可以根据实际需要,在Header内部嵌套不同图标,实现不同功能。该方法易于对不同图标设定相应的调用方法
属性描述默认值类型
name标题string
onLeftArrowClick返回按钮调用方法func
内部child元素内嵌元素React elem
import { Header } from 'gsp-react';
<Header name="Header" 
    onLeftArrowClick={this.onLeftArrowClick.bind(this)}>
</Header>
<Header name="Header" 
    onLeftArrowClick={this.onLeftArrowClick.bind(this)}>
    <img src={require("../images/add.png")} style={{width: '25px', height: '25px'}} 
        alt="" className="pull-right"
        onClick={this.props.onLeftArrowClick}></img>
</Header>
<Header name="Header" 
    onLeftArrowClick={this.onLeftArrowClick.bind(this)}>
    <img src={require("../images/add.png")} style={{width: '25px', height: '25px'}} alt="" className="pull-right" onClick={this.props.onLeftArrowClick}></img>
    <div className="ml-2"><Icon key="1" type="ellipsis" size={'md'}/></div>
</Header>
npm.ionpm.ionpm.io
右侧无按钮右侧一个按钮右侧若干按钮

Footer

  • Footer组件可以包含不同数量的按钮。用户可设定footer的按钮数量为1、2、3…(建议不超过3)
  • 每个按钮显示的文字、调用的方法、按钮的样式均可以自由设定
  • 提供了两种高度的footer供用户选择
属性描述默认值类型
buttonName按钮名称数组[]: string
callBackFooterButtonClick按钮调用方法数组[]: func
style按钮样式数组[]: object
size按钮高度"lg"string ("lg", "sm")
import { Footer } from 'gsp-react';
<Footer size="sm"
    style={[{'color': 'white', 'backgroundColor': '#318ccf'}]}
    buttonName={["下单"]}
    callBackFooterButtonClick={[
        this.callBackFooter0]}>
</Footer>
<Footer size="lg"
    style={[{'color': '#318ccf', 'backgroundColor': '#ffffff'}, 
            {'color': 'white', 'backgroundColor': '#318ccf'},
            {'color': '#318ccf', 'backgroundColor': '#ffffff'}]}
    buttonName={["取消", "删除", "确定"]}
    callBackFooterButtonClick={[
        this.callBackFooter0, 
        this.callBackFooter1,
        this.callBackFooter2
        ]}>
</Footer>
npm.ionpm.ionpm.io
npm.ionpm.ionpm.io
一个按钮(lg, sm)两个按钮(lg, sm)三个按钮(lg, sm)

Button

Button组件根据Bootstrap v4的Button进行封装。

属性描述默认值类型
bstyle按钮样式"primary"string ("primary", "secondary", "success", "danger", "warning", "info", "light", "dark")
size按钮大小"lg"string ("lg", "sm")
text按钮文字string
col按钮所占colnum (12, 6, 4, 3)
onClick调用方法func
newStylestyle={"new"}时设定object,例如 {color: 'white', backgroundColor: '#318ccf'}
import { Button } from 'gsp-react';
<Button bstyle={"primary"} size="lg" text={"col-12"} col={12} onClick={this.buttonClick.bind(this)} />
<Button bstyle={"primary"} size="lg" text={"col-6"} col={6} onClick={this.buttonClick.bind(this)} />
<Button bstyle={"default"} size="lg" text={"col-6"} col={6} onClick={this.buttonClick.bind(this)}/>
<Button bstyle={"success"} size="" text={"col-4"} col={4} onClick={this.buttonClick.bind(this)} />
<Button bstyle={"warning"} size="" text={"col-4"} col={4} onClick={this.buttonClick.bind(this)} />
<Button bstyle={"danger"} size="" text={"col-4"} col={4} onClick={this.buttonClick.bind(this)}/>
<Button bstyle={"primary"} size="sm" text="col-3" col={3} onClick={this.buttonClick.bind(this)} />
<Button bstyle={"primary"} size="sm" text="col-3" col={3} onClick={this.buttonClick.bind(this)} />
<Button bstyle={"primary"} size="sm" text="col-3" col={3} onClick={this.buttonClick.bind(this)} />
<Button bstyle={"primary"} size="sm" text="col-3" col={3} onClick={this.buttonClick.bind(this)} />
<Button bstyle={"new"} newStyle={{color: 'white', backgroundColor: '#318ccf'}} size="sm" text="自定义" col={12} onClick={this.buttonClick.bind(this)} />

npm.io

Input

属性描述默认值类型
label左侧描述性label信息string
text输入框内的内容string
placeholderplaceholderstring
align对其方式"left"string ("left", "right")
clear是否带有清空按钮bool
onChange输入时调用的方法func
内部child组件嵌套的内部组件React elem
name绑定数据表中的名为name的字段string
required是否必填falseboolean

==name属性需与数据表中的数据属性对应== 📌

import { Input } from 'gsp-react';
this.state = {
    info: [{"a": "", "b": "", "c": "", "d": "", "e": "", "f": ""}]
};
<Input label={"左对齐带清空"} name={"a"} text={this.state.info[0]["a"]} onChange={this.onTextChange} placeholder={"姓名"} align={"left"} clear={true} />
<Input label={"上级审批人"} name={"b"} text={this.state.info[0]["b"]} onChange={this.onTextChange} placeholder={"上级审批人姓名"} align={"left"} />
<Input label={"左对齐带图标"} name={"c"} text={this.state.info[0]["c"]} onChange={this.onTextChange} placeholder={"审批意见"} align={"left"} 
    img={<Icon type="calendar" />}/>
<Input label={"右对齐带清空"} name={"d"} text={this.state.info[0]["d"]} onChange={this.onTextChange} placeholder={"请输入金额"} align={"right"} clear={true}/>
<Input label={"交易金额"} name={"e"} text={this.state.info[0]["e"]} onChange={this.onTextChange} placeholder={"请输入金额"} align={"right"} 
    img={<Icon type="right" />}/>
<Input label={"交易金额"} name={"f"} text={this.state.info[0]["f"]} onChange={this.onTextChange} placeholder={"请输入金额"} align={"right"} 
    img={<Icon type="pay-circle-o" />}/>
onTextChange(e) {
    let info = this.state.info;
    info[0][e.target.name] = e.target.value;
    this.setState({
        info: info
    });
}

npm.io

Message

消息提示Message组件完全基于antdMessage组件和antd-mobileToast组件,将Message和Toast的调用进行了简单的封装,导出为 ShowMessageShowToast两个方法,易于调用。

  • Message
属性描述默认值类型
typeMessage的类型String(success, fail, info, warning)
textMessage内容String
durationMessage显示时长(秒)2num
positionMessage显示在屏幕的位置(距顶部的像素数)70num
  • Toast
属性描述默认值类型
typeToast的类型String(success, fail, offline,loading)
textToast的内容String
durationToast显示时长2num
import { ShowMessage } from "gsp-react";
import { ShowToast } from 'gsp-react';

<Button style={"primary"} size="lg" text="info" col={12} onClick={() => ShowMessage("info", "这是一条消息", 2)}/>

<Button style={"primary"} size="lg" text={"success"} col={6} onClick={() => ShowToast("success", "加载成功")}/>

Modal

Modal组件是弹出的对话框及输入框,基于antd-mobileModal组件开发,导出为ShowModal方法。

属性描述默认值类型
modeModal类型string ("alert", "prompt")
title标题string
message提示信息string
actionArr按钮文字及绑定的方法[]: {text, onPress}
optionmode为"prompt"时可以设置,用于设置输入框的默认值string ("default")
defaultValuemode为"prompt"时可以设置,输入框的默认值string
import { ShowModal } from 'gsp-react';
<Button style={"primary"} size="lg" text="普通提示框" col={12} 
    onClick={
        () => {ShowModal("alert", 
            "这是一个提示框", 
            "确定要删除?", 
            [
                { text: 'Button1', onPress: () => this.callback1() },
                { text: 'Button2', onPress: () => this.callback2() },
            ]
            )
        }
    }
/>
<Button style={"success"} size="lg" text="普通输入框" col={12} 
    onClick={
        () => {ShowModal("prompt", 
            "这是一个输入框", 
            "请输入要导出的邮箱", 
            [
                { text: '取消', onPress: () => this.callback1() },
                { text: '确认', onPress: (pswd) => this.callback4(pswd) },
            ]
            )
        }
    }
/>
<Button style={"success"} size="lg" text="带默认值输入框" col={12} 
    onClick={
        () => {ShowModal("prompt", 
            "这是一个输入框", 
            "请输入要导出的邮箱", 
            [
                { text: '取消', onPress: () => this.callback1() },
                { text: '确认', onPress: (pswd) => this.callback4(pswd) },
            ], 'default', 'abc@inspur.com'
            )
        }
    }
/>
npm.ionpm.ionpm.ionpm.io
两个按钮的提示框多个按钮的提示框普通输入框带默认值的输入框

Card

Card组件用于设计页面中的卡片元素以更好地展示内容。

Card组件基于Bootstrap v4的Media-object 设计,可以制作美观的卡片header部分,并在下方嵌套所需的其他组件。

属性描述默认值类型
avatar头像<img>
positionheader在Card中的位置"top"string ("top", "bottom")
titleheader标题string
textHeader内容String
onClick点击卡片的调用方法func
内部child组件卡片header下方的其他内容React elem
topRightheader右上方显示内容React elem
bottomRightheader右下方显示的内容React elem
middleLeftheader中间行左侧的内容React elem
middleRightheader中间行右侧的内容React elem
width卡片所占的宽度“100%”string
padding卡片内边距"8px"string ("6px 5px 6px 5px")
margin卡片外边距"0"string ("6px 5px 6px 5px")

解释

npm.io

import { Card } from 'gsp-react';
<label>Example-1:餐厅卡片</label>
<Card key={3}
    avatar={<img className={`align-self-center mr-2`} 
                src={require("../images/canteen.jpg")} alt="image" 
                style={{'width': `120px`}}/>}
    // avatarSize={200}
    avatarPosition={'start'}
    title={"舜华餐厅(S05负一楼)"}
    text={"点菜时间:周一至周五下午4点前。"}
    onClick={this.goCardDetails.bind(this)}>
</Card>
<label>Example-2:仿微信消息卡片</label>
<Card key={7}
    avatar={<img className={`align-self-center mr-1`} 
                src={require("../images/avatar2.jpg")} alt="image" 
                style={{'width': `60px`, borderRadius: '5px'}}/>}
    // avatarSize={200}
    avatarPosition={'start'}
    title={"张三"}
    text={"马上到家!"}
    topRight={<label>17:31:12</label>}
    bottomRight={<Icon type="star-o" />}
    onClick={this.goCardDetails.bind(this)}>
</Card>
<label>Example-3:机票申请</label>
<Card key={2}
    avatar={<img className={`align-self-center mr-3`} 
                src={require("../images/avatar.png")} alt="image" 
                style={{'width': `54px`}}/>}
    // avatarSize={54}
    avatarPosition={'start'}
    title={"张经理"}
    text={"平台与技术部"}
    onClick={this.goCardDetails.bind(this)}
>
    <div className="ticketInfo">
      <label>起止城市:</label> <label>济南 - 成都</label>
    </div>
    <div className="ticketInfo">
      <label>航班信息:</label> <label>2018-01-26 ca4527 经济舱</label>
    </div>
    <div className="ticketInfo">
      <label>出票时间:</label> <label>2018-01-22</label>
    </div>
</Card>
<label>Example-4: 复杂卡片</label>
<Card key={1}
    avatar={<img className={`align-self-start mr-3`} 
                src={require("../images/avatar.png")} alt="Generic placeholder image" 
                style={{'width': `54px`}}/>}
    // avatarSize={54}
    avatarPosition={'start'}
    title={"美食频道"}
    text={"2018/01/26"}
    onClick={this.goCardDetails.bind(this)}
>
    <p>
        每到年终岁尾的新年晚宴,中国人总得有点讲究的内容,比如说一定要有鱼,
        就是要年年有余。今天给大伙儿准备的就是一道不光有余,
        还天长地久的私房菜,蒜烧海鳗鱼。
    </p>

    <div style={{'display': 'inline-table'}}>
        <img src={require("../images/timg.jpg")} alt="" style={{'width': '200px'}}/>
    </div>
    <div className="d-flex justify-content-around mt-2">
        <div className="text-center" onClick={this.thumbsUp.bind(this)}>
            <Icon type="heart-o" style={{ fontSize: 26, color: '#318ccf'}}/>
        </div>
        <div className="text-center" onClick={this.shareToWeibo.bind(this)}>
            <Icon type="weibo-circle" style={{ fontSize: 26, color: '#318ccf' }}/>
        </div>
        <div className="text-center" onClick={this.shareToWeChat.bind(this)}>
            <Icon type="wechat" style={{ fontSize: 26, color: '#318ccf' }}/>
        </div>
        <div className="text-center" onClick={this.editForm.bind(this)}>
            <Icon type="form" style={{ fontSize: 26, color: '#318ccf' }}/>
        </div>
    </div>
</Card>
<label>Example-5:仿Youtube卡片(图片在上方)</label>
<Card key={9} position={"below"}
    avatar={<img className={`align-self-start mr-1 mt-1`} 
                src={require("../images/avatar.jpg")} alt="Generic placeholder image" 
                style={{'width': `48px`, borderRadius: '24px'}}/>}
    // avatarSize={200}
    avatarPosition={'start'}
    title={"Stephen Curry UNREAL 44 Pts, 14-19 FG 2018.02.22 Golden States Warrious vs LA Clippers"}
    text={<div style={{fontSize: '10px', color: 'grey'}}>FreeDawkings · 17万次观看 · 20小时前</div>}
    topRight={
        <div className="text-center" onClick={this.thumbsUp.bind(this)}>
            <Icon type="heart-o" style={{ fontSize: 18, color: 'grey'}}/>
        </div>}
    onClick={this.goCardDetails.bind(this)}
>
    <div className="mb-1" style={{'display': 'inline-table'}}>
        <img src={require("../images/nba.jpg")} alt="" style={{'width': '100%', height: '200px'}}/>
    </div>
</Card>
<label>Example-6:仿Gmail卡片</label>
<Card key={10}
    avatar={<img className={`align-self-start mr-1 mt-2`} 
                src={require("../images/avatar.jpg")} alt="Generic placeholder image" 
                style={{'width': `48px`, borderRadius: '24px'}}/>}
    // avatarSize={200}
    avatarPosition={'start'}
    title={<div style={{fontSize: '15px', color: '#000000'}}>Google安全中心</div>}
    text={<div style={{fontSize: '10px', color: 'grey'}}>您的账号在新设备上有登陆行为,请注意。</div>}
    topRight={<label>17:31:12</label>}
    bottomRight={<Icon type="star-o" />}
    middleLeft={<div style={{fontSize: '10px', color: '#000000'}}>您的账号有风险!</div>}
    onClick={this.goCardDetails.bind(this)}
>
</Card>
<label>Example-9:仿Medium(无头像且上方有图片卡片)</label>
<Card key={13} position="bottom"
    title={<div style={{fontSize: '21px', color: '#000000', fontFamily: 'Lucida Grande'}}>The Singular Pursuit of Comrade Bezos</div>}
    middleLeft={<div style={{fontSize: '16px', color: 'rgba(0, 0, 0, 0.54)', fontFamily: 'Lucida Grande'}}>Is Amazon’s plan to increase our efficiency a good thing?</div>}
    text={
        <div>
            <span className="mr-2" style={{fontSize: '12px', color: 'grey', fontFamily: 'Lucida Grande'}}>New York Magazine</span>
            <Icon type="like-o" style={{ fontSize: 16, color: 'grey'}}/>
        </div>
    }
    onClick={this.goCardDetails.bind(this)}
>
    <div className="mb-1" style={{'display': 'inline-table'}}>
        <img src={require("../images/jeff-bezos.jpeg")} alt="" style={{'width': '100%', height: '190px'}}/>
    </div>
</Card>
<label>横向滑动卡片列表</label>
<div className="horizontalSlide">
    <div className="wrapper d-inline-flex">
        <Card key={14} position="bottom" width={"40%"} margin={"0 5px 0 0"}
            title={<div style={{fontSize: '21px', color: '#000000', fontFamily: 'Lucida Grande'}}>Larry</div>}
            text={
                <div>
                    <span className="mr-2" style={{fontSize: '12px', color: 'grey', fontFamily: 'Lucida Grande'}}>New York Magazine</span>
                    <span className="mr-2" style={{fontSize: '12px', color: 'grey', fontFamily: 'Lucida Grande'}}>17:31:12</span>
                    <Icon type="like-o" style={{ fontSize: 16, color: 'grey'}} onClick={this.thumbsUp.bind(this)}/>
                </div>
            }
            onClick={this.goCardDetails.bind(this)}
        >
            <div className="mb-1" style={{'display': 'inline-table'}}>
                <img src={require("../images/larry.jpg")} alt="" style={{'width': '100%'}}/>
            </div>
        </Card>
         .
         .
         .
    </div>
</div>
npm.ionpm.ionpm.ionpm.ionpm.io
餐厅卡片仿微信卡片机票申请卡片复杂卡片仿Medium卡片
npm.ionpm.ionpm.ionpm.ionpm.io
仿YouTube卡片1仿YouTube卡片2仿Gmail卡片无头像卡片卡片width不为100%(可横向滑动)

Picker

目前只有时间选择器。

时间选择器基于react-mobile-datepicker 开发,可以对YYYY、MM、DD、hh、mm进行选择。(计划在未来使用antd-mobile的时间选择器)

属性描述默认值类型
value时间控件的值object: Date()
isOpen是否显示选择器falsebool
onSelect点击“完成”调用的方法func
onCancel点击“取消”调用的方法Func
dateFormat时间格式[]: string
showFormat显示在选择器上方的事件字符样式string
theme样式主题"android"string ("android", "ios"),推荐"android"
min最小时间object: Date()
import { Picker } from 'gsp-react';
<label>日期时间DateTime</label>
<Listview text={"时间"} onClick={this.handleClick1.bind(this)}>
    <label onClick={this.handleClick1.bind(this)}>{this.state.timestring1}			</label>
    <div className="pt-2"><Icon type="right" size={'lg'}/></div>
</Listview>
<div>
    <DatePicker
        value={this.state.time1}
        isOpen={this.state.isOpen1}
        onSelect={this.handleSelect1.bind(this)}
        onCancel={this.handleCancel1.bind(this)}
        dateFormat={['YYYY', 'MM', 'DD', 'hh', 'mm']}
        showFormat={'YYYY-MM-DD hh:mm'}
        theme={'android'}
    />
</div>
<label>日期Date</label>
<Listview text={"借款日期"} onClick={this.handleClick3.bind(this)}>
    <label onClick={this.handleClick3.bind(this)}>{this.state.timestring3}</label>
    <div className="pt-2"><Icon type="right" size={'lg'}/></div>
</Listview>
<div>
    <DatePicker
        value={this.state.time3}
        isOpen={this.state.isOpen3}
        onSelect={this.handleSelect3.bind(this)}
        onCancel={this.handleCancel3.bind(this)}
        dateFormat={['YYYY', 'MM', 'DD']}
        showFormat={'YYYY-MM-DD'}
        theme={'android'}
        min={this.state.time}
    />
</div>
<label>起止时间</label>
<Listview text={"起止时间"}>
    <input type="text" value={this.state.timestring5} placeholder={"起始时间"}
        onClick={this.handleClick5.bind(this)} readOnly="true"/>
    <div className="pt-2 ml-2 mr-2"><Icon type="arrow-right" size={'lg'}/></div>
    <input type="text" value={this.state.timestring6} placeholder={"结束时间"}
        onClick={this.handleClick6.bind(this)} readOnly="true"/>
</Listview>
<div>
    <DatePicker
        value={this.state.time5}
        isOpen={this.state.isOpen5}
        onSelect={this.handleSelect5.bind(this)}
        onCancel={this.handleCancel5.bind(this)}
        dateFormat={['hh', 'mm']}
        showFormat={'hh:mm'}
        theme={'android'}
        min={this.state.time}
    />
</div>
<div>
    <DatePicker
        value={this.state.time6}
        isOpen={this.state.isOpen6}
        onSelect={this.handleSelect6.bind(this)}
        onCancel={this.handleCancel6.bind(this)}
        dateFormat={['hh', 'mm']}
        showFormat={'hh:mm'}
        theme={'android'}
        min={this.state.time}
    />
</div>
handleClick1() {
    this.setState({ isOpen1: true });
}
handleCancel1() {
    this.setState({ isOpen1: false });
}
handleSelect1(time) {
    this.setState({
        time1: time,
        timestring1: this.getDateString(time), 
        isOpen1: false 
    });
}

Spin

加载数据时显示的等待动画,目前只有三种样式。

属性描述默认值类型
isSpinning是否显示boolean
indicator样式图案"a"string ("a", "b", "c")
size图案大小30num
color图案颜色“#318ccf”string
import { Spin } from 'gsp-react';

...
<Spin isSpinning={this.state.isSpinning} indicator="a" size={40} color={"red"}/>
npm.ionpm.ionpm.io
样式a样式b样式c

Refresh/Loadmore

下拉刷新组件只是对antd-mobile的PullToRefresh进行了简单的封装,调用过程相对简单。

如需在刷新时显示旋转加载动画,可以引入<Spin />组件。

npm.io

import { PullRefresh } from 'gsp-react';
import { Spin } from 'gsp-react';
this.state = {
    results: [],
    isRefreshing: false,
    timesOfLoad: 0, // 计数加载次数(实际应用中可以采用其他方法)
    hasMore: true, // 是否继续上划加载
    isSpinning: true // 是否显示加载动画
};
/**
 * 1. 挂载scroll监听方法
 */
componentDidMount() {
    let scrollableElement = document.getElementsByClassName("scroll");
    console.log(scrollableElement)
    if (scrollableElement && scrollableElement.length > 0) {
        scrollableElement[0].addEventListener('scroll', this.onScrollHandle.bind(this));
        this.refresh();
        this.setState({
            timesOfLoad: 1
        });
    }
}

/**
 * 3. 卸载scroll监听方法
 */
componentWillUnmount() {
    let scrollableElement = document.getElementsByClassName("scroll");
    if (scrollableElement && scrollableElement.length > 0) {
        scrollableElement[0].removeEventListener('scroll', this.onScrollHandle.bind(this));
    }
}

/**
 * 2. scroll监听方法,滚动至底部时,在自动加载更多数据的方法-->更新state中的数据-->更新dom
 * @param {*} event 
 */
onScrollHandle(event) {
    const clientHeight = event.target.clientHeight; // 屏幕高度
    const scrollHeight = event.target.scrollHeight; // 总的内容高度
    const scrollTop = event.target.scrollTop; // 已经滑动的距离
    const isBottom = (clientHeight + scrollTop === scrollHeight);
    if(isBottom) {
        if(this.state.hasMore) {
            this.setState({
                isSpinning: true
            });
            this.loadMore();
        }
    }
}
<Spin isSpinning={this.state.isSpinning} indicator="c" size={40} color={"#318ccf"}/>
<PullRefresh 
    refreshing={this.state.isRefreshing} 
    onRefresh={this.refresh.bind(this)}
    className={"scroll"}
>
    {listDiv}

    {/* 下方组件为列表底部提示性信息:列表还有内容时,显示"正在加载";列表无更多内容时,显示"—— 已无更多 ——" */}
    {<div className="text-center" 
        style={{backgroundColor: '#ededed', color: '#808080', fontSize: '14px', height: '45px', 
            verticalAlign: 'middle', paddingTop: '10px'}}>
        {this.state.hasMore ? <div><Icon type="loading" />  正在加载...</div> : "———— 已无更多 ————"}
    </div>}
</PullRefresh>
refresh() {
    let url = "http://jsonplaceholder.typicode.com/users";
    // let self = this;
    let optionsGET = {
    };

    let FETCH = new requestObj(url, optionsGET);
    FETCH.get()
    .subscribe(result => {
        this.setState({
            results: result,
            isSpinning: false,
            hasMore: true,
            timesOfLoad: 0
        });
    }, function (err) {
        if(err.status === 'timeout') {
            showMessage("info", "网络超时,请重试");
        }
        if(err.status=== 'offline') {
            showToast("offline", "网络连接不可用,请检查网络设置");
        }
        if(err.status=== 'error') {
            console.log(err);
            showMessage("info", "列表获取失败,请重试");
        }
    })
}
loadMore() {
    let url = "http://jsonplaceholder.typicode.com/users";
    let self = this;
    let optionsGET = {};

    let FETCH = new requestObj(url, optionsGET);
    FETCH.get()
    .subscribe(result => {
        let prevResults = self.state.results;
        let newResults = prevResults.concat(result);
        let timesOfLoad = self.state.timesOfLoad + 1;
        let hasMore = timesOfLoad > 2 ? false : true;
        self.setState({
            timesOfLoad: timesOfLoad,
            results: newResults,
            isSpinning: false,
            hasMore: hasMore
        });
    }, function (err) {
        if(err.status === 'timeout') {
            showMessage("info", "网络超时,请重试");
        }
        if(err.status=== 'offline') {
            showToast("offline", "网络连接不可用,请检查网络设置");
        }
        if(err.status=== 'error') {
            console.log(err);
            showMessage("info", "列表获取失败,请重试");
        }
    })
}
属性描述默认值类型
style目前没搞明白原理…)可以控制<Spin />的显示位置object
distanceToRefresh激活刷新的的拉动距离80num
indicator组件不同状态时的提示文字{ activate: '松开立即刷新', deactivate: '下拉可以刷新', finish: '完成刷新' }object
refreshing(不建议修改该属性)是否显示刷新状态falsebool
onRefresh必选,刷新回调函数func
内部child组件调用下拉刷新的长列表React elem

上滑加载功能因为需要调用React自身的生命周期函数,所以尚未封装为独立的组件。(antd-mobile中的上划加载功能因为强制使用其List组件,且调用不便,所以目前未采用)

实现步骤:

  1. 为页面添加ref ,这里起名为contentNode

     //此处的className=content的div具有属性 overflow-y: scroll,必须添加,否则无法触发loadMore方法
    <div className="content" ref={ node => this.contentNode = node }> 
        <Spin spinning={this.state.isSpinning} tip={"加载中"} delay={500} size="large">		
            <PullRefresh 
                style={{
                    height: this.state.height - 56,
                }}
                distanceToRefresh={80}
                // indicator={{ activate: '松开刷新', deactivate: '继续下拉刷新', finish: '刷新完成' }}
                refreshing={this.state.isRefreshing} 
                onRefresh={this.refresh.bind(this)}
            >
                {listDiv}
    
                {/* 下方组件为列表底部提示性信息:列表还有内容时,显示"正在加载";列表无更多内容时,显示"—— 已无更多 ——" */}
                {<div className="text-center" 
                    style={{backgroundColor: '#ededed', color: '#808080', fontSize: '14px', height: '45px', 
                        verticalAlign: 'middle', paddingTop: '10px'}}>
                    {this.state.hasMore ? <div><Icon type="loading" />  正在加载</div> : "———— 已无更多 ————"}
                </div>}
            </PullRefresh>
        </Spin>
    </div>
  2. 挂载scroll监听方法至contentNode上,该过程可以在componentDidMount()声明函数上执行

    /**
     * 1. 挂载scroll监听方法至contentNode上
     * 该contentNode为scrollable的实体dom
     */
    componentDidMount() {
        if (this.contentNode) {
            this.contentNode.addEventListener('scroll', this.onScrollHandle.bind(this));
        }
        this.refresh();
        this.setState({
            timesOfLoad: 1
        });
    }
  3. scroll监听方法,滚动至底部时,自动加载loadMore()方法—>更新state中的数据—>更新dom

    /**
     * 2. scroll监听方法,滚动至底部时,在自动加载更多数据的方法-->更新state中的数据-->更新dom
     * @param {*} event 
     */
    onScrollHandle(event) {
        const clientHeight = event.target.clientHeight; // 屏幕高度
        const scrollHeight = event.target.scrollHeight; // 总的内容高度
        const scrollTop = event.target.scrollTop; // 已经滑动的距离
        const isBottom = (clientHeight + scrollTop === scrollHeight)
        if(isBottom) {
            if(this.state.hasMore) {
                this.loadMore();
            }
        }
    }
    loadMore() {
        let url = "http://jsonplaceholder.typicode.com/users";
        let self = this;
        let optionsGET = {};
    
        let FETCH = new requestObj(url, optionsGET);
        FETCH.get()
        .subscribe(result => {
            let prevResults = self.state.results;
            let newResults = prevResults.concat(result);
            let timesOfLoad = self.state.timesOfLoad + 1;
            let hasMore = timesOfLoad > 2 ? false : true;
            self.setState({
                timesOfLoad: timesOfLoad,
                results: newResults,
                isSpinning: false,
                hasMore: hasMore
            });
        }, function (err) {
            if(err.status === 'timeout') {
                showMessage("info", "网络超时,请重试");
            }
            if(err.status=== 'offline') {
                showToast("offline", "网络连接不可用,请检查网络设置");
            }
            if(err.status=== 'error') {
                console.log(err);
                showMessage("info", "列表获取失败,请重试");
            }
        })
    }
  4. 卸载scroll监听方法

    componentWillUnmount() {
        if (this.contentNode) {
            this.contentNode.removeEventListener('scroll', this.onScrollHandle.bind(this));
        }
    }

Tab

  • Tab数量可以设定(2 <= n <= 5)
  • Tab样式可以设定(激活和未激活Tab的字体,背景色等)
  • 当前Tab下方横线样式可以设定(粗细、样式、颜色等)
属性描述默认值类型
tabstab标签文字数组[]: string
selected当前被选中tab0num
callBack点击tab的回调方法func
activeStyle激活状态tab样式{color: '#318ccf', backgroundColor: '#ffffff'}{}
inactiveStyle未激活状态tab样式{color: '#000000', backgroundColor: '#ffffff'}{}
indicatorStyle激活状态tab下方横线样式{color: '#318ccf', style: 'solid', width: '2px'}{}

Tab组件添加位置:

<Container>
    <Header name="Tab" 
        onLeftArrowClick={this.onLeftArrowClick.bind(this)}>
    </Header>
    {/* Tab */}
    {tabDiv}
    <Content>

    </Content>
</Container>

示例:

import { Tab } from 'gsp-react';
<Tab tabs={['本日', '本周']} 
    selected={this.state.selected} callBack={this.changeTab.bind(this)}/>
<Tab tabs={['本日', '本周', '本月']} 
	selected={this.state.selected} callBack={this.changeTab.bind(this)}/>
<Tab tabs={['本日', '本周', '本月']} 
    activeStyle={{color: 'red', backgroundColor: '#ffffff', fontWeight: 'bold'}}
    inactiveStyle={{color: 'green', backgroundColor: '#ffffff'}}
    indicatorStyle={{width: '2px', color: '#318ccf', style: 'dashed'}}
    selected={this.state.selected} callBack={this.changeTab.bind(this)}/>
npm.ionpm.ionpm.ionpm.ionpm.io
2个tab3个tab4个tab5个tab样式修改

TODO:

  • 高度自定义
  • 动画切换效果

Listitem

  • Listitem组件方便用户在页面上进行信息设定。
  • 左侧为提示性信息,右侧根据用户需要可以嵌套不同数量、不同种类的元素(IconimageInputlabelSwitchButton等);
  • 右侧部分应用了Bootstrap v4定位,根据元素数量自动定位 👉here
  • Trick:右侧只有一个元素而又想帖靠在右侧时,可以添加一个空的div进行占位(此时右侧实际包含两个元素,详见“索要发票”示例)(很矬,待改进)
右侧元素数量右侧分布情况
1A
2A ————————————————————————————— B
3A——————————————B———————————————C
4A————————B————————— C ———————————D
属性描述默认值类型
text左侧描述性信息string
内部child组件右侧元素React elem
required是否必填falseboolean
import { Listview } from 'gsp-react';
<Listview text={"时间"}>
    <input type="text" value={"2018-01-30 16:45:30"} placeholder={"请输入时间"}
        onClick={this.onClick.bind(this)} readOnly="true" style={{width: '100%'}}/>
</Listview>
<Listview text={"所在单位"}>
    <label onClick={this.onClick.bind(this)}>{"浪潮国际平台与技术部"}</label>
    <div className="pt-2 ml-2" onClick={this.onClick.bind(this)}><Icon type="right" size={'lg'}/></div>
</Listview>
<Listview text={"起止时间"}>
    <input type="text" value={this.state.timestring5} placeholder={"起始时间"}
        onClick={this.handleClick5.bind(this)} readOnly="true"/>
    <div className="pt-2 ml-2 mr-2"><Icon type="arrow-right" size={'lg'}/></div>
    <input type="text" value={this.state.timestring6} placeholder={"结束时间"} className="text-right"
        onClick={this.handleClick6.bind(this)} readOnly="true"/>
</Listview>
<div>
    <DatePicker
        value={this.state.time5}
        isOpen={this.state.isOpen5}
        onSelect={this.handleSelect5.bind(this)}
        onCancel={this.handleCancel5.bind(this)}
        dateFormat={['hh', 'mm']}
        showFormat={'hh:mm'}
        theme={'android'}
        min={this.state.time}
    />
</div>
<div>
    <DatePicker
        value={this.state.time6}
        isOpen={this.state.isOpen6}
        onSelect={this.handleSelect6.bind(this)}
        onCancel={this.handleCancel6.bind(this)}
        dateFormat={['hh', 'mm']}
        showFormat={'hh:mm'}
        theme={'android'}
        min={this.state.time}
    />
</div>
<Listview text={"城市区间"}>
    <input type="text" value={"济南市"} placeholder={"始发城市"}
        onClick={this.onClick.bind(this)} readOnly="true"/>
    {/* <div className="pt-2 ml-2 mr-2"><Icon type="arrow-right" size={'lg'}/></div> */}
    <img className="mt-3" src={require("../images/arrowdotted.png")} alt="" style={{width: "20px", height: "10px"}}/>
    <input type="text" value={"布宜诺斯艾利斯"} placeholder={"到达城市"} className="text-right"
        onClick={this.onClick.bind(this)} readOnly="true"/>
</Listview>
<Listview text={"索要发票"}>
    <div></div>
    <Switch checked={this.state.switchChecked} onChange={this.onSwitchChange.bind(this)}/>
</Listview>
<Listview text={"支付方式"}>
    <div></div>
    <div className="pt-1">
        <RadioGroup name="payment" mode="divide"
            size="sm"
            option={['签单', '工卡', '微信']} 
            val={[0, 1, 2]} 
            id={['op1', 'op2', 'op3']}
            selected={this.state.selectedRadio}
            onChange={this.radioChange.bind(this)}>
        </RadioGroup>
    </div>
</Listview> 
npm.ionpm.ionpm.ionpm.ionpm.ionpm.io
时间所在单位起止时间城市区间索要发票支付方式

Radio/Check

单选按钮分为divide型和line型两种;

CheckGroup目前有一种样式(之后可能会扩展)。

  • Radio
属性描述默认值类型
modeRadio样式“divide”string("divide", "line")
sizeRadio按钮大小"md"string ("lg", "md", "sm")
optionRadio选项[]
valRadio按钮的值[]
id(可选)按钮的ID,在传统Radio中需要设置[]
selected当前选中项num
onChange点击调用的方法func
  • Check
属性描述默认值类型
optionCheck选项[]
valCheck按钮的值[]
selected当前选中项[]: num
onChange点击调用方法func
npm.ionpm.ionpm.ionpm.io
divide单选按钮divide按钮在listitem中line单选按钮多选按钮
import { CheckGroup, RadioGroup } from 'gsp-react;
<RadioGroup name="payment" mode="divide"
    size="lg"
    option={['签单', '工卡', '微信']} 
    val={[0, 1, 2]} 
    id={['op1', 'op2', 'op3']}
    selected={this.state.selectedRadio}
    onChange={this.radioChange.bind(this)}>
</RadioGroup>
<RadioGroup name="payment" mode="line"
    size="sm"
    option={['签单', '工卡', '微信']} 
    val={[0, 1, 2]} 
    id={['op1', 'op2', 'op3']}
    selected={this.state.selectedRadio}
    onChange={this.radioChange.bind(this)}>
</RadioGroup>
<CheckGroup 
    option={['待确认', '制作中', '待结算', '已完成']}
    val={[0, 1, 2, 3]}
    selected={this.state.selectedCheckbox}
    onChange={this.checkChange.bind(this)}
/>

Switch

  • 建议与Listitem组件一起使用
属性描述默认值类型
checked是否开启bool
onChange点击回调函数func
color开启后的颜色"#4dd865"string
disabled禁用falsebool
npm.ionpm.ionpm.io
默认样式自定义按钮颜色禁用状态
import { Switch } from 'gsp-react';
<Listview text={"索要发票"}>
    <div></div>
    <Switch checked={this.state.switchChecked} onChange={this.onSwitchChange.bind(this)}/>
</Listview>
<Listview text={"索要发票"}>
    <div></div>
    <Switch checked={this.state.switchChecked} onChange={this.onSwitchChange.bind(this)} disabled/>
</Listview>

<Listview text={"删除购买历史"}>
    <div></div>
    <Switch checked={this.state.switchChecked} onChange={this.onSwitchChange.bind(this)} color={"#318ccf"}/>
</Listview>

断网检测

  • 断网监测功能使用了offline.js插件
  • Offline.js插件按照一定时间间隔对网络资源进行请求,以此判断设备的网络状况
  • 当设备处于断网状态时,屏幕顶部弹出全局提示条对用户进行友好提示;网络恢复时,自动对网络进行重连并提示用户

使用方法

  1. ./public 文件夹中添加一下三个文件:(可去官网下载更多样式或者自定义)

    • offline.min.js // 断网监测功能
    • offline-theme-chrome.css // 提示条样式
    • offline-language-chine se-simplified.css // 提示条语言
  2. ./public/index.html 文件中引入以上文件并设置定时访问的URL及检测间隔:

    <head>
        <link rel="stylesheet" href="./offline-language-chinese-simplified.css" />
        <link rel="stylesheet" href="./offline-theme-chrome.css" />
        <script src="./offline.min.js"></script>    
        <script>
          Offline.options = {
            game: false,
            checks: { xhr: { url: 'http://jsonplaceholder.typicode.com/posts/1/comments' } }
          }
          var run = function () {
            if (Offline.state === 'up')
              Offline.check();
          }
          setInterval(run, 10000);
        </script>
    </head>

效果展示

npm.io

API调用操作

  • 对API的操作使用fetch,导出为对象,并对基本的操作方法(GET,POST,PUT,DELETE,PATCH等)进行了简单封装
  • 采用了流式数据操作方式,融入了RxJS的技术
  • 对异常情况进行了简单分类(timeout, offline, error),方便用户调用及异常信息提示

使用方法

  1. 引入fetch文件

    import { Fetch as requestObj } from 'gsp-react';
  2. 初始化对象示例

    接收参数:

    • url:API url
    • options:API参数,包括需要上传的数据,数据返回的类型("json"、"text"、"blob")…
    let url = "http://jsonplaceholder.typicode.com/users";
    let options = {format: 'text'}; // options不需要进行stringify操作;可以设置返回值类型
    let FETCH = new requestObj(url, options);
  3. 根据操作方法(GET,POST,PUT,DELETE,PATCH等)的不同,调用不同对象方法,并对获取的数据、产生的异常进行处理

    FETCH.get()  // .get()|.post()|.put()|.delete()|.patch() ...
    .subscribe(result => {
        this.setState({
            results: result
        }, () => {
            showMessage("success", "列表获取成功!"); // 操作成功提示信息
        });
    
    }, function (err) {
        if(err.status === 'timeout') {
            showMessage("info", "网络超时,请重试");  // 网络超时提示信息
        }
        if(err.status=== 'offline') {
            showToast("offline", "网络连接不可用,请检查网络设置");  // 网络断开提示信息
        }
        if(err.status=== 'error') {
            console.log(err);
            showMessage("info", "列表获取失败,请重试");  // API操作异常提示信息
        }
    })

网络超时时长可在fetch.js文件request()方法中设置:

//这里使用Promise.race设置网络超时
let abortable_promise = Promise.race([fetch_promise, abort_promise]);
setTimeout(function () {
    abort_fn();
}, 5 * 1000); // 默认网络超时时长为5秒
npm.ionpm.ionpm.ionpm.io
正常获取数据并弹出提示信息网络连接中断网络超时API调用失败

按需加载

为了优化打包速度及打包生成文件的大小,需要使用按需加载技术,根据实际用到的gsp-react组件,打包相应的CSS样式文件,具体步骤如下:

  1. 安装插件

    npm install babel-plugin-import --save-dev

  2. 将项目进行降级处理

    npm run eject
  3. 暴露出的webpack配置文件(webpack.config.dev.js和webpack.config.prod.js)中添加如下插件配置,babel-loader配置器如下:

    // Process JS with Babel.
      {
        test: /\.(js|jsx|mjs)$/,
        include: paths.appSrc,
        loader: require.resolve('babel-loader'),
        options: {
          plugins: [
            ['import', { libraryName: 'gsp-react', style: "css" }],
            ['import', { libraryName: 'antd', style: true }],
          ],
          // This is a feature of `babel-loader` for webpack (not Babel itself).
          // It enables caching results in ./node_modules/.cache/babel-loader/
          // directory for faster rebuilds.
          cacheDirectory: true,
        },
      },
  4. 按需在页面中引入组件

    import { Header, Footer, Container, Content, Card } from 'gsp-react';

    webpack会自动引入相应组件的CSS文件,未使用组件的样式文件不会引入。

注意:为了使babel-plugin-import插件能够顺利实现按需引入css文件,在进行组件文件夹命名时,需要保证组件名须与其所在文件夹相同,但大小写可以不同,如Button组件所在文件夹为/button,PullToRefresh组件文件夹为/pull-to-refresh。若将RadioGroup组件置于Radio文件夹,则加载出错。

发布至npm

  • ~/package.jsonbabel 配置项修改为如下所示(presets处)

    "babel": {
        "presets": [
          "react",
          "es2015"
        ],
        "plugins": [
          "transform-object-rest-spread",
          "transform-class-properties"
        ]
      },
  • 将修改后的待发版组件文件夹~/src/components 内所有文件拷贝至~/es6es5 文件夹下

    npm.ionpm.io
    ~/src/components~/es6es5
  • 在根目录执行命令

    babel es6es5 -d es6es5

    此时~/es6es5 文件夹内的所有js文件被转码为ES5语法,css及其他文件不受影响。

  • 拷贝~/es6es5 内所有文件至~/publish/lib

    npm.ionpm.io
    ~/es6es5~/publish/lib
  • ~/publish 目录执行命令

    npm adduser //只在第一次发布时执行
    
    npm publish

    注意:

    1、需要先在npm官网注册账号;

    2、每次发布新版本需要修改publish文件夹下package.json中的version。

    3、若使用了其他npm源(如taobao的registry),需切换至默认的npm源。推荐使用nrm进行源管理。