index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. import React, { Component } from 'react';
  2. import './index.less';
  3. import { Modal, Icon, Button, Tooltip } from 'antd';
  4. import Assets from '@src/components/Assets';
  5. import { asyncSMessage } from '@src/services/AsyncTools';
  6. import { Icon as GIcon } from '../Icon';
  7. import { Button as GButton } from '../Button';
  8. import { User } from '../../stores/user';
  9. import { Common } from '../../stores/common';
  10. import { MobileArea, WechatUserAppId } from '../../../Constant';
  11. const LOGIN_PHONE = 'LOGIN_PHONE';
  12. const LOGIN_WX = 'LOGIN_WX';
  13. const BIND_PHONE = 'BIND_PHONE';
  14. const BIND_WX = 'BIND_WX';
  15. const BIND_WX_ERROR = 'BIND_WX_ERROR';
  16. export default class Login extends Component {
  17. constructor(props) {
  18. super(props);
  19. this.validNumber = 0;
  20. this.state = { type: LOGIN_WX, data: { area: MobileArea[0].value } };
  21. window.addEventListener(
  22. 'message',
  23. event => {
  24. if (typeof event.data === 'string' && event.data.indexOf('code:') === 0) {
  25. const code = event.data.split(':')[1];
  26. if (this.state.type === LOGIN_WX) {
  27. this.scanLogin(code);
  28. } else if (this.state.type === BIND_WX) {
  29. this.scanBind(code);
  30. }
  31. }
  32. },
  33. false,
  34. );
  35. }
  36. close() {
  37. User.closeLogin();
  38. }
  39. login() {
  40. const { data, needEmail, mobileError, validError } = this.state;
  41. const { area, mobile, mobileVerifyCode, email } = data;
  42. if (mobileError || validError) return;
  43. if (!area || !mobile || !mobileVerifyCode) return;
  44. if (needEmail && !email) return;
  45. User.login(area, mobile, mobileVerifyCode, email)
  46. .then((result) => {
  47. if (result.bindWechat) {
  48. this.close();
  49. } else {
  50. this.setState({ type: BIND_WX });
  51. }
  52. })
  53. .catch(err => {
  54. if (err.message.indexOf('验证码') >= 0) {
  55. this.setState({ validError: err.message });
  56. } else {
  57. this.setState({ mobileError: err.message });
  58. }
  59. });
  60. }
  61. bind() {
  62. const { data, needEmail, mobileError, validError } = this.state;
  63. const { area, mobile, mobileVerifyCode, email } = data;
  64. if (mobileError || validError) return;
  65. if (!area || !mobile || !mobileVerifyCode) return;
  66. if (needEmail && !email) return;
  67. User.bind(area, mobile, mobileVerifyCode, email)
  68. .then(() => {
  69. this.close();
  70. })
  71. .catch(err => {
  72. if (err.message.indexOf('验证码') >= 0) {
  73. this.setState({ validError: err.message });
  74. } else {
  75. this.setState({ mobileError: err.message });
  76. }
  77. });
  78. }
  79. scanLogin(code) {
  80. User.loginWechat(code).then(result => {
  81. if (result.bindMobile) {
  82. this.close();
  83. } else {
  84. this.setState({ type: BIND_PHONE });
  85. }
  86. });
  87. }
  88. scanBind(code) {
  89. User.loginWechat(code)
  90. .then(() => {
  91. this.close();
  92. })
  93. .catch(err => {
  94. this.setState({ type: BIND_WX_ERROR, wechatError: err.message });
  95. });
  96. }
  97. changeData(field, value) {
  98. let { data } = this.state;
  99. data = data || {};
  100. data[field] = value;
  101. this.setState({ data });
  102. }
  103. validMobile(login) {
  104. const { data } = this.state;
  105. const { area, mobile } = data;
  106. if (!area || !mobile) return;
  107. this.validNumber += 1;
  108. const number = this.validNumber;
  109. User.validWechat(area, mobile)
  110. .then(result => {
  111. if (result) {
  112. this.setState({ mobileError: '' });
  113. return User.validMobile(area, mobile).then(r => {
  114. if (number !== this.validNumber) return;
  115. this.setState({ needEmail: r });
  116. });
  117. }
  118. this.setState({ needEmail: false });
  119. return login ? Promise.resolve() : Promise.reject(new Error('该手机已绑定其他账号,请更换手机号码'));
  120. })
  121. .catch(err => {
  122. this.setState({ mobileError: err.message });
  123. });
  124. }
  125. sendValid() {
  126. const { data, mobileError } = this.state;
  127. const { area, mobile } = data;
  128. if (!area || !mobile || mobileError) return Promise.reject();
  129. return Common.sendSms(area, mobile)
  130. .then(result => {
  131. if (result) {
  132. asyncSMessage('发送成功');
  133. this.setState({ mobileError: '', validError: '' });
  134. } else {
  135. throw new Error('发送失败');
  136. }
  137. })
  138. .catch(err => {
  139. this.setState({ mobileError: err.message });
  140. throw err;
  141. });
  142. }
  143. render() {
  144. const { type } = this.state;
  145. const { user } = this.props;
  146. return (
  147. <Modal ref={(ref) => { this.modalR = ref; }} wrapClassName={`login-modal ${type}`} visible={user.needLogin} footer={null} closable={false} width={470}>
  148. {this.renderBody(type)}
  149. <GIcon
  150. name="close"
  151. onClick={() => {
  152. this.close();
  153. }}
  154. />
  155. </Modal>
  156. );
  157. }
  158. renderBody(type) {
  159. switch (type) {
  160. case LOGIN_WX:
  161. return this.renderLoginWx();
  162. case BIND_PHONE:
  163. return this.renderBindPhone();
  164. case BIND_WX:
  165. return this.renderBindWx();
  166. case BIND_WX_ERROR:
  167. return this.renderBindWxError();
  168. case LOGIN_PHONE:
  169. default:
  170. return this.renderLoginPhone();
  171. }
  172. }
  173. renderLoginPhone() {
  174. const { needEmail } = this.state;
  175. return (
  176. <div className="body">
  177. <div className="title">手机号登录</div>
  178. <div className="tip" hidden={!needEmail}>
  179. <Assets name="notice" />
  180. 该手机号尚未注册,将自动为您注册账户
  181. </div>
  182. <SelectInput
  183. placeholder="请输入手机号"
  184. selectValue={this.state.data.area}
  185. select={MobileArea}
  186. value={this.state.data.mobile}
  187. error={this.state.mobileError}
  188. onSelect={value => {
  189. this.changeData('area', value);
  190. this.validMobile(true);
  191. }}
  192. onChange={e => {
  193. this.changeData('mobile', e.target.value);
  194. this.validMobile(true);
  195. }}
  196. />
  197. <VerificationInput
  198. placeholder="请输入验证码"
  199. value={this.state.data.mobileVerifyCode}
  200. error={this.state.validError}
  201. onSend={() => {
  202. return this.sendValid();
  203. }}
  204. onChange={e => {
  205. this.changeData('mobileVerifyCode', e.target.value);
  206. }}
  207. />
  208. {needEmail && (
  209. <Input
  210. placeholder="请输入邮箱"
  211. value={this.state.data.email}
  212. onChange={e => {
  213. this.changeData('email', e.target.value);
  214. }}
  215. />
  216. )}
  217. <Button
  218. type="primary"
  219. size="large"
  220. block
  221. onClick={() => {
  222. this.login();
  223. }}
  224. >
  225. 登录
  226. </Button>
  227. <Tooltip overlayClassName="gray" placement="left" title="微信扫码登录">
  228. <a
  229. className="other"
  230. onClick={() => {
  231. this.setState({ type: LOGIN_WX });
  232. }}
  233. >
  234. <Assets name="code" />
  235. </a>
  236. </Tooltip>
  237. </div>
  238. );
  239. }
  240. renderLoginWx() {
  241. return (
  242. <div className="body">
  243. <div className="title">微信扫码登录</div>
  244. <div className="qr-code">
  245. <iframe frameBorder="0" src={`/login.html?appid=${WechatUserAppId}&redirectUri=${encodeURIComponent('http://www.duoshaojiaoyu.com')}`} width="300" height="300" />
  246. <div className="text">请使用微信扫描二维码登录</div>
  247. </div>
  248. <Tooltip overlayClassName="gray" placement="left" title="手机号登录">
  249. <a
  250. className="other"
  251. onClick={() => {
  252. this.setState({ type: LOGIN_PHONE });
  253. }}
  254. >
  255. <Assets name="phone" />
  256. </a>
  257. </Tooltip>
  258. </div>
  259. );
  260. }
  261. renderBindPhone() {
  262. const { needEmail } = this.state;
  263. return (
  264. <div className="body">
  265. <div className="title">绑定手机号</div>
  266. <div className="tip">
  267. <Assets name="notice" />
  268. 微信登录成功!为更好的使用服务,请您绑定手机号和邮箱。
  269. </div>
  270. <SelectInput
  271. placeholder="请输入手机号"
  272. selectValue={this.state.data.area}
  273. select={MobileArea}
  274. value={this.state.data.mobile}
  275. error={this.state.mobileError}
  276. onSelect={value => {
  277. this.changeData('area', value);
  278. this.validMobile(false);
  279. }}
  280. onChange={e => {
  281. this.changeData('mobile', e.target.value);
  282. this.validMobile(false);
  283. }}
  284. />
  285. <VerificationInput
  286. placeholder="请输入验证码"
  287. value={this.state.data.mobileVerifyCode}
  288. error={this.state.validError}
  289. onSend={() => {
  290. return this.sendValid();
  291. }}
  292. onChange={e => {
  293. this.changeData('mobileVerifyCode', e.target.value);
  294. }}
  295. />
  296. {needEmail && (
  297. <Input
  298. placeholder="请输入邮箱"
  299. value={this.state.data.email}
  300. onChange={e => {
  301. this.changeData('email', e.target.value);
  302. }}
  303. />
  304. )}
  305. <Button
  306. type="primary"
  307. size="large"
  308. block
  309. onClick={() => {
  310. this.bind();
  311. }}
  312. >
  313. 绑定
  314. </Button>
  315. </div>
  316. );
  317. }
  318. renderBindWx() {
  319. return (
  320. <div className="body">
  321. <div className="title">绑定微信号</div>
  322. <div className="tip">
  323. <Assets name="notice" />
  324. 手机号注册成功!为更好的使用服务,建议您绑定微信号。
  325. </div>
  326. <div className="qr-code">
  327. <iframe frameBorder="0" src={`/login.html?appid=${WechatUserAppId}&redirectUri=${encodeURIComponent('http://www.duoshaojiaoyu.com')}`} width="300" height="300" />
  328. <div className="text">请使用微信扫描二维码登录</div>
  329. <div
  330. className="jump"
  331. onClick={() => {
  332. this.close();
  333. }}
  334. >
  335. 跳过
  336. </div>
  337. </div>
  338. </div>
  339. );
  340. }
  341. renderBindWxError() {
  342. return (
  343. <div className="body">
  344. <div className="title">绑定失败</div>
  345. <div className="text">该微信账户已绑定其他手机号,您可直接使用微信登入。</div>
  346. <div className="btn">
  347. <GButton
  348. radius
  349. onClick={() => {
  350. this.close();
  351. }}
  352. >
  353. Ok
  354. </GButton>
  355. </div>
  356. </div>
  357. );
  358. }
  359. }
  360. class Input extends Component {
  361. render() {
  362. const { className = '', onChange, placeholder, value, error, left, right } = this.props;
  363. return (
  364. <div className={`g-input-container ${className}`}>
  365. <div className={`g-input-wrapper ${error ? 'error' : ''}`}>
  366. {left && <div className="g-input-left">{left}</div>}
  367. <input
  368. className="g-input"
  369. placeholder={placeholder}
  370. value={value}
  371. onChange={data => onChange && onChange(data)}
  372. />
  373. {right && <div className="g-input-right">{right}</div>}
  374. </div>
  375. <div hidden={!error} className="g-input-error">
  376. {error}
  377. </div>
  378. </div>
  379. );
  380. }
  381. }
  382. class SelectInput extends Component {
  383. constructor(props) {
  384. super(props);
  385. this.state = { showSelect: false };
  386. }
  387. render() {
  388. const { showSelect } = this.state;
  389. const { className = '', onChange, placeholder, value, error, selectValue, select, onSelect } = this.props;
  390. return (
  391. <Input
  392. className={className}
  393. left={
  394. <span className="g-input-left-select" onClick={() => this.setState({ showSelect: !showSelect })}>
  395. {selectValue}
  396. <Icon type={showSelect ? 'up' : 'down'} />
  397. {showSelect && (
  398. <ul className="select-list">
  399. {select.map(row => {
  400. return (
  401. <li
  402. onClick={() => {
  403. this.setState({ showSelect: false });
  404. if (onSelect) onSelect(row.value);
  405. }}
  406. >
  407. {row.label}
  408. </li>
  409. );
  410. })}
  411. </ul>
  412. )}
  413. </span>
  414. }
  415. value={value}
  416. placeholder={placeholder}
  417. onChange={data => onChange && onChange(data)}
  418. error={error}
  419. />
  420. );
  421. }
  422. }
  423. export class VerificationInput extends Component {
  424. constructor(props) {
  425. super(props);
  426. this.timeKey = null;
  427. this.state = { loading: 0 };
  428. }
  429. componentWillUnmount() {
  430. if (this.timeKey) clearTimeout(this.timeKey);
  431. }
  432. onSend() {
  433. const { onSend, time = 60 } = this.props;
  434. if (onSend) {
  435. onSend().then(() => {
  436. this.setTime(time);
  437. });
  438. }
  439. }
  440. setTime(time) {
  441. this.setState({ loading: time });
  442. this.timeKey = setTimeout(() => {
  443. this.setTime(time - 1);
  444. }, 1000);
  445. }
  446. render() {
  447. const { loading } = this.state;
  448. const { className = '', onChange, placeholder, value } = this.props;
  449. return (
  450. <Input
  451. className={className}
  452. right={
  453. loading <= 0 ? (
  454. <span className="g-input-right-verification" onClick={() => this.onSend()}>
  455. 获取验证码
  456. </span>
  457. ) : (
  458. <span className="g-input-right-verification-loading">等待{loading}秒</span>
  459. )
  460. }
  461. value={value}
  462. placeholder={placeholder}
  463. onChange={data => onChange && onChange(data)}
  464. />
  465. );
  466. }
  467. }