login.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. <template>
  2. <div class="login-container" id="particles-js">
  3. <div class="bg-logo anim"></div>
  4. <div class="login-form">
  5. <div class="title-container">
  6. <!-- <h3 class="title">{{appName}}</h3> -->
  7. <img class="csms-logo" src="../../icons/logo.png"/>
  8. <p class="title" v-if="showOTP">Please enter 6-digit OTP sent to your email</p>
  9. <p class="title" v-else-if="passwordTitle">{{passwordTitle}}</p>
  10. <p class="title" v-else>{{showSetPassword ? "Reset Password" : "CSMS LOGIN"}}</p>
  11. <p class="sub-title" v-if="passwordSubTitle">{{passwordSubTitle}}</p>
  12. </div>
  13. <div v-if="showResetSuccess">
  14. <div style="height: 200px;"></div>
  15. <el-button
  16. :loading="loading"
  17. type="primary"
  18. class="login-button"
  19. @click="handleShowLogin">
  20. GO TO LOGIN
  21. </el-button>
  22. </div>
  23. <OtpView
  24. v-else-if="showOTP"
  25. :loginInfo="loginForm"
  26. :resetInfo="newPasswordInfo"
  27. :isReset="showSetPassword"
  28. @onLogin="handleLogin"
  29. @onReset="handleResetSuccess"/>
  30. <el-form
  31. v-else-if="!showSetPassword"
  32. ref="loginForm"
  33. :model="loginForm"
  34. :rules="loginRules"
  35. autocomplete="on"
  36. label-position="left">
  37. <el-form-item prop="email">
  38. <div class="form-login-input">
  39. <span class="svg-container">
  40. <svg-icon icon-class="email" />
  41. </span>
  42. <el-input
  43. ref="username"
  44. v-model="loginForm.email"
  45. name="email"
  46. type="text"
  47. tabindex="1"
  48. autocomplete="on"
  49. clearable
  50. maxlength="50"
  51. @focus="() => focusFiled.email = true"
  52. @blur="() => focusFiled.email = false"/>
  53. <label :class='isNameValid ? "focus" : ""'>Email</label>
  54. </div>
  55. </el-form-item>
  56. <el-form-item prop="password">
  57. <div class="form-login-input">
  58. <span class="svg-container">
  59. <svg-icon icon-class="password" />
  60. </span>
  61. <el-input
  62. ref="password"
  63. v-model="loginForm.password"
  64. :type="passwordType"
  65. name="password"
  66. tabindex="2"
  67. autocomplete="off"
  68. clearable
  69. maxlength="32"
  70. @keyup.enter.native="onPreLogin"
  71. @focus="() => focusFiled.password = true"
  72. @blur="() => focusFiled.password = false"
  73. />
  74. <span class="show-pwd" @click="showPwd">
  75. <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
  76. </span>
  77. <label :class='isPsdValid ? "focus" : ""'>Password</label>
  78. </div>
  79. </el-form-item>
  80. <div>
  81. <el-button
  82. :loading="loading"
  83. type="primary"
  84. class="login-button"
  85. @click.native.prevent="onPreLogin">
  86. LOGIN
  87. </el-button>
  88. <el-button
  89. type="content"
  90. class="forgot-button"
  91. @click="handleForgotPassword">
  92. FORGET PASSWORD
  93. </el-button>
  94. </div>
  95. </el-form>
  96. <Password
  97. v-else
  98. :email="expireEmail"
  99. @back="handleShowLogin"
  100. @confirm="onResetPasswordConfirm"/>
  101. </div>
  102. <div class="copyinfo">©{{copyYear}}&nbsp; {{company}}</div>
  103. </div>
  104. </template>
  105. <script>
  106. import VueRouter from 'vue-router'
  107. import { Message } from 'element-ui'
  108. import settings from '../../settings.js'
  109. import {getEmail} from '../../utils/auth.js'
  110. import particles from 'particles.js'
  111. import animateJson from './animate.json'
  112. import Password from './Password.vue'
  113. import OtpView from './otp.vue'
  114. import api from '@/api/user'
  115. import {parseTime} from '@/utils/index'
  116. import permission from '@/utils/permission.js'
  117. const { isNavigationFailure, NavigationFailureType } = VueRouter
  118. export default {
  119. name: 'Login',
  120. data() {
  121. const validatePassword = (rule, value, callback) => {
  122. if (value.length == 0) {
  123. callback(new Error("Please type password"))
  124. } else {
  125. if (settings.enablePassword12) {
  126. if (value.length < 12) {
  127. callback(new Error('The password can not be less than 12 characters'))
  128. } else {
  129. callback()
  130. }
  131. } else {
  132. if (value.length < 6) {
  133. callback(new Error('The password can not be less than 6 characters'))
  134. } else {
  135. callback()
  136. }
  137. }
  138. }
  139. }
  140. return {
  141. year: 2023,
  142. appName: settings.title,
  143. company: settings.company,
  144. loginForm: {
  145. otp: '',
  146. email: '',
  147. password: ''
  148. },
  149. loginRules: {
  150. email: [{
  151. required: true,
  152. trigger: 'blur',
  153. message: "Please type email"
  154. }, {
  155. pattern: /^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/,
  156. trigger: 'blur',
  157. message: 'Please type a correct email'
  158. }],
  159. password: [{
  160. required: true,
  161. trigger: 'blur',
  162. validator: validatePassword
  163. }]
  164. },
  165. passwordType: 'password',
  166. loading: false,
  167. redirect: undefined,
  168. otherQuery: {},
  169. focusFiled: {
  170. email: false,
  171. password: false
  172. },
  173. showOTP: false,
  174. expireEmail: "",
  175. showSetPassword: false,
  176. showResetSuccess: false,
  177. passwordTitle: "",
  178. passwordSubTitle: "",
  179. newPasswordInfo: {}
  180. }
  181. },
  182. watch: {
  183. $route: {
  184. handler: function(route) {
  185. const query = route.query
  186. if (query) {
  187. this.redirect = query.redirect
  188. this.otherQuery = this.getOtherQuery(query)
  189. }
  190. },
  191. immediate: true
  192. }
  193. },
  194. computed: {
  195. copyYear() {
  196. const year = new Date().getFullYear();
  197. if (year > this.year) {
  198. return this.year + "-" + year;
  199. } else {
  200. return this.year;
  201. }
  202. },
  203. isNameValid() {
  204. return (this.loginForm.email !== "" || this.focusFiled.email)
  205. },
  206. isPsdValid() {
  207. return (this.loginForm.password !== "" || this.focusFiled.password)
  208. }
  209. },
  210. components: {Password, OtpView},
  211. created() {
  212. // window.addEventListener('storage', this.afterQRScan)
  213. const user = getEmail()
  214. if (user) {
  215. this.loginForm.email = user;
  216. }
  217. const pasd = localStorage.getItem("vbe-vapt-password");
  218. if (pasd) {
  219. this.loginForm.password = pasd;
  220. }
  221. //console.log(animateJson);
  222. //
  223. },
  224. beforeMount() {
  225. this.$nextTick(() => {
  226. //const sss = document.getElementById("particles-js");
  227. //particlesJS('particles-js', animateJson);
  228. })
  229. },
  230. mounted() {
  231. if (this.loginForm.email === '') {
  232. this.$refs.username.focus()
  233. } else if (this.loginForm.password === '') {
  234. this.$refs.password.focus()
  235. }
  236. },
  237. destroyed() {
  238. // window.removeEventListener('storage', this.afterQRScan)
  239. },
  240. methods: {
  241. showPwd() {
  242. if (this.passwordType === 'password') {
  243. this.passwordType = 'text'
  244. } else {
  245. this.passwordType = 'password'
  246. }
  247. this.$nextTick(() => {
  248. this.$refs.password.focus()
  249. })
  250. },
  251. handleShowLogin() {
  252. this.showOTP = false;
  253. this.showSetPassword = false;
  254. this.showResetSuccess = false;
  255. this.expireEmail = "";
  256. this.passwordTitle = "";
  257. this.passwordSubTitle = "";
  258. this.newPasswordInfo = {};
  259. },
  260. handleResetSuccess() {
  261. this.handleShowLogin();
  262. this.showResetSuccess = true;
  263. this.passwordTitle = "Successful. Your password has been changed.";
  264. this.passwordSubTitle = parseTime(new Date(), "{d}/{m}/{y}");
  265. },
  266. handleForgotPassword() {
  267. this.handleShowLogin();
  268. this.showSetPassword = true;
  269. },
  270. handleResetPassword(isFirst) {
  271. this.handleShowLogin();
  272. this.passwordTitle = isFirst ? "Please set your password." : "Password Expired.";
  273. this.passwordSubTitle = isFirst ? "" : "Please create new password";
  274. this.expireEmail = this.loginForm.email;
  275. this.showSetPassword = true;
  276. },
  277. onResetPasswordConfirm(info) {
  278. //reset 忘记密码
  279. //create 首次登录、密码过期
  280. this.loginForm.password = "";
  281. this.newPasswordInfo = info;
  282. this.showOTP = true;
  283. },
  284. onPreLogin() {
  285. this.$refs.loginForm.validate(valid => {
  286. if (valid) {
  287. this.loading = true
  288. api.preLogin(this.loginForm).then(res => {
  289. if (res.code) {
  290. switch(res.code) {
  291. case 2016: //未启用二次登录验证
  292. this.handleLogin("");
  293. break;
  294. case 2017: //首次登录流程
  295. this.handleResetPassword(true);
  296. break;
  297. case 2018: //密码过期流程
  298. this.handleResetPassword(false);
  299. break;
  300. case 2019: //OTP 登录流程
  301. this.showOTP = true;
  302. break;
  303. default:
  304. this.handleLogin("");
  305. break;
  306. }
  307. }
  308. }).catch(err => {
  309. this.$message({
  310. type: 'error',
  311. message: err
  312. })
  313. }).finally(() => {
  314. this.loading = false;
  315. })
  316. } else {
  317. console.log('error submit!!')
  318. }
  319. })
  320. },
  321. handleLogin(otp) {
  322. if (otp) {
  323. this.loginForm.otp = otp;
  324. }
  325. this.$store.dispatch('user/login', this.loginForm).then((response) => {
  326. let path = this.redirect || '/'
  327. if (response.data.roleName === "MCST") {
  328. path = '/site-management'
  329. } else if (response.data.resources.length > 0) {
  330. this.$store.commit('permission/SET_ROUTES', response.data.resources);
  331. let validPath = false;
  332. for (let _route of response.data.resources) {
  333. if (path.indexOf(_route[permission.RESOURCE_KEY]) >= 0) {
  334. validPath = true;
  335. break
  336. }
  337. }
  338. if (!validPath) {
  339. path = response.data.resources[0][permission.RESOURCE_KEY];
  340. }
  341. }
  342. this.$router.push({
  343. path: path,
  344. query: this.otherQuery,
  345. }).catch((failure) => {
  346. if (!isNavigationFailure(failure, NavigationFailureType.redirected)) {
  347. throw failure
  348. }
  349. })
  350. this.loading = false
  351. }).catch((error) => {
  352. Message.error(error)
  353. this.loading = false
  354. this.handleShowLogin();
  355. });
  356. },
  357. getOtherQuery(query) {
  358. return Object.keys(query).reduce((acc, cur) => {
  359. if (cur !== 'redirect') {
  360. acc[cur] = query[cur]
  361. }
  362. return acc
  363. }, {})
  364. }
  365. }
  366. }
  367. </script>
  368. <style lang="scss" scoped>
  369. /* reset element-ui css */
  370. .login-container {
  371. width: 100%;
  372. display: flex;
  373. overflow: hidden;
  374. min-height: 100%;
  375. user-select: none;
  376. align-items: center;
  377. justify-content: center;
  378. background: linear-gradient(0deg, #333333 0%, #A1A1A1 100%);
  379. .login-form {
  380. width: 100%;
  381. z-index: 2;
  382. margin: 0 auto;
  383. max-width: 450px;
  384. overflow: hidden;
  385. background: #fff;
  386. position: relative;
  387. border-radius: 3px;
  388. padding: 30px 40px 50px;
  389. transition: all .3s;
  390. box-shadow: 1px 1px 5px 3px rgba(0,0,0,.2);
  391. }
  392. .title-container {
  393. text-align: center;
  394. position: relative;
  395. padding: 20px 0 0;
  396. .title {
  397. font-size: 16px;
  398. color: #333;
  399. margin: 20px 0;
  400. text-align: center;
  401. font-weight: bold;
  402. }
  403. .sub-title {
  404. font-size: 16px;
  405. color: #333;
  406. margin-top: -10px;
  407. padding-bottom: 5px;
  408. text-align: center;
  409. }
  410. .csms-logo {
  411. max-width: 200px;
  412. height: 70px;
  413. object-fit: contain;
  414. }
  415. }
  416. .form-login-input {
  417. width: 100%;
  418. display: flex;
  419. padding: 0 10px;
  420. margin-top: 20px;
  421. position: relative;
  422. align-items: center;
  423. border-bottom: 1px solid #999;
  424. label {
  425. top: 0;
  426. left: 0;
  427. color: #aaa;
  428. font-size: 14px;
  429. position:absolute;
  430. padding-top: 7px;
  431. padding-left: 45px;
  432. font-weight: normal;
  433. pointer-events: none;
  434. transition: all 0.3s;
  435. }
  436. label.focus {
  437. top: -30px;
  438. color: #333;
  439. font-size: 15px;
  440. font-weight: bold;
  441. padding-left: 5px;
  442. }
  443. .svg-container {
  444. width: 30px;
  445. color: #333;
  446. font-size: 16px;
  447. padding: 6px 6px 6px 5px;
  448. }
  449. .el-input {
  450. flex: 1;
  451. height: 42px;
  452. &::v-deep input {
  453. color: #333;
  454. height: 42px;
  455. border: none;
  456. font-size: 14px;
  457. padding: 0 5px;
  458. border-radius: 0px;
  459. -webkit-appearance: none;
  460. background: transparent;
  461. }
  462. }
  463. }
  464. .show-pwd {
  465. color: #666;
  466. cursor: pointer;
  467. font-size: 16px;
  468. user-select: none;
  469. }
  470. .login-button {
  471. width:100%;
  472. color: #fff;
  473. padding: 15px;
  474. margin-top: 40px;
  475. margin-bottom: 20px;
  476. font-weight: bold;
  477. }
  478. .forgot-button {
  479. width:100%;
  480. padding: 15px;
  481. margin: 0 0 20px;
  482. font-weight: bold;
  483. }
  484. .copyinfo {
  485. left: 0;
  486. right: 0;
  487. bottom: 10px;
  488. color: #e0e0e0;
  489. font-size: 12px;
  490. user-select: none;
  491. text-align: center;
  492. position: absolute;
  493. transform: scale(.8);
  494. }
  495. .bg-logo {
  496. top: 0;
  497. left: 0;
  498. right: 0;
  499. bottom: 0;
  500. z-index: 1;
  501. opacity: .3;
  502. position: fixed;
  503. filter: blur(30px);
  504. pointer-events: none;
  505. background-size: contain;
  506. background-position: center;
  507. background-repeat: no-repeat;
  508. background-image: url(../../assets/logo.png);
  509. }
  510. .bg-logo.anim {
  511. animation: moveBg 10s infinite;
  512. background-origin: content-box;
  513. }
  514. }
  515. @keyframes moveBg {
  516. 0% {
  517. padding: 0;
  518. filter: blur(30px);
  519. }
  520. 50% {
  521. padding: 0 200px;
  522. filter: blur(20px);
  523. }
  524. 100% {
  525. padding: 0;
  526. filter: blur(30px);
  527. }
  528. }
  529. </style>
  530. <style>
  531. .particles-js-canvas-el {
  532. z-index: 1;
  533. position: fixed;
  534. }
  535. </style>