import React, { Component } from 'react'; import './index.less'; import { Modal, Icon, Button, Tooltip } from 'antd'; import Assets from '@src/components/Assets'; import { asyncSMessage } from '@src/services/AsyncTools'; import { Icon as GIcon } from '../Icon'; import { Button as GButton } from '../Button'; import RadioItem from '../RadioItem'; import { User } from '../../stores/user'; import { Common } from '../../stores/common'; import { Main } from '../../stores/main'; import { MobileArea, WechatPcAppId, PcUrl } from '../../../Constant'; const LOGIN_PHONE = 'LOGIN_PHONE'; const LOGIN_WX = 'LOGIN_WX'; const BIND_PHONE = 'BIND_PHONE'; const BIND_WX = 'BIND_WX'; const BIND_WX_ERROR = 'BIND_WX_ERROR'; export default class Login extends Component { constructor(props) { super(props); this.validNumber = 0; this.state = { type: LOGIN_WX, error: {}, data: { area: MobileArea[0].value } }; this.validMobileNumber = 0; this.validEmailNumber = 0; window.addEventListener( 'message', event => { if (typeof event.data === 'string' && event.data.indexOf('code:') === 0) { const code = event.data.split(':')[1]; if (this.state.type === LOGIN_WX) { this.scanLogin(code); } else if (this.state.type === BIND_WX) { this.scanBind(code); } } }, false, ); } componentWillReceiveProps(nextProps) { if (nextProps.user.needLogin && !this.init) { this.init = true; Main.getContract('register').then(result => { this.setState({ registerContract: result }); }); Main.getContract('privacy').then(result => { this.setState({ privacyContract: result }); }); } } close() { User.closeLogin(); } login() { const { data, needEmail, mobileError, validError, emailError, error } = this.state; const { area, mobile, mobileVerifyCode, email } = data; if (mobileError || emailError || validError) return; if (!area || !mobile || !mobileVerifyCode || (needEmail && !email)) { error[LOGIN_PHONE] = { mobile: !mobile, mobileVerifyCode: !mobileVerifyCode, email: !email }; this.setState({ error }); return; } error[LOGIN_PHONE] = {}; this.setState({ error }); User.login(area, mobile, mobileVerifyCode, email, null, false) .then(result => { if (result.bindWechat) { this.close(); } else { this.setState({ type: BIND_WX }); } }) .catch(err => { if (err.message.indexOf('验证码') >= 0) { this.setState({ validError: err.message }); } else if (err.message.indexOf('邮箱') >= 0) { this.setState({ emailError: err.message }); } else { this.setState({ mobileError: err.message }); } }); } bind() { const { data, needEmail, mobileError, emailError, validError, error } = this.state; const { area, mobile, mobileVerifyCode, email } = data; if (mobileError || emailError || validError) return; if (!area || !mobile || !mobileVerifyCode || (needEmail && !email)) { error[BIND_PHONE] = { mobile: !mobile, mobileVerifyCode: !mobileVerifyCode, email: !email }; this.setState({ error }); return; } error[BIND_PHONE] = {}; this.setState({ error }); User.bind(area, mobile, mobileVerifyCode, email) .then(() => { this.close(); }) .catch(err => { if (err.message.indexOf('验证码') >= 0) { this.setState({ validError: err.message }); } else if (err.message.indexOf('邮箱') >= 0) { this.setState({ emailError: err.message }); } else { this.setState({ mobileError: err.message }); } }); } scanLogin(code) { User.loginWechat(code, false) .then(result => { if (result.bindMobile) { this.close(); } else { this.setState({ type: BIND_PHONE }); } }) .catch(err => { this.setState({ type: BIND_WX_ERROR, wechatError: err.message }); }); } scanBind(code) { User.loginWechat(code, false) .then(() => { this.close(); }) .catch(err => { this.setState({ type: BIND_WX_ERROR, wechatError: err.message }); }); } changeData(field, value) { let { data } = this.state; data = data || {}; data[field] = value; this.setState({ data }); } validMobile(login) { const { data } = this.state; const { area, mobile } = data; if (!area || !mobile) return; this.validMobileNumber += 1; const number = this.validMobileNumber; User.validWechat(area, mobile) .then(result => { if (result) { this.setState({ mobileError: '' }); return User.validMobile(area, mobile).then(r => { if (number !== this.validMobileNumber) return; this.setState({ needEmail: r }); }); } this.setState({ needEmail: false }); return login ? Promise.resolve() : Promise.reject(new Error('该手机已绑定其他账号,请更换手机号码')); }) .catch(err => { this.setState({ mobileError: err.message }); }); } validEmail() { const { data } = this.state; const { email } = data; if (!email) return; this.validEmailNumber += 1; const number = this.validEmailNumber; User.validEmail(email) .then(result => { if (number !== this.validEmailNumber) return Promise.resolve(); if (result) { this.setState({ emailError: '' }); return Promise.resolve(); } return Promise.reject(new Error('该邮箱已绑定其他账号,请更换邮箱地址')); }) .catch(err => { this.setState({ emailError: err.message }); }); } sendValid() { const { data, mobileError } = this.state; const { area, mobile } = data; if (!area || !mobile || mobileError) return Promise.reject(); return Common.sendSms(area, mobile) .then(result => { if (result) { asyncSMessage('发送成功'); this.setState({ mobileError: '', validError: '' }); } else { throw new Error('发送失败'); } }) .catch(err => { this.setState({ mobileError: err.message }); throw err; }); } render() { const { type } = this.state; const { user } = this.props; return ( <Modal ref={ref => { this.modalR = ref; }} wrapClassName={`login-modal ${type}`} visible={user.needLogin} footer={null} closable={false} width={470} > {this.renderBody(type)} <GIcon name="close" onClick={() => { if (type === BIND_WX_ERROR) { // 绑定微信错误,返回重新绑定微信 this.setState({ type: BIND_WX }); } else { User.closeLogin(new Error('no login')); } }} /> </Modal> ); } renderBody(type) { switch (type) { case LOGIN_WX: return this.renderLoginWx(); case BIND_PHONE: return this.renderBindPhone(); case BIND_WX: return this.renderBindWx(); case BIND_WX_ERROR: return this.renderBindWxError(); case LOGIN_PHONE: default: return this.renderLoginPhone(); } } renderLoginPhone() { const { needEmail, registerContract = {}, privacyContract, error = {} } = this.state; const emptyError = error[LOGIN_PHONE] || {}; return ( <div className="body"> <div className="title">手机号登录</div> <div className="tip" hidden={!needEmail}> <Assets name="notice" /> 该手机号尚未注册,将自动为您注册账户 </div> <SelectInput placeholder="请输入手机号" selectValue={this.state.data.area} select={MobileArea} value={this.state.data.mobile} error={this.state.mobileError} empty={emptyError.mobile} onSelect={value => { this.changeData('area', value); this.validMobile(true); }} onChange={e => { this.changeData('mobile', e.target.value); this.validMobile(true); }} /> <VerificationInput placeholder="请输入验证码" value={this.state.data.mobileVerifyCode} error={this.state.validError} empty={emptyError.mobileVerifyCode} onSend={() => { return this.sendValid(); }} onChange={e => { this.changeData('mobileVerifyCode', e.target.value); }} /> {needEmail && ( <Input placeholder="请输入邮箱" value={this.state.data.email} empty={emptyError.email} error={this.state.emailError} onChange={e => { this.changeData('email', e.target.value); this.validEmail(); }} /> )} {needEmail && ( <div className="m-b-2"> <RadioItem checked theme="white" className="m-r-5" /> 我已同意{' '} <a href={`/contract/${registerContract.key}`} target="_blank"> 《{registerContract.title}》 </a>{' '} <a href={`/contract/${privacyContract.key}`} target="_blank"> 《{privacyContract.title}》 </a> 。 </div> )} <Button type="primary" size="large" block onClick={() => { this.login(); }} > 登录 </Button> <Tooltip overlayClassName="gray" placement="left" title="微信扫码登录"> <a className="other" onClick={() => { this.setState({ type: LOGIN_WX }); }} > <Assets name="code" /> </a> </Tooltip> </div> ); } renderLoginWx() { return ( <div className="body"> <div className="title">微信扫码登录</div> <div className="qr-code"> <iframe frameBorder="0" src={`/login.html?appid=${WechatPcAppId}&redirectUri=${encodeURIComponent(PcUrl)}`} width="300" height="300" /> <div className="text">请使用微信扫描二维码登录</div> </div> <Tooltip overlayClassName="gray" placement="left" title="手机号登录"> <a className="other" onClick={() => { this.setState({ type: LOGIN_PHONE }); }} > <Assets name="phone" /> </a> </Tooltip> </div> ); } renderBindPhone() { const { needEmail = true, registerContract = {}, privacyContract = {}, error = {} } = this.state; const emptyError = error[BIND_PHONE] || {}; return ( <div className="body"> <div className="title">绑定手机号</div> <div className="tip"> <Assets name="notice" /> 微信登录成功!为更好的使用服务,请您绑定手机号和邮箱。 </div> <SelectInput placeholder="请输入手机号" selectValue={this.state.data.area} select={MobileArea} value={this.state.data.mobile} error={this.state.mobileError} empty={emptyError.mobile} onSelect={value => { this.changeData('area', value); this.validMobile(false); }} onChange={e => { this.changeData('mobile', e.target.value); this.validMobile(false); }} /> <VerificationInput placeholder="请输入验证码" value={this.state.data.mobileVerifyCode} error={this.state.validError} empty={emptyError.mobileVerifyCode} onSend={() => { return this.sendValid(); }} onChange={e => { this.changeData('mobileVerifyCode', e.target.value); }} /> {needEmail && ( <Input placeholder="请输入邮箱" value={this.state.data.email} empty={emptyError.email} error={this.state.emailError} onChange={e => { this.changeData('email', e.target.value); this.validEmail(); }} /> )} {needEmail && ( <div className="m-b-2"> <RadioItem checked theme="white" className="m-r-5" /> 我已同意{' '} <a href={`/contract/${registerContract.key}`} target="_blank"> 《{registerContract.title}》 </a>{' '} <a href={`/contract/${privacyContract.key}`} target="_blank"> 《{privacyContract.title}》 </a> <a className="f-r" onClick={() => this.setState({ type: LOGIN_PHONE })}> 使用手机号码登录 </a> 。 </div> )} {!needEmail && ( <div className="m-b-2"> <a className="f-r" onClick={() => this.setState({ type: LOGIN_PHONE })}> 使用手机号码登录 </a> </div> )} <Button type="primary" size="large" disabled={this.state.validError || this.state.mobileError} block onClick={() => { this.bind(); }} > 绑定 </Button> </div> ); } renderBindWx() { return ( <div className="body"> <div className="title">绑定微信号</div> <div className="tip"> <Assets name="notice" /> 手机号注册成功!为更好的使用服务,建议您绑定微信号。 </div> <div className="qr-code"> <iframe frameBorder="0" src={`/login.html?appid=${WechatPcAppId}&redirectUri=${encodeURIComponent(PcUrl)}`} width="300" height="300" /> <div className="text">请使用微信扫描二维码登录</div> <div className="jump" onClick={() => { this.close(); }} > 跳过 </div> </div> </div> ); } renderBindWxError() { return ( <div className="body"> <div className="title">绑定失败</div> <div className="text">该微信账户已绑定其他手机号码,您可直接扫码登入。</div> <div className="btn"> <GButton radius onClick={() => { this.setState({ type: LOGIN_WX }); }} > 扫码登入 </GButton> </div> </div> ); } } export class Input extends Component { render() { const { className = '', onChange, placeholder, value, error, left, right, empty } = this.props; return ( <div className={`g-input-container ${className}`}> <div className={`g-input-wrapper ${error ? 'error' : ''}`}> {left && <div className="g-input-left">{left}</div>} <input className={`g-input ${empty ? 'empty' : ''}`} placeholder={placeholder} value={value} onChange={data => onChange && onChange(data)} /> {right && <div className="g-input-right">{right}</div>} </div> <div hidden={!error} className="g-input-error"> {error} </div> </div> ); } } export class SelectInput extends Component { constructor(props) { super(props); this.state = { showSelect: false }; } render() { const { showSelect } = this.state; const { className = '', onChange, placeholder, value, error, empty, selectValue, select, onSelect } = this.props; return ( <Input className={className} left={ <span className="g-input-left-select" onClick={() => this.setState({ showSelect: !showSelect })}> {selectValue} <Icon type={showSelect ? 'up' : 'down'} /> {showSelect && ( <ul className="select-list"> {select.map(row => { return ( <li onClick={() => { this.setState({ showSelect: false }); if (onSelect) onSelect(row.value); }} > {row.label} </li> ); })} </ul> )} </span> } value={value} placeholder={placeholder} onChange={data => onChange && onChange(data)} error={error} empty={empty} /> ); } } export class VerificationInput extends Component { constructor(props) { super(props); this.timeKey = null; this.state = { loading: 0 }; } componentWillUnmount() { if (this.timeKey) clearTimeout(this.timeKey); } onSend() { const { onSend, time = 60 } = this.props; if (onSend) { onSend().then(() => { if (this.timeKey) clearTimeout(this.timeKey); this.setTime(time); }); } } setTime(time) { this.setState({ loading: time }); this.timeKey = setTimeout(() => { this.setTime(time - 1); }, 1000); } render() { const { loading } = this.state; const { className = '', onChange, placeholder, value, error, empty } = this.props; return ( <Input className={className} right={ loading <= 0 ? ( <span className="g-input-right-verification" onClick={() => this.onSend()}> 获取验证码 </span> ) : ( <span className="g-input-right-verification-loading">等待{loading}秒</span> ) } value={value} error={error} empty={empty} placeholder={placeholder} onChange={data => onChange && onChange(data)} /> ); } }