index.js 14 KB

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