index.js 15 KB

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