register.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. <script lang="ts">
  2. import Vue from 'vue'
  3. import countryCode from '@/lib/countryCode.json'
  4. import { sendSmsCode, sentEmailCode, register, confirmEmail, getGoogleStatus, getLinkedinStatus } from '@/api/user'
  5. import { getExpoInfo } from '@/api/expo'
  6. import thirdLogin from '@/views/login/third-login.vue'
  7. export default Vue.extend({
  8. name: 'Index',
  9. components: {
  10. thirdLogin
  11. },
  12. data() {
  13. return {
  14. regWay: 0, // 0:手机注册 1:邮箱注册
  15. countryCode,
  16. loginInfo: {
  17. phone: '',
  18. countryCode: '86',
  19. password: '',
  20. password_2: '',
  21. smsCode: '',
  22. email: '',
  23. emailCode: '',
  24. pre_register_key: '',
  25. showPasswordDialog: false
  26. },
  27. emailCounter: 0,
  28. emailTimer: null,
  29. smsCounter: 0,
  30. smsTimer: null,
  31. expoData: {},
  32. expo_key: '',
  33. subTimer: '',
  34. subTime: '',
  35. isLogin: false,
  36. third_login: '',
  37. third_token: '',
  38. need_bind: false,
  39. is_back: false
  40. }
  41. },
  42. computed: {
  43. user() { return this.$store.state.user.user }
  44. },
  45. mounted() {
  46. this.init()
  47. },
  48. beforeDestroy() {
  49. clearInterval(this.subTimer)
  50. },
  51. methods: {
  52. init() {
  53. if (this.$route.query.expo_key) {
  54. this.expo_key = this.$route.query.expo_key
  55. getExpoInfo(this.expo_key).then(res => {
  56. this.expoData = res.data
  57. this.subTimer = setInterval(() => {
  58. this.subTime = Date.parse(res.data.end_date) - Date.now()
  59. }, 1000)
  60. }).catch(err => {
  61. this.$notify({
  62. title: '提示',
  63. message: '展会信息获取失败:'+err,
  64. type: 'error'
  65. })
  66. })
  67. } else {
  68. this.$router.push({
  69. name: 'login'
  70. })
  71. }
  72. },
  73. tokenLogin(token) {
  74. this.$store.dispatch('tokenLogin', { token: token }).then(res => {
  75. this.gotoForm()
  76. }).catch(err => {
  77. this.$notify({
  78. title: '提示',
  79. message: '使用本地令牌登录失败'+err,
  80. type: 'error'
  81. })
  82. this.$message.error(err.message)
  83. })
  84. },
  85. openThirdLogin(type) {
  86. this.is_back = false
  87. this.third_login = type
  88. },
  89. closeThirdLogin() {
  90. this.is_back = false
  91. this.need_bind = false
  92. this.third_login = ''
  93. this.third_token = ''
  94. },
  95. parseQuery() {
  96. const url = new URL(window.location.href.replace('#', '?'))
  97. function showError() {
  98. this.$notify({
  99. title: '登录失败',
  100. message: '登录失败,请稍后重试。',
  101. type: 'error'
  102. })
  103. this.third_login = ''
  104. this.is_back = false
  105. }
  106. if (url.searchParams.get('code')) {
  107. this.is_back = true
  108. this.third_login = 'linkin'
  109. getLinkedinStatus(
  110. url.searchParams.get('state'),
  111. url.searchParams.get('code')
  112. ).then(res => {
  113. if (res.data.api_token) {
  114. this.tokenLogin(res.data.api_token)
  115. } else if (res.data.third_token) {
  116. this.third_token = res.data.third_token
  117. this.need_bind = true
  118. } else {
  119. showError()
  120. }
  121. }).catch(err => {
  122. this.$notify({
  123. title: '提示',
  124. message: '领英授权登录失败'+err,
  125. type: 'error'
  126. })
  127. showError()
  128. })
  129. }
  130. if (url.searchParams.get('access_token')) {
  131. this.is_back = true
  132. this.third_login = 'google'
  133. getGoogleStatus(
  134. url.searchParams.get('state'),
  135. url.searchParams.get('access_token')
  136. ).then(res => {
  137. if (res.data.api_token) {
  138. this.tokenLogin(res.data.api_token)
  139. } else if (res.data.third_token) {
  140. this.third_token = res.data.third_token
  141. this.need_bind = true
  142. } else {
  143. showError()
  144. }
  145. }).catch(err => {
  146. this.$notify({
  147. title: '提示',
  148. message: '谷歌授权登录失败'+err,
  149. type: 'error'
  150. })
  151. showError()
  152. })
  153. }
  154. },
  155. sentSms() {
  156. if (this.loginInfo.phone === '') {
  157. this.$message.error('请填写电话号码!')
  158. return
  159. }
  160. if (this.smsTimer) {
  161. return
  162. } else {
  163. sendSmsCode(this.loginInfo.phone, this.loginInfo.countryCode).then(res => {
  164. this.$message.success('发送成功!')
  165. this.smsCounter = 60
  166. this.smsTimer = setInterval(() => {
  167. this.smsCounter--
  168. if (this.smsCounter <= 0) {
  169. clearInterval(this.smsTimer)
  170. this.smsTimer = null
  171. }
  172. }, 1000)
  173. }).catch(err => {
  174. this.$notify({
  175. title: '提示',
  176. message: '验证码发送失败:'+err,
  177. type: 'error'
  178. })
  179. })
  180. }
  181. },
  182. sentEmail() {
  183. if (this.loginInfo.email === '') {
  184. this.$message.error('请填写邮箱!')
  185. return
  186. }
  187. if (this.emailTimer) {
  188. return
  189. } else {
  190. sentEmailCode(this.loginInfo.email).then(res => {
  191. this.$message.success('发送成功!')
  192. this.emailCounter = 60
  193. this.emailTimer = setInterval(() => {
  194. this.emailCounter--
  195. if (this.emailCounter <= 0) {
  196. clearInterval(this.emailTimer)
  197. this.emailTimer = null
  198. }
  199. }, 1000)
  200. }).catch(err => {
  201. this.$notify({
  202. title: '提示',
  203. message: '验证码发送失败'+err,
  204. type: 'error'
  205. })
  206. })
  207. }
  208. },
  209. submitLogin() {
  210. let username = ''
  211. let password = ''
  212. if (this.regWay) {
  213. if (this.loginInfo.email === '' || this.loginInfo.password === '') {
  214. this.$message.error('请填写账号与密码!')
  215. return
  216. }
  217. username = this.loginInfo.email
  218. password = this.loginInfo.password
  219. } else {
  220. if (this.loginInfo.smsCode === '' || this.loginInfo.phone === '') {
  221. this.$message.error('请填写手机号码与验证码!')
  222. return
  223. }
  224. username = this.loginInfo.phone
  225. password = this.loginInfo.smsCode
  226. }
  227. this.$store.dispatch('login', {
  228. username: username,
  229. password: password,
  230. phone: username,
  231. vaild_code: password,
  232. login_type: this.regWay ? '0' : '1',
  233. login_portal: 0
  234. }).then(res => {
  235. this.gotoForm()
  236. }).catch(err => {
  237. this.$notify({
  238. title: '提示',
  239. message: '登录失败:'+err,
  240. type: 'error'
  241. })
  242. })
  243. },
  244. gotoForm() {
  245. if (this.expo_key) {
  246. this.$router.push({
  247. name: 'userForm',
  248. params: {
  249. url: this.expo_key
  250. }
  251. })
  252. }
  253. },
  254. register() {
  255. if (this.regWay) {
  256. if (this.loginInfo.email === '' || this.loginInfo.emailCode === '') {
  257. this.$message.error('请填写邮箱与验证码!')
  258. return
  259. }
  260. register(this.loginInfo.email, '', this.loginInfo.emailCode, 1, '').then(res => {
  261. this.loginInfo.pre_register_key = res.data.pre_register_key
  262. this.loginInfo.showPasswordDialog = true
  263. }).catch(err => {
  264. this.$notify({
  265. title: '提示',
  266. message: '注册失败:'+err,
  267. type: 'error'
  268. })
  269. })
  270. } else {
  271. if (this.loginInfo.phone === '' || this.loginInfo.smsCode === '') {
  272. this.$message.error('请填写手机号码与验证码!')
  273. return
  274. }
  275. register('', this.loginInfo.phone, this.loginInfo.smsCode, 0, this.loginInfo.countryCode).then(res => {
  276. this.isLogin = true
  277. this.$message.success('注册成功!请前往登录')
  278. }).catch(err => {
  279. this.$notify({
  280. title: '提示',
  281. message: '注册失败:'+err,
  282. type: 'error'
  283. })
  284. })
  285. }
  286. },
  287. checkPassword() {
  288. if (this.loginInfo.password !== this.loginInfo.password_2) {
  289. this.$message.error('两次密码输入不一致!')
  290. return false
  291. }
  292. this.loginInfo.showPasswordDialog = false
  293. confirmEmail(this.loginInfo.pre_register_key, this.loginInfo.password, this.loginInfo.password_2).then(res => {
  294. this.isLogin = true
  295. this.$message.success('注册成功!请前往登录')
  296. }).catch(err => {
  297. this.$notify({
  298. title: '提示',
  299. message: '注册失败:'+err,
  300. type: 'error'
  301. })
  302. })
  303. }
  304. }
  305. })
  306. </script>
  307. <template>
  308. <div class="body">
  309. <div class="head">
  310. <div class="title">
  311. {{ isLogin?'登录账号':'注册账号' }}
  312. </div>
  313. <div class="desc">
  314. 中国大陆观众建议使用手机号码进行注册登录。
  315. </div>
  316. </div>
  317. <div v-if="expo_key" class="time-counter">
  318. <div class="title">距离展览开幕时间</div>
  319. <div class="counter-list">
  320. <div class="counter-item">
  321. <div class="num">{{ new Date(subTime).getDate() || '0' }}</div>
  322. <div class="unit">天</div>
  323. </div>
  324. <div class="counter-item">
  325. <div class="num">{{ new Date(subTime).getHours() || '0' }}</div>
  326. <div class="unit">时</div>
  327. </div>
  328. <div class="counter-item">
  329. <div class="num">{{ new Date(subTime).getMinutes() || '0' }}</div>
  330. <div class="unit">分</div>
  331. </div>
  332. <div class="counter-item">
  333. <div class="num">{{ new Date(subTime).getSeconds() || '0' }}</div>
  334. <div class="unit">秒</div>
  335. </div>
  336. </div>
  337. </div>
  338. <div v-if="isLogin" class="register-body">
  339. <div class="tab">
  340. <div class="tab-item" :class="regWay?'':'active'" @click="regWay=0">验证码登录</div>
  341. <div class="tab-item" :class="regWay?'active':''" @click="regWay=1">账号密码登录</div>
  342. </div>
  343. <template v-if="regWay">
  344. <div class="form-item">
  345. <div class="label">账号</div>
  346. <el-input v-model="loginInfo.email" placeholder="请输入账号" />
  347. </div>
  348. <div class="form-item">
  349. <div class="label">密码</div>
  350. <div class="sms-cont">
  351. <el-input v-model="loginInfo.password" show-password placeholder="请输入密码" />
  352. </div>
  353. </div>
  354. </template>
  355. <template v-else>
  356. <div class="form-item">
  357. <div class="label">
  358. 国家/地区
  359. </div>
  360. <el-select v-model="loginInfo.countryCode" placeholder="请选择国家/地区">
  361. <el-option v-for="(item,index) in countryCode" :key="item.country_code+index" :label="'+'+item.phone_code+'('+item.chinese_name+')'" :value="item.phone_code" />
  362. </el-select>
  363. </div>
  364. <div class="form-item">
  365. <div class="label">手机号码</div>
  366. <el-input v-model="loginInfo.phone" placeholder="请输入手机号码" />
  367. </div>
  368. <div class="form-item">
  369. <div class="label">验证码</div>
  370. <div class="sms-cont">
  371. <el-input v-model="loginInfo.smsCode" placeholder="请输入验证码" />
  372. <el-button :disabled="!!smsTimer" type="primary" @click="sentSms()">{{ smsTimer?smsCounter+'秒后重新获取':'获取验证码' }}</el-button>
  373. </div>
  374. </div>
  375. </template>
  376. <div class="piracy">
  377. 登录即表示同意
  378. <span>《隐私政策》</span>
  379. <span>《用户协议》</span>
  380. </div>
  381. <el-button type="primary" @click="submitLogin()">登录</el-button>
  382. <el-button type="text" @click="isLogin=false">没有账号?去注册。</el-button>
  383. <div class="third-login">
  384. <div @click="openThirdLogin('wechat')" class="button wechat">
  385. <img class="icon" src="/static/image/icon/wechat.png">
  386. 微信扫码登录
  387. </div>
  388. <div @click="openThirdLogin('google')" class="button google">
  389. <img class="icon" src="/static/image/icon/google.png">
  390. 谷歌授权登录
  391. </div>
  392. <div @click="openThirdLogin('linkin')" class="button linkin">
  393. <img class="icon" src="/static/image/icon/linkin.png">
  394. 领英授权登录
  395. </div>
  396. </div>
  397. </div>
  398. <div v-else class="register-body">
  399. <div class="tab">
  400. <div class="tab-item" :class="regWay?'':'active'" @click="regWay=0">手机注册</div>
  401. <div class="tab-item" :class="regWay?'active':''" @click="regWay=1">邮箱注册</div>
  402. </div>
  403. <template v-if="regWay">
  404. <div class="form-item">
  405. <div class="label">账号</div>
  406. <el-input v-model="loginInfo.email" placeholder="请输入电子邮箱地址" />
  407. </div>
  408. <div class="form-item">
  409. <div class="label">密码</div>
  410. <div class="sms-cont">
  411. <el-input v-model="loginInfo.emailCode" placeholder="请输入验证码" />
  412. <el-button :disabled="emailTimer" type="primary" @click="sentEmail()">{{ emailTimer?emailCounter+'秒后重新获取':'获取验证码' }}</el-button>
  413. </div>
  414. </div>
  415. </template>
  416. <template v-else>
  417. <div class="form-item">
  418. <div class="label">
  419. 国家/地区
  420. </div>
  421. <el-select v-model="loginInfo.countryCode" placeholder="请选择国家/地区">
  422. <el-option v-for="(item,index) in countryCode" :key="item.country_code+index" :label="'+'+item.phone_code+'('+item.chinese_name+')'" :value="item.phone_code" />
  423. </el-select>
  424. </div>
  425. <div class="form-item">
  426. <div class="label">手机号码</div>
  427. <el-input v-model="loginInfo.phone" placeholder="请输入手机号码" />
  428. </div>
  429. <div class="form-item">
  430. <div class="label">验证码</div>
  431. <div class="sms-cont">
  432. <el-input v-model="loginInfo.smsCode" placeholder="请输入验证码" />
  433. <el-button :disabled="!!smsTimer" type="primary" @click="sentSms()">{{ smsTimer?smsCounter+'秒后重新获取':'获取验证码' }}</el-button>
  434. </div>
  435. </div>
  436. </template>
  437. <div class="piracy">
  438. 注册即表示同意
  439. <span>《隐私政策》</span>
  440. <span>《用户协议》</span>
  441. </div>
  442. <el-button type="primary" @click="register()">注册</el-button>
  443. <el-button type="text" @click="isLogin=true">已有账号?去登陆。</el-button>
  444. <div class="third-login">
  445. <div @click="openThirdLogin('wechat')" class="button wechat">
  446. <img class="icon" src="/static/image/icon/wechat.png">
  447. 微信扫码登录
  448. </div>
  449. <div @click="openThirdLogin('google')" class="button google">
  450. <img class="icon" src="/static/image/icon/google.png">
  451. 谷歌授权登录
  452. </div>
  453. <div @click="openThirdLogin('linkin')" class="button linkin">
  454. <img class="icon" src="/static/image/icon/linkin.png">
  455. 领英授权登录
  456. </div>
  457. </div>
  458. </div>
  459. <el-dialog
  460. title="设置初始密码"
  461. custom-class="password-dialog"
  462. :close-on-click-modal="false"
  463. :close-on-press-escape="false"
  464. :show-close="false"
  465. :visible.sync="loginInfo.showPasswordDialog"
  466. width="30%"
  467. >
  468. <div class="dialog-body">
  469. <div class="label">请设置密码</div>
  470. <el-input v-model="loginInfo.password" placeholder="请设置密码" show-password />
  471. <div class="label">请再次输入密码</div>
  472. <el-input v-model="loginInfo.password_2" placeholder="请再次输入密码" show-password />
  473. </div>
  474. <span slot="footer" class="dialog-footer">
  475. <el-button type="primary" @click="checkPassword()">确 定</el-button>
  476. </span>
  477. </el-dialog>
  478. <div :class="['mask',third_login?'show':'']">
  479. <div class="third-body">
  480. <span class="el-icon-close" @click="closeThirdLogin()" />
  481. <third-login
  482. v-if="third_login"
  483. :is_back="is_back"
  484. :need_bind.sync="need_bind"
  485. :third_token.sync="third_token"
  486. :login_type="third_login"
  487. @tokenLogin="tokenLogin"
  488. />
  489. </div>
  490. </div>
  491. </div>
  492. </template>
  493. <style scoped lang="scss">
  494. .body{
  495. width: 100%;
  496. height: 100%;
  497. overflow: hidden;
  498. overflow-y: auto;
  499. width: 100%;
  500. .mask{
  501. transition-duration: 300ms;
  502. width: 100%;
  503. height: 100%;
  504. background-color: #00000044;
  505. left: 0;
  506. top: 0;
  507. position: fixed;
  508. display: flex;
  509. justify-content: center;
  510. align-items: center;
  511. pointer-events: none;
  512. opacity: 0;
  513. .third-body{
  514. position: relative;
  515. transition-duration: 300ms;
  516. background-color: white;
  517. transform: translateY(40px);
  518. width: 460px;
  519. height: 600px;
  520. .el-icon-close{
  521. position: absolute;
  522. right: 8px;
  523. top: 8px;
  524. border-radius: 4px;
  525. padding: 8px;
  526. transition-duration: 300ms;
  527. cursor: pointer;
  528. &:hover{
  529. background-color: #00000011;
  530. }
  531. }
  532. }
  533. &.show{
  534. pointer-events: all;
  535. opacity: 1;
  536. .third-body{
  537. transform: translateY(0);
  538. }
  539. }
  540. }
  541. .register-body{
  542. margin: 48px auto 64px;
  543. max-width: 800px;
  544. display: flex;
  545. flex-direction: column;
  546. gap: 16px;
  547. padding: 40px 50px;
  548. border-radius: 4px;
  549. box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
  550. background: white;
  551. .third-login{
  552. display: grid;
  553. grid-gap: 16px;
  554. grid-template-columns: 1fr 1fr 1fr;
  555. .button{
  556. transition-duration: 300ms;
  557. cursor: pointer;
  558. display: flex;
  559. align-items: center;
  560. gap: 8px;
  561. justify-content: center;
  562. padding: 8px 16px;
  563. font-size: 16px;
  564. color: black;
  565. background-color: #FFFFFF;
  566. .icon{
  567. transition-duration: 300ms;
  568. width: 20px;
  569. height: 20px;
  570. object-fit: contain;
  571. filter: brightness(1);
  572. }
  573. &:hover{
  574. color: white;
  575. .icon{
  576. filter: brightness(100);
  577. }
  578. &.wechat{
  579. background: #0E932E;
  580. }
  581. &.google{
  582. background: #EA4335;
  583. }
  584. &.linkin{
  585. background: #0B66C1;
  586. }
  587. }
  588. }
  589. }
  590. .piracy{
  591. color: #6B7280;
  592. display: flex;
  593. font-size: 16px;
  594. span{
  595. color: #0052CC;
  596. }
  597. }
  598. .form-item{
  599. .label{
  600. color: #374151;
  601. font-size: 16px;
  602. margin-bottom: 8px;
  603. }
  604. .el-select{
  605. width: 100%;
  606. }
  607. .sms-cont{
  608. display: flex;
  609. gap: 12px;
  610. }
  611. }
  612. .tab{
  613. display: flex;
  614. border-bottom: 3px solid #E5E7EB;
  615. .tab-item{
  616. cursor: pointer;
  617. padding: 8px 16px;
  618. color: #6B7280;
  619. transform: translateY(3px);
  620. border-bottom: 3px solid transparent;
  621. &.active{
  622. color: #0052CC;
  623. border-bottom: 3px solid #0052CC;
  624. }
  625. }
  626. }
  627. }
  628. .time-counter{
  629. max-width: 800px;
  630. margin: 32px auto 0;
  631. background: #4DA9FF22;
  632. padding: 24px;
  633. border-radius: 8px;
  634. .counter-list{
  635. display: grid;
  636. grid-template-columns: repeat(4, 1fr);
  637. gap: 16px;
  638. .counter-item{
  639. background: white;
  640. padding: 16px 0;
  641. border-radius: 4px;
  642. text-align: center;
  643. .num{
  644. color: #4D7CFF;
  645. font-size: 30px;
  646. font-weight: bold;
  647. }
  648. .unit{
  649. font-size: 14px;
  650. color: #6B7280;
  651. }
  652. }
  653. }
  654. .title{
  655. font-size: 18px;
  656. margin-bottom: 12px;
  657. }
  658. }
  659. .head{
  660. margin: 48px auto 0;
  661. max-width: 800px;
  662. .title{
  663. text-align: center;
  664. font-size: 24px;
  665. font-weight: bold;
  666. }
  667. .desc{
  668. margin-top: 8px;
  669. text-align: center;
  670. color: #4B5563;
  671. }
  672. }
  673. }
  674. </style>
  675. <style>
  676. body{
  677. background: #F9FAFB;
  678. .password-dialog{
  679. .dialog-body{
  680. .label{
  681. margin: 6px 0;
  682. }
  683. }
  684. }
  685. }
  686. </style>