login.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <template>
  2. <div class="login" ref="loginRef">
  3. <!-- <a-carousel autoplay class="carousel" dotsClass="dots" lazyLoad :autoplaySpeed="60000" adaptiveHeight>
  4. <div>
  5. <img :src="loginBg1" class="img" alt="">
  6. </div>
  7. <div>
  8. <img :src="loginBg2" class="img" alt="">
  9. </div>
  10. </a-carousel> -->
  11. <div class="login-box">
  12. <div class="login-left">
  13. <div class="login-left-box">
  14. <!-- <img class="logo" :src="logo" alt=""> -->
  15. <!-- <div class="slogan">合作共赢 · 品质第一 </div> -->
  16. <!-- <div class="type">
  17. 研发设计/生产制造/工程管理/投资运营
  18. </div> -->
  19. </div>
  20. </div>
  21. <div class="login-right">
  22. <div class="login-right-box">
  23. <div class="login-right-title">
  24. <span class="welcome">欢迎登录</span>Admin内控平台
  25. </div>
  26. <div class="login-right-form">
  27. <a-tabs v-model:activeKey="activeKey" class="tabs">
  28. <a-tab-pane key="0" tab="账号登录" force-render>
  29. <div class="login-form" v-if="activeKey === '0'">
  30. <a-form ref="formRef" :model="loginForm" :rules="rules">
  31. <a-form-item name="loginName" class="form-item">
  32. <div class="label">
  33. 账户
  34. </div>
  35. <a-input class="input" v-model:value.trim="loginForm.loginName" placeholder="请输入用户名">
  36. <template #prefix>
  37. <UserOutlined />
  38. </template>
  39. </a-input>
  40. </a-form-item>
  41. <a-form-item name="emailCode" class="form-item" v-if="emailCodeShowFlag" label="邮箱验证码">
  42. <a-input-group compact>
  43. <a-input style="width: calc(100% - 110px)" v-model:value="loginForm.emailCode" autocomplete="on"
  44. placeholder="请输入邮箱验证码" />
  45. <a-button @click="sendSmsCode" type="primary" :disabled="emailCodeButtonDisabled">
  46. {{ emailCodeTips }}
  47. </a-button>
  48. </a-input-group>
  49. </a-form-item>
  50. <a-form-item name="password" class="form-item">
  51. <div class="label">
  52. 密码
  53. </div>
  54. <a-input-password class="input" v-model:value="loginForm.password" autocomplete="on"
  55. :type="showPassword ? 'text' : 'password'" placeholder="请输入密码">
  56. <template #prefix>
  57. <SafetyOutlined />
  58. </template>
  59. </a-input-password>
  60. </a-form-item>
  61. <a-form-item name="captchaCode" class="form-item">
  62. <div class="label">
  63. 验证码
  64. </div>
  65. <div class="captchaCode-img">
  66. <a-input v-model:value.trim="loginForm.captchaCode" class="input" placeholder="请输入验证码">
  67. <template #prefix>
  68. <SafetyOutlined />
  69. </template>
  70. <template #suffix>
  71. <img :src="captchaBase64Image" class="img" @click="getCaptcha" />
  72. </template>
  73. </a-input>
  74. </div>
  75. </a-form-item>
  76. <!-- <a-form-item>
  77. <a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
  78. </a-form-item> -->
  79. <a-form-item class="form-item">
  80. <div class="form-btn">
  81. <a-button class="btn" @click="onLogin" type="primary" size="large">登录</a-button>
  82. </div>
  83. </a-form-item>
  84. </a-form>
  85. </div>
  86. </a-tab-pane>
  87. <a-tab-pane key="1" tab="扫码登录" force-render>
  88. <div class="qrcode-login" v-if="activeKey === '1'" :class="ratio > 0.985 ? 'margin-top' : 'flex-center'">
  89. <img class="img" :src="tip" alt="" v-if="ratio > 0.985">
  90. <div class="code" :class="{ 'absolute-right': ratio > 0.985 }">
  91. <ddLogin />
  92. <!-- <a-qrcode value="https://www.antdv.com/" :size="250" /> -->
  93. </div>
  94. </div>
  95. </a-tab-pane>
  96. </a-tabs>
  97. <!-- <div class="login-right-bottom">如有疑问请致电:营销热线0532-88988868</div> -->
  98. </div>
  99. </div>
  100. </div>
  101. </div>
  102. </div>
  103. </template>
  104. <script setup>
  105. import { message } from 'ant-design-vue';
  106. import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
  107. import { useRouter } from 'vue-router';
  108. import { loginApi } from '/@/api/system/login-api';
  109. import { SmartLoading } from '/@/components/framework/smart-loading';
  110. import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
  111. import { useUserStore } from '/@/store/modules/system/user';
  112. import loginBg2 from '/@/assets/images/login/login_bg2.png'
  113. import loginBg1 from '/@/assets/images/login/login_bg1.png'
  114. import tip from '/@/assets/images/login/tooltip.png'
  115. import logo from '/@/assets/images/login/logo1.png'
  116. import { buildRoutes } from '/@/router/index';
  117. import { smartSentry } from '/@/lib/smart-sentry';
  118. import { encryptData, decryptData } from '/@/lib/encrypt';
  119. import { localSave, localRemove, localRead } from '/@/utils/local-util.js';
  120. import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
  121. import { Form } from 'ant-design-vue';
  122. import useBsDict from "/@/utils/dict.js";
  123. import ddLogin from '/@/components/support/dd-login/index.vue'
  124. const activeKey = ref('0')
  125. //--------------------- 登录表单 ---------------------------------
  126. const loginForm = reactive({
  127. loginName: 'admin',
  128. password: '',
  129. captchaCode: '',
  130. captchaUuid: '',
  131. loginDevice: LOGIN_DEVICE_ENUM.PC.value,
  132. });
  133. const rules = {
  134. loginName: [{ required: true, message: '用户名不能为空' }],
  135. password: [{ required: true, message: '密码不能为空' }],
  136. captchaCode: [{ required: true, message: '验证码不能为空' }],
  137. };
  138. const showPassword = ref(false);
  139. const router = useRouter();
  140. const formRef = ref();
  141. // const rememberPwd = ref(false);
  142. // function readLocalRememberMe() {
  143. // const rememberMe = JSON.parse(localRead('rememberMe') || null);
  144. // if (rememberMe) {
  145. // rememberMe.password = rememberMe.password ? decryptData(rememberMe.password) : '';
  146. // loginForm.loginName = rememberMe.loginName;
  147. // loginForm.password = rememberMe.password;
  148. // rememberPwd.value = true;
  149. // // console.log('readLocalRememberMe', rememberMe);
  150. // }
  151. // }
  152. onMounted(() => {
  153. // readLocalRememberMe();
  154. document.onkeyup = (e) => {
  155. if (e.keyCode === 13) {
  156. onLogin();
  157. }
  158. };
  159. });
  160. onUnmounted(() => {
  161. document.onkeyup = null;
  162. });
  163. //登录
  164. async function onLogin() {
  165. formRef.value.validate().then(async () => {
  166. try {
  167. SmartLoading.show();
  168. // 密码加密
  169. let encryptPasswordForm = Object.assign({}, loginForm, {
  170. password: encryptData(loginForm.password),
  171. });
  172. const res = await loginApi.login(encryptPasswordForm);
  173. stopRefreshCaptchaInterval();
  174. // if (rememberPwd.value === true) {
  175. // localSave('rememberMe', JSON.stringify({ loginName: encryptPasswordForm.loginName, password: encryptPasswordForm.password }));
  176. // } else {
  177. // localRemove('rememberMe');
  178. // }
  179. localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
  180. message.success('登录成功');
  181. // 刷新本地缓存
  182. await useBsDict.refresh();
  183. //更新用户信息到pinia
  184. useUserStore().setUserLoginInfo(res.data);
  185. localSave('loginFrom', '0');
  186. //构建系统的路由
  187. buildRoutes();
  188. router.push('/home');
  189. } catch (e) {
  190. if (e.data && e.data.code !== 0) {
  191. loginForm.captchaCode = '';
  192. getCaptcha();
  193. }
  194. smartSentry.captureError(e);
  195. } finally {
  196. SmartLoading.hide();
  197. }
  198. });
  199. }
  200. //--------------------- 验证码 ---------------------------------
  201. const captchaBase64Image = ref('');
  202. async function getCaptcha() {
  203. try {
  204. let captchaResult = await loginApi.getCaptcha();
  205. captchaBase64Image.value = captchaResult.data.captchaBase64Image;
  206. loginForm.captchaUuid = captchaResult.data.captchaUuid;
  207. beginRefreshCaptchaInterval(captchaResult.data.expireSeconds);
  208. } catch (e) {
  209. console.log(e);
  210. }
  211. }
  212. let refreshCaptchaInterval = null;
  213. function beginRefreshCaptchaInterval(expireSeconds) {
  214. if (refreshCaptchaInterval === null) {
  215. refreshCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
  216. }
  217. }
  218. function stopRefreshCaptchaInterval() {
  219. if (refreshCaptchaInterval != null) {
  220. clearInterval(refreshCaptchaInterval);
  221. refreshCaptchaInterval = null;
  222. }
  223. }
  224. onMounted(() => {
  225. getCaptcha();
  226. getTwoFactorLoginFlag();
  227. });
  228. //--------------------- 邮箱验证码 ---------------------------------
  229. const emailCodeShowFlag = ref(false);
  230. let emailCodeTips = ref('获取邮箱验证码');
  231. let emailCodeButtonDisabled = ref(false);
  232. // 定时器
  233. let countDownTimer = null;
  234. // 开始倒计时
  235. function runCountDown() {
  236. emailCodeButtonDisabled.value = true;
  237. let countDown = 60;
  238. emailCodeTips.value = `${countDown}秒后重新获取`;
  239. countDownTimer = setInterval(() => {
  240. if (countDown > 1) {
  241. countDown--;
  242. emailCodeTips.value = `${countDown}秒后重新获取`;
  243. } else {
  244. clearInterval(countDownTimer);
  245. emailCodeButtonDisabled.value = false;
  246. emailCodeTips.value = '获取验证码';
  247. }
  248. }, 1000);
  249. }
  250. // 获取双因子登录标识
  251. async function getTwoFactorLoginFlag() {
  252. try {
  253. let result = await loginApi.getTwoFactorLoginFlag();
  254. emailCodeShowFlag.value = result.data;
  255. } catch (e) {
  256. smartSentry.captureError(e);
  257. }
  258. }
  259. // 发送邮箱验证码
  260. async function sendSmsCode() {
  261. try {
  262. SmartLoading.show();
  263. let result = await loginApi.sendLoginEmailCode(loginForm.loginName);
  264. message.success('验证码发送成功!请登录邮箱查看验证码~');
  265. runCountDown();
  266. } catch (e) {
  267. smartSentry.captureError(e);
  268. } finally {
  269. SmartLoading.hide();
  270. }
  271. }
  272. // ---------------- 适配屏幕缩放 ----------------
  273. const loginRef = ref(null);
  274. const ratio = ref(1);
  275. function reduceRatio() {
  276. handleResize();
  277. window.addEventListener('resize', handleResize);
  278. }
  279. function handleResize() {
  280. ratio.value = window.innerWidth / 1920;
  281. loginRef.value.style.setProperty('--fitWidthRatio', ratio.value);
  282. }
  283. watch(() => ratio.value, (value) => {
  284. console.log(value);
  285. })
  286. onMounted(() => {
  287. reduceRatio();
  288. });
  289. onUnmounted(() => {
  290. window.removeEventListener('resize', handleResize);
  291. });
  292. </script>
  293. <style lang="scss" scoped>
  294. .login {
  295. height: 100vh;
  296. width: 100vw;
  297. // background-color: #e39f77;
  298. .carousel {
  299. height: 100%;
  300. position: absolute;
  301. width: 100%;
  302. left: 0;
  303. top: 0;
  304. z-index: 0;
  305. .img {
  306. height: 100vh;
  307. width: 100vw;
  308. }
  309. :deep(.dots) {
  310. width: 65%;
  311. align-items: center;
  312. }
  313. :deep(.dots li) {
  314. height: calc(var(--fitWidthRatio) * 20px);
  315. width: calc(var(--fitWidthRatio) * 20px);
  316. }
  317. :deep(.dots :not(.slick-active) button) {
  318. height: 100%;
  319. border-radius: calc(var(--fitWidthRatio) * 20px);
  320. }
  321. :deep(.dots .slick-active) {
  322. width: calc(var(--fitWidthRatio) * 52px);
  323. height: calc(var(--fitWidthRatio) * 16px);
  324. }
  325. :deep(.slick-active button) {
  326. background-color: #2b69f8;
  327. border-radius: calc(var(--fitWidthRatio) * 20px);
  328. height: 100%;
  329. }
  330. }
  331. .login-box {
  332. display: flex;
  333. flex-direction: row;
  334. // align-items: center;
  335. gap: calc(var(--fitWidthRatio) * 60px);
  336. height: 100%;
  337. width: 100%;
  338. .login-left {
  339. display: flex;
  340. flex-direction: column;
  341. height: 35%;
  342. width: 55%;
  343. position: absolute;
  344. left: 0;
  345. top: 20%;
  346. background: linear-gradient(to right, #FFFFFF, #99999900);
  347. z-index: 1;
  348. padding: calc(var(--fitWidthRatio) * 30px);
  349. .login-left-box {
  350. // width: 46%;
  351. height: 100%;
  352. display: flex;
  353. flex-direction: column;
  354. justify-content: space-evenly;
  355. .logo {
  356. height: calc(var(--fitWidthRatio) * 84px);
  357. width: calc(var(--fitWidthRatio) * 240px);
  358. }
  359. .slogan {
  360. font-size: calc(var(--fitWidthRatio) * 32px);
  361. font-weight: 600;
  362. font-family: 'PingFang SC';
  363. color: #1C449B;
  364. }
  365. .type {
  366. width: 31.5%;
  367. background-color: #1c449b;
  368. padding: calc(var(--fitWidthRatio) * 8px) calc(var(--fitWidthRatio) * 16px);
  369. border-radius: calc(var(--fitWidthRatio) * 16px);
  370. color: #FFFFFF;
  371. font-size: calc(var(--fitWidthRatio) * 16px);
  372. font-weight: 600;
  373. font-family: 'PingFang SC';
  374. }
  375. }
  376. }
  377. .login-right {
  378. display: flex;
  379. flex-direction: column;
  380. align-items: center;
  381. height: 100%;
  382. width: 40%;
  383. position: absolute;
  384. right: 0;
  385. background-color: rgba(255, 255, 255, 0.6);
  386. border-radius: calc(var(--fitWidthRatio) * 20px) 0 0 calc(var(--fitWidthRatio) * 20px);
  387. backdrop-filter: blur(10px);
  388. .login-right-box {
  389. display: flex;
  390. flex-direction: column;
  391. gap: calc(var(--fitWidthRatio) * 20px);
  392. position: absolute;
  393. top: 12%;
  394. .login-right-title {
  395. font-size: calc(var(--fitWidthRatio) * 24px);
  396. font-weight: 600;
  397. width: 100%;
  398. color: rgba(0, 0, 0, 1);
  399. font-family: 'PingFang SC';
  400. .welcome {
  401. font-size: calc(var(--fitWidthRatio) * 32px);
  402. }
  403. }
  404. .login-right-form {
  405. width: 30vw;
  406. height: 65vh;
  407. min-height: 386px;
  408. background-color: #ffffff;
  409. box-shadow: 0px calc(var(--fitWidthRatio) * 6px) calc(var(--fitWidthRatio) * 4px) 0px rgba(149, 149, 149, 0.25);
  410. border-radius: calc(var(--fitWidthRatio) * 8px);
  411. display: flex;
  412. flex-direction: column;
  413. align-items: center;
  414. .tabs {
  415. height: 90%;
  416. width: 100%;
  417. padding: calc(var(--fitWidthRatio) * 32px) calc(var(--fitWidthRatio) * 32px) 0 calc(var(--fitWidthRatio) * 32px);
  418. :deep(.ant-tabs-nav) {
  419. margin: 0;
  420. padding: 0 calc(var(--fitWidthRatio) * 24px);
  421. }
  422. :deep(.ant-tabs-tab) {
  423. padding: calc(var(--fitWidthRatio) * 4px) 0;
  424. }
  425. :deep(.ant-tabs-tab-active .ant-tabs-tab-btn) {
  426. color: #000;
  427. }
  428. :deep(.ant-tabs-nav-list :not(.ant-tabs-tab-active) .ant-tabs-tab-btn) {
  429. color: #9B9B9B;
  430. }
  431. :deep(.ant-tabs-nav::before) {
  432. border: none;
  433. }
  434. :deep(.ant-tabs-tab-btn) {
  435. font-size: calc(var(--fitWidthRatio) * 24px);
  436. }
  437. :deep(.ant-tabs-content) {
  438. height: 100%;
  439. }
  440. .margin-top {
  441. margin-top: calc(var(--fitWidthRatio) * 50px);
  442. }
  443. .flex-center {
  444. margin: 0;
  445. display: flex;
  446. justify-content: center;
  447. align-items: center;
  448. height: 100%;
  449. width: 100%;
  450. }
  451. .qrcode-login {
  452. .img {
  453. width: calc(var(--fitWidthRatio) * 355px);
  454. height: calc(var(--fitWidthRatio) * 370px);
  455. position: absolute;
  456. z-index: 1;
  457. right: 0;
  458. }
  459. .code {
  460. left: 0;
  461. top: calc(var(--fitWidthRatio) * 16px);
  462. }
  463. .absolute-right {
  464. position: absolute;
  465. }
  466. }
  467. .login-form {
  468. width: 100%;
  469. height: 100%;
  470. display: flex;
  471. justify-content: center;
  472. align-items: center;
  473. padding: 0 calc(var(--fitWidthRatio) * 32px) 0 calc(var(--fitWidthRatio) * 16px);
  474. .label {
  475. font-size: calc(var(--fitWidthRatio) * 16px);
  476. font-family: 'PingFang SC';
  477. font-weight: 500;
  478. margin-top: calc(var(--fitWidthRatio) * 16px);
  479. margin-bottom: calc(var(--fitWidthRatio) * 8px);
  480. }
  481. .input {
  482. height: calc(var(--fitWidthRatio) * 44px);
  483. width: 100%;
  484. font-size: calc(var(--fitWidthRatio) * 16px);
  485. border: 1px solid rgba(238, 238, 238, 1);
  486. box-shadow: 0px calc(var(--fitWidthRatio) * 4px) calc(var(--fitWidthRatio) * 4px) 0px rgba(214, 214, 214, 0.25);
  487. }
  488. .form-item {
  489. margin: 0;
  490. .form-btn {
  491. width: 100%;
  492. height: calc(var(--fitWidthRatio) * 50px);
  493. margin-top: calc(var(--fitWidthRatio) * 24px);
  494. display: flex;
  495. justify-content: center;
  496. .btn {
  497. height: 100%;
  498. width:calc(var(--fitWidthRatio) * 410px) ;
  499. font-size: calc(var(--fitWidthRatio) * 16px);
  500. }
  501. }
  502. .img {
  503. width: calc(var(--fitWidthRatio) * 124px);
  504. height: calc(var(--fitWidthRatio) * 34px);
  505. border-radius: calc(var(--fitWidthRatio) * 8px);
  506. }
  507. }
  508. :deep(.ant-form) {
  509. width: 100%;
  510. height: 100%;
  511. display: flex;
  512. flex-direction: column;
  513. justify-content: center;
  514. }
  515. :deep(.ant-form-item) {
  516. margin-left: calc(var(--fitWidthRatio) * 20px);
  517. }
  518. }
  519. }
  520. .login-right-bottom {
  521. width: 100%;
  522. height: 10%;
  523. text-align: center;
  524. font-size: calc(var(--fitWidthRatio) * 16px);
  525. padding: calc(var(--fitWidthRatio) * 16px);
  526. border-top: 1px dotted #DEDEDE;
  527. color: #EF2F22;
  528. }
  529. }
  530. }
  531. }
  532. }
  533. }
  534. </style>