index.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. import React, { Component } from 'react';
  2. import Cropper from 'react-cropper';
  3. import 'cropperjs/dist/cropper.css';
  4. import './index.less';
  5. import FileUpload from '@src/components/FileUpload';
  6. import Assets from '@src/components/Assets';
  7. import scale from '@src/services/Scale';
  8. import { asyncSMessage } from '@src/services/AsyncTools';
  9. import { SelectInput, VerificationInput, Input } from '../Login';
  10. import { MobileArea } from '../../../Constant';
  11. import Invite from '../Invite';
  12. import Modal from '../Modal';
  13. import { Common } from '../../stores/common';
  14. import { User } from '../../stores/user';
  15. import { My } from '../../stores/my';
  16. export class BindPhone extends Component {
  17. constructor(props) {
  18. super(props);
  19. this.props.data = this.props.data || {};
  20. this.state = Object.assign({ step: 0, data: {} }, this.initState(this.props));
  21. this.stepProp = {
  22. 0: {
  23. title: '绑定手机',
  24. onConfirm: props.onConfirm,
  25. },
  26. 1: {
  27. title: '绑定手机',
  28. onConfirm: () => {
  29. this.submit();
  30. },
  31. onCancel: props.onCancel,
  32. confirmText: '提交',
  33. },
  34. };
  35. }
  36. initState(props) {
  37. if (!props.show || this.props.show) return {};
  38. const data = Object.assign({}, props.data);
  39. if (!data.area) data.area = MobileArea[0].value;
  40. return { step: props.data.mobile ? 0 : 1, data };
  41. }
  42. componentWillReceiveProps(nextProps) {
  43. this.setState(this.initState(nextProps));
  44. }
  45. onNext() {
  46. this.setState({ step: 1 });
  47. }
  48. changeData(field, value) {
  49. let { data } = this.state;
  50. data = data || {};
  51. data[field] = value;
  52. this.setState({ data, error: null });
  53. }
  54. validMobile() {
  55. const { data } = this.state;
  56. const { area, mobile } = data;
  57. if (!area || !mobile) return;
  58. this.validNumber += 1;
  59. const number = this.validNumber;
  60. User.validMobile(area, mobile)
  61. .then(result => {
  62. if (number !== this.validNumber) return Promise.resolve();
  63. return result ? Promise.resolve() : Promise.reject(new Error('该手机已绑定其他账号,请更换手机号码'));
  64. })
  65. .catch(err => {
  66. this.setState({ mobileError: err.message });
  67. });
  68. }
  69. sendValid() {
  70. const { data, error } = this.state;
  71. const { area, mobile } = data;
  72. if (!area || !mobile || error) return Promise.reject();
  73. return Common.sendSms(area, mobile)
  74. .then(result => {
  75. if (result) {
  76. asyncSMessage('发送成功');
  77. this.setState({ error: '', validError: '' });
  78. } else {
  79. throw new Error('发送失败');
  80. }
  81. })
  82. .catch(err => {
  83. this.setState({ error: err.message });
  84. throw err;
  85. });
  86. }
  87. submit() {
  88. const { data, error } = this.state;
  89. if (!data.mobile || !data.area || error) return;
  90. const { area, mobile } = data;
  91. My.bindMobile(area, mobile)
  92. .then(() => {
  93. asyncSMessage('操作成功');
  94. this.setState({ step: 0 });
  95. User.infoHandle(Object.assign(this.props.data, { area, mobile }));
  96. this.props.onConfirm();
  97. })
  98. .catch(e => {
  99. this.setState({ error: e.message });
  100. });
  101. }
  102. render() {
  103. const { show } = this.props;
  104. const { step = 0 } = this.state;
  105. return (
  106. <Modal className="bind-phone-modal" show={show} width={630} {...this.stepProp[step]}>
  107. <div className="bind-phone-modal-wrapper">{this[`renderStep${step}`]()}</div>
  108. </Modal>
  109. );
  110. }
  111. renderStep0() {
  112. const { data } = this.state;
  113. return (
  114. <div className="step-0-layout">
  115. 已绑定手机 {data.mobile}
  116. <a onClick={() => this.onNext()}>修改</a>
  117. </div>
  118. );
  119. }
  120. renderStep1() {
  121. return (
  122. <div className="step-1-layout">
  123. <div className="label">手机号</div>
  124. <div className="input-layout">
  125. <SelectInput
  126. placeholder="请输入手机号"
  127. selectValue={this.state.data.area}
  128. select={MobileArea}
  129. value={this.state.data.mobile}
  130. error={this.state.error}
  131. onSelect={value => {
  132. this.changeData('area', value);
  133. this.validMobile();
  134. }}
  135. onChange={e => {
  136. this.changeData('mobile', e.target.value);
  137. this.validMobile();
  138. }}
  139. />
  140. <VerificationInput
  141. placeholder="请输入验证码"
  142. value={this.state.data.mobileVerifyCode}
  143. error={this.state.validError}
  144. onSend={() => {
  145. return this.sendValid();
  146. }}
  147. onChange={e => {
  148. this.changeData('mobileVerifyCode', e.target.value);
  149. }}
  150. />
  151. </div>
  152. </div>
  153. );
  154. }
  155. }
  156. export class BindEmail extends Component {
  157. constructor(props) {
  158. super(props);
  159. this.props.data = this.props.data || {};
  160. this.state = Object.assign({ step: 0, data: {} }, this.initState(this.props));
  161. this.stepProp = {
  162. 0: {
  163. title: '绑定邮箱',
  164. onConfirm: props.onConfirm,
  165. },
  166. 1: {
  167. title: '绑定邮箱',
  168. onConfirm: () => {
  169. this.submit();
  170. },
  171. onCancel: props.onCancel,
  172. confirmText: '提交',
  173. },
  174. };
  175. }
  176. initState(props) {
  177. if (!props.show || this.props.show) return {};
  178. return { step: props.data.email ? 0 : 1, data: Object.assign({}, props.data) };
  179. }
  180. componentWillReceiveProps(nextProps) {
  181. this.setState(this.initState(nextProps));
  182. }
  183. onNext() {
  184. this.setState({ step: 1 });
  185. }
  186. changeData(field, value) {
  187. let { data } = this.state;
  188. data = data || {};
  189. data[field] = value;
  190. this.setState({ data, error: null });
  191. }
  192. submit() {
  193. const { data, error } = this.state;
  194. if (!data.email || error) return;
  195. const { email } = data;
  196. My.bindEmail(email)
  197. .then(() => {
  198. asyncSMessage('操作成功');
  199. this.setState({ step: 0 });
  200. User.infoHandle(Object.assign(this.props.data, { email }));
  201. this.props.onConfirm();
  202. })
  203. .catch(e => {
  204. this.setState({ error: e.message });
  205. });
  206. }
  207. render() {
  208. const { show } = this.props;
  209. const { step = 0 } = this.state;
  210. return (
  211. <Modal className="bind-email-modal" show={show} width={630} {...this.stepProp[step]}>
  212. <div className="bind-email-modal-wrapper">{this[`renderStep${step}`]()}</div>
  213. </Modal>
  214. );
  215. }
  216. renderStep0() {
  217. const { data } = this.state;
  218. return (
  219. <div className="step-0-layout">
  220. 已绑定邮箱 {data.email}
  221. <a onClick={() => this.onNext()}>修改</a>
  222. </div>
  223. );
  224. }
  225. renderStep1() {
  226. return (
  227. <div className="step-1-layout">
  228. <div className="label">邮箱地址</div>
  229. <div className="input-layout">
  230. <Input
  231. placeholder="请输入邮箱"
  232. value={this.state.data.email}
  233. error={this.state.error}
  234. onChange={e => {
  235. this.changeData('email', e.target.value);
  236. }}
  237. />
  238. </div>
  239. </div>
  240. );
  241. }
  242. }
  243. export class EditInfo extends Component {
  244. constructor(props) {
  245. super(props);
  246. this.props.data = this.props.data || {};
  247. this.state = Object.assign({ data: {} }, this.initState(this.props));
  248. }
  249. initState(props) {
  250. if (props.image && this.waitImage) {
  251. const { state } = this;
  252. Common.upload(props.image)
  253. .then(result => {
  254. const { data } = this.state;
  255. data.avatar = result.url;
  256. this.setState({ data, uploading: false });
  257. })
  258. .catch(e => {
  259. this.setState({ imageError: e.message });
  260. });
  261. state.uploading = true;
  262. this.waitImage = false;
  263. return state;
  264. }
  265. if (!props.show || this.props.show) return {};
  266. return { data: Object.assign({}, props.data) };
  267. }
  268. componentWillReceiveProps(nextProps) {
  269. this.setState(this.initState(nextProps));
  270. }
  271. changeData(field, value) {
  272. let { data } = this.state;
  273. data = data || {};
  274. data[field] = value;
  275. this.setState({ data, error: null });
  276. }
  277. submit() {
  278. const { data, error } = this.state;
  279. if (!data.nickname || error) return;
  280. const { nickname, avatar } = data;
  281. My.editInfo({ nickname, avatar })
  282. .then(() => {
  283. asyncSMessage('操作成功');
  284. User.infoHandle(Object.assign(this.props.data, { nickname, avatar }));
  285. this.props.onConfirm();
  286. })
  287. .catch(e => {
  288. this.setState({ error: e.message });
  289. });
  290. }
  291. render() {
  292. const { show, onCancel, onSelectImage } = this.props;
  293. return (
  294. <Modal
  295. className="edit-info-modal"
  296. show={show}
  297. width={630}
  298. title="修改资料"
  299. confirmText="保存"
  300. onCancel={onCancel}
  301. onConfirm={() => {
  302. this.submit();
  303. }}
  304. >
  305. <div className="edit-info-modal-wrapper">
  306. <div className="edit-info-modal-block">
  307. <div className="label">昵称</div>
  308. <div className="input-layout">
  309. <Input
  310. placeholder="2-20位,不可使用特殊字符。"
  311. value={this.state.data.nickname || ''}
  312. error={this.state.error}
  313. onChange={e => {
  314. this.changeData('nickname', e.target.value);
  315. }}
  316. />
  317. </div>
  318. </div>
  319. <div className="edit-info-modal-block">
  320. <div className="label">头像</div>
  321. <div className="input-layout">
  322. <FileUpload
  323. uploading={this.state.uploading}
  324. value={this.state.data.avatar}
  325. onUpload={({ file }) => {
  326. this.waitImage = true;
  327. onSelectImage(file);
  328. return Promise.reject();
  329. }}
  330. />
  331. {this.state.imageError || ''}
  332. </div>
  333. </div>
  334. </div>
  335. </Modal>
  336. );
  337. }
  338. }
  339. export class RealAuth extends Component {
  340. constructor(props) {
  341. super(props);
  342. this.state = { data: {} };
  343. }
  344. render() {
  345. const { show, onConfirm } = this.props;
  346. return (
  347. <Modal
  348. className="real-auth-modal"
  349. show={show}
  350. width={630}
  351. title="实名认证"
  352. confirmText="好的,知道了"
  353. btnAlign="center"
  354. onConfirm={onConfirm}
  355. >
  356. <div className="real-auth-modal-wrapper">
  357. <div className="real-auth-text">
  358. <div className="t1">完成实名认证即可领取:</div>
  359. <div className="t2">6个月VIP权限 和 5折机经优惠劵。</div>
  360. <div className="t3">扫码关注公众号,点击“我的-实名认证”</div>
  361. </div>
  362. <div className="real-auth-qrcode">
  363. <Assets name="qrcode" />
  364. </div>
  365. </div>
  366. </Modal>
  367. );
  368. }
  369. }
  370. export class EditAvatar extends Component {
  371. constructor(props) {
  372. super(props);
  373. this.state = Object.assign({ data: {} }, this.initState(this.props));
  374. }
  375. initState(props) {
  376. if (props.image) this.loadImage(props.image);
  377. if (!props.show || this.props.show) return {};
  378. return { data: {} };
  379. }
  380. componentWillReceiveProps(nextProps) {
  381. this.setState(this.initState(nextProps));
  382. }
  383. loadImage(file) {
  384. this.defaultZoomValue = 1;
  385. if (window.FileReader) {
  386. const reader = new FileReader();
  387. reader.readAsDataURL(file);
  388. // 渲染文件
  389. reader.onload = arg => {
  390. this.setState({ image: arg.target.result, fileName: file.name, zoomValue: 1 });
  391. };
  392. } else {
  393. const img = new Image();
  394. img.onload = function() {
  395. const canvas = document.createElement('canvas');
  396. canvas.height = img.height;
  397. canvas.width = img.width;
  398. const ctx = canvas.getContext('2d');
  399. ctx.drawImage(img, 0, 0);
  400. this.setState({ image: canvas.toDataURL() });
  401. };
  402. img.src = '1.gif';
  403. }
  404. }
  405. computerZoom() {
  406. const data = this.cropRef.getImageData();
  407. this.defaultZoomValue = 200 / parseFloat(Math.max(data.naturalWidth, data.naturalHeight));
  408. }
  409. crop() {
  410. // image in dataUrl
  411. // console.log(this.cropRef.getCroppedCanvas().toDataURL())
  412. // const canvas = this.cropRef.getCroppedCanvas();
  413. // const avatar = scale(this.props.crop, canvas, 'png-src');
  414. // let scaleCanvas = this.cropRef.getCroppedCanvas(this.props.crop)
  415. // this.setState({ avatar });
  416. }
  417. select() {
  418. const { fileName } = this.state;
  419. const { onConfirm } = this.props;
  420. const canvas = this.cropRef.getCroppedCanvas();
  421. const scaleCanvas = scale(this.props.crop, canvas);
  422. // let scaleCanvas = this.cropRef.getCroppedCanvas(this.props.crop)
  423. scaleCanvas.toBlob(blob => {
  424. const file = new File([blob], fileName);
  425. onConfirm(file);
  426. });
  427. }
  428. render() {
  429. const { show, onCancel } = this.props;
  430. const { image } = this.state;
  431. return (
  432. <Modal
  433. className="edit-avatar-modal"
  434. show={show}
  435. width={630}
  436. title="调整头像"
  437. confirmText="保存头像"
  438. onConfirm={() => {
  439. this.select();
  440. }}
  441. onCancel={onCancel}
  442. >
  443. <div className="edit-avatar-modal-wrapper">
  444. <div className="edit-avatar-o">
  445. <Cropper
  446. ref={ref => {
  447. this.cropRef = ref;
  448. }}
  449. src={image}
  450. ready={() => {
  451. this.computerZoom();
  452. }}
  453. style={{ height: 360, width: 360 }}
  454. // Cropper.js options
  455. aspectRatio={1}
  456. viewMode={0}
  457. // autoCropArea={0.8}
  458. preview=".img-preview"
  459. // dragMode='move'
  460. guides={false}
  461. movable={false}
  462. rotatable={false}
  463. scalable={false}
  464. // zoom={(value) => {
  465. // console.log('zoom', value);
  466. // const zoomValue = value * this.defaultZoomValue / 50;
  467. // this.cropRef.zoomTo(zoomValue);
  468. // }}
  469. cropmove={() => {
  470. this.crop();
  471. }}
  472. toggleDragModeOnDblclick={false}
  473. cropBoxResizable
  474. />
  475. </div>
  476. <div className="edit-avatar-r">
  477. <div className="text">头像预览</div>
  478. <div className="img-preview" style={{ width: 100, height: 100, overflow: 'hidden' }} />
  479. </div>
  480. </div>
  481. </Modal>
  482. );
  483. }
  484. }
  485. export class InviteModal extends Component {
  486. constructor(props) {
  487. super(props);
  488. this.state = { data: {} };
  489. }
  490. render() {
  491. const { show, onClose, data } = this.props;
  492. return (
  493. <Modal className="invite-modal" show={show} width={630} title="邀请好友" onClose={onClose}>
  494. <div className="invite-modal-wrapper">
  495. <div className="tip">每邀请一位小伙伴加入“千行GMAT”, 您的VIP权限会延长7天,可累加 无上限!赶紧行动吧~</div>
  496. <Invite data={data} />
  497. </div>
  498. </Modal>
  499. );
  500. }
  501. }