index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. <template>
  2. <div class="login-wrapper">
  3. <!-- 登录卡片 - 左右一体 -->
  4. <div class="login-card">
  5. <!-- 左侧蓝色插画区域 -->
  6. <div class="login-card-left">
  7. <div class="login-illustration"></div>
  8. </div>
  9. <!-- 右侧表单区域 -->
  10. <div class="login-card-right">
  11. <el-form
  12. ref="form"
  13. size="large"
  14. :model="form"
  15. :rules="rules"
  16. class="login-form"
  17. @keyup.enter.native="submit"
  18. >
  19. <!-- 二维码按钮 -->
  20. <el-popover width="150" trigger="hover">
  21. <div>
  22. <img style="width: 100%" :src="qrcodeUrl" alt="" srcset="" />
  23. <div style="text-align: center">浏览器扫一扫下载</div>
  24. </div>
  25. <div slot="reference" class="switch-btn"></div>
  26. </el-popover>
  27. <!-- 标题 -->
  28. <div class="form-header">
  29. <h1 class="form-title">
  30. <span class="title-dark">{{
  31. projectName || '工业互联网平台'
  32. }}</span>
  33. </h1>
  34. </div>
  35. <!-- 账号输入 -->
  36. <el-form-item prop="loginName">
  37. <div class="input-wrapper">
  38. <i class="el-icon-user input-icon"></i>
  39. <el-input
  40. clearable
  41. v-model="form.loginName"
  42. placeholder="账号"
  43. class="login-input"
  44. />
  45. </div>
  46. </el-form-item>
  47. <!-- 密码输入 -->
  48. <el-form-item prop="loginPwd">
  49. <div class="input-wrapper">
  50. <i class="el-icon-lock input-icon"></i>
  51. <el-input
  52. show-password
  53. v-model="form.loginPwd"
  54. placeholder="密码"
  55. class="login-input"
  56. />
  57. </div>
  58. </el-form-item>
  59. <!-- 验证码 -->
  60. <el-form-item prop="captcha">
  61. <div class="input-wrapper captcha-wrapper">
  62. <i class="el-icon-first-aid-kit input-icon"></i>
  63. <el-input
  64. clearable
  65. v-model="form.captcha"
  66. placeholder="验证码"
  67. class="login-input"
  68. />
  69. <img
  70. alt=""
  71. :src="captcha"
  72. class="login-captcha"
  73. @click="changeCaptcha"
  74. />
  75. </div>
  76. </el-form-item>
  77. <!-- 记住密码 -->
  78. <!-- <div class="remember-wrapper">
  79. <el-checkbox v-model="form.remember" class="remember-checkbox">
  80. 记住密码
  81. </el-checkbox>
  82. </div> -->
  83. <!-- 登录按钮 -->
  84. <el-form-item>
  85. <el-button
  86. size="large"
  87. type="primary"
  88. class="login-btn"
  89. :loading="loading"
  90. @click="submit"
  91. >
  92. {{ loading ? $t('login.loading') : '登录' }}
  93. </el-button>
  94. </el-form-item>
  95. </el-form>
  96. </div>
  97. </div>
  98. <!-- 多语言切换 -->
  99. <div class="lang-switch">
  100. <i18n-icon
  101. :icon-style="{ fontSize: '20px', color: '#fff', cursor: 'pointer' }"
  102. />
  103. </div>
  104. </div>
  105. </template>
  106. <script>
  107. import I18nIcon from '@/layout/components/i18n-icon.vue';
  108. import { getToken } from '@/utils/token-util';
  109. import {
  110. login,
  111. getCaptcha,
  112. getLatestVersion,
  113. logoByCode,
  114. getParentIdListByDeptId
  115. } from '@/api/login';
  116. import xyy from '@/assets/xyy.jpg';
  117. import { getPathAddress } from '@/api/system/file';
  118. import { setCurrentUser } from '@/utils/token-util';
  119. import { factoryareaId } from '@/api/system/user';
  120. import QRCode from 'qrcode';
  121. export default {
  122. // eslint-disable-next-line vue/multi-word-component-names
  123. name: 'Login',
  124. components: { I18nIcon },
  125. data() {
  126. return {
  127. // 登录框方向, 0居中, 1居右, 2居左
  128. xyy,
  129. direction: 0,
  130. projectName: '',
  131. // 加载状态
  132. loading: false,
  133. // 表单数据
  134. form: {
  135. loginName: localStorage.getItem('accountInfo')
  136. ? JSON.parse(localStorage.getItem('accountInfo')).loginName
  137. : '',
  138. loginPwd: localStorage.getItem('accountInfo')
  139. ? JSON.parse(localStorage.getItem('accountInfo')).loginPwd
  140. : '',
  141. remember: localStorage.getItem('accountInfo')
  142. ? JSON.parse(localStorage.getItem('accountInfo')).remember
  143. : false,
  144. captcha: '',
  145. uuid: ''
  146. },
  147. // 验证码base64数据
  148. captcha: '',
  149. // 验证码内容, 实际项目去掉
  150. text: '',
  151. qrcodeUrl: ''
  152. };
  153. },
  154. computed: {
  155. // 表单验证规则
  156. rules() {
  157. return {
  158. loginName: [
  159. {
  160. required: true,
  161. message: this.$t('请输入登录账号'),
  162. type: 'string',
  163. trigger: 'blur'
  164. }
  165. ],
  166. loginPwd: [
  167. {
  168. required: true,
  169. message: this.$t('请输入登录密码'),
  170. type: 'string',
  171. trigger: 'blur'
  172. }
  173. ],
  174. captcha: [
  175. {
  176. required: true,
  177. message: '验证码不能为空',
  178. type: 'string',
  179. trigger: 'blur'
  180. }
  181. ]
  182. };
  183. }
  184. },
  185. created() {
  186. this.changeCaptcha();
  187. this.generateQRCode();
  188. this.getLogo();
  189. if (getToken()) {
  190. this.goHome();
  191. } else {
  192. // 重置菜单权限
  193. this.$store.commit('user/setMenus', null);
  194. }
  195. },
  196. methods: {
  197. getLogo() {
  198. logoByCode('logoCode').then((res) => {
  199. this.projectName = res.logoName;
  200. });
  201. },
  202. generateQRCode() {
  203. getLatestVersion().then((res) => {
  204. console.log(res, 'dasdasd');
  205. QRCode.toDataURL(res.fileUrl, {
  206. errorCorrectionLevel: 'H',
  207. margin: 2,
  208. scale: 8
  209. }).then((url) => {
  210. console.log('qrcodeUrl', url);
  211. this.qrcodeUrl = url;
  212. });
  213. });
  214. },
  215. /* 提交 */
  216. submit() {
  217. this.$refs.form.validate((valid) => {
  218. if (!valid) {
  219. return false;
  220. }
  221. this.loading = true;
  222. login(this.form)
  223. .then(async (res) => {
  224. localStorage.setItem('userId', res.data.userId);
  225. localStorage.setItem('accountId', res.data.accountId);
  226. // 用户信息:默认部门/角色取主所属部门(isMainGroup=1,后端以 t_main_user.group_id 为基准),没有则取第一个
  227. const groupList = res.data?.loginChangeGroupVOList || [];
  228. const mainGroup = groupList.find((item) => item.isMainGroup == 1);
  229. const defaultGroup = mainGroup || groupList[0];
  230. if (defaultGroup) {
  231. setCurrentUser(
  232. {
  233. currentGroupId: defaultGroup.groupId,
  234. currentRoleId:
  235. defaultGroup.loginChangeRoleVOList?.[0]?.roleId
  236. },
  237. this.form.remember
  238. );
  239. }
  240. res.data.groupIdList = await getParentIdListByDeptId(
  241. res.data.groupId
  242. );
  243. const filePath = await getPathAddress();
  244. res.data.avatarAddress =
  245. res.data.avatar && res.data.avatar.length
  246. ? filePath + res.data.avatar[0].storePath
  247. : xyy;
  248. if (res.data.factoryId) {
  249. let factoryareaData = await factoryareaId(res.data.factoryId);
  250. res.data['factoryName'] = factoryareaData.name;
  251. }
  252. this.$store.commit('user/setUserInfo', res.data);
  253. this.loading = false;
  254. this.$message.success(res.message);
  255. if (this.form.remember) {
  256. localStorage.setItem('accountInfo', JSON.stringify(this.form));
  257. } else {
  258. localStorage.removeItem('accountInfo');
  259. }
  260. // await this.$store.dispatch('user/getCurrentUserAuthorityDept');
  261. // await this.$store.dispatch('paramsSetData/setParamsDataInfo');
  262. this.goHome();
  263. })
  264. .catch(() => {
  265. this.changeCaptcha();
  266. this.loading = false;
  267. // this.$message.error(e.message);
  268. });
  269. });
  270. },
  271. /* 跳转到首页 */
  272. goHome() {
  273. localStorage.setItem('singleUserInfo', '0');
  274. this.$router.push('/').catch(() => {});
  275. },
  276. /* 更换图形验证码 */
  277. changeCaptcha() {
  278. this.form.uuid = this.generateUUID();
  279. // 这里演示的验证码是后端返回base64格式的形式, 如果后端地址直接是图片请参考忘记密码页面
  280. getCaptcha({ uuid: this.form.uuid })
  281. .then((data) => {
  282. this.captcha = URL.createObjectURL(data.data);
  283. this.$refs?.form?.clearValidate();
  284. })
  285. .catch(() => {
  286. // this.$message.error(e.message);
  287. });
  288. },
  289. generateUUID() {
  290. var d = new Date().getTime(); //Timestamp
  291. var d2 =
  292. (performance && performance.now && performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported
  293. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
  294. /[xy]/g,
  295. function (c) {
  296. var r = Math.random() * 16; //random number between 0 and 16
  297. if (d > 0) {
  298. //Use timestamp until depleted
  299. r = (d + r) % 16 | 0;
  300. d = Math.floor(d / 16);
  301. } else {
  302. //Use microseconds since page-load if supported
  303. r = (d2 + r) % 16 | 0;
  304. d2 = Math.floor(d2 / 16);
  305. }
  306. return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
  307. }
  308. );
  309. }
  310. }
  311. };
  312. </script>
  313. <style lang="scss" scoped>
  314. .login-wrapper {
  315. width: 100vw;
  316. height: 100vh;
  317. display: flex;
  318. align-items: center;
  319. justify-content: center;
  320. background-image: url('@/assets/bg-login.png');
  321. background-size: cover;
  322. background-position: center;
  323. background-repeat: no-repeat;
  324. position: relative;
  325. overflow: hidden;
  326. }
  327. /* 登录卡片 - 左右一体 */
  328. .login-card {
  329. display: flex;
  330. width: 1000px;
  331. height: 500px;
  332. background: #ffffff;
  333. border-radius: 4px;
  334. box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25);
  335. overflow: hidden;
  336. }
  337. /* 左侧插画区域 */
  338. .login-card-left {
  339. width: 50%;
  340. background: linear-gradient(135deg, #1e5bb5 0%, #2c7ad6 50%, #3d8deb 100%);
  341. display: flex;
  342. align-items: center;
  343. justify-content: center;
  344. .login-illustration {
  345. width: 100%;
  346. height: 100%;
  347. background-image: url('@/assets/bg-login-left.png');
  348. background-size: contain;
  349. background-repeat: no-repeat;
  350. background-position: center;
  351. }
  352. }
  353. /* 右侧表单区域 */
  354. .login-card-right {
  355. width: 50%;
  356. padding: 45px 50px;
  357. display: flex;
  358. flex-direction: column;
  359. justify-content: center;
  360. background: #ffffff;
  361. box-sizing: border-box;
  362. }
  363. /* 登录表单 */
  364. .login-form {
  365. width: 100%;
  366. position: relative;
  367. /* 二维码按钮 */
  368. .switch-btn {
  369. background: url(@/assets/qr-code-sprites.png) 0 0 / 36px auto no-repeat;
  370. cursor: pointer;
  371. height: 36px;
  372. width: 36px;
  373. position: absolute;
  374. right: -10px;
  375. top: -65px;
  376. z-index: 10;
  377. &:hover {
  378. background-position: 0 -36px;
  379. }
  380. }
  381. }
  382. /* 表单标题 */
  383. .form-header {
  384. margin-bottom: 35px;
  385. .form-title {
  386. font-size: 28px;
  387. font-weight: 600;
  388. margin: 0;
  389. letter-spacing: 2px;
  390. .title-dark {
  391. color: #1a3a5c;
  392. }
  393. .title-light {
  394. color: #409eff;
  395. }
  396. }
  397. }
  398. /* 输入框包装器 */
  399. .input-wrapper {
  400. position: relative;
  401. display: flex;
  402. align-items: center;
  403. .input-icon {
  404. position: absolute;
  405. left: 12px;
  406. color: #909399;
  407. font-size: 16px;
  408. z-index: 1;
  409. }
  410. &.captcha-wrapper {
  411. .el-input {
  412. flex: 1;
  413. }
  414. }
  415. }
  416. /* 输入框样式 */
  417. .login-input {
  418. width: 100%;
  419. :deep(.el-input__inner) {
  420. height: 44px;
  421. line-height: 44px;
  422. padding-left: 38px;
  423. border-radius: 4px;
  424. border: 1px solid #dcdfe6;
  425. font-size: 14px;
  426. transition: all 0.3s;
  427. &::placeholder {
  428. color: #c0c4cc;
  429. }
  430. &:focus {
  431. border-color: #409eff;
  432. }
  433. }
  434. :deep(.el-input__suffix) {
  435. right: 10px;
  436. }
  437. }
  438. /* 验证码图片 */
  439. .login-captcha {
  440. height: 44px;
  441. width: 100px;
  442. margin-left: 10px;
  443. border-radius: 4px;
  444. border: 1px solid #dcdfe6;
  445. cursor: pointer;
  446. flex-shrink: 0;
  447. &:hover {
  448. opacity: 0.85;
  449. }
  450. }
  451. /* 记住密码 */
  452. .remember-wrapper {
  453. margin-bottom: 20px;
  454. .remember-checkbox {
  455. :deep(.el-checkbox__label) {
  456. font-size: 13px;
  457. color: #606266;
  458. padding-left: 6px;
  459. }
  460. }
  461. }
  462. /* 登录按钮 */
  463. .login-btn {
  464. width: 100%;
  465. height: 46px;
  466. font-size: 16px;
  467. font-weight: 500;
  468. border-radius: 4px;
  469. background: linear-gradient(135deg, #409eff 0%, #2c8cf0 100%);
  470. border: none;
  471. box-shadow: 0 4px 12px rgba(64, 158, 255, 0.35);
  472. transition: all 0.3s;
  473. &:hover {
  474. background: linear-gradient(135deg, #66b1ff 0%, #409eff 100%);
  475. box-shadow: 0 6px 16px rgba(64, 158, 255, 0.45);
  476. transform: translateY(-1px);
  477. }
  478. &:active {
  479. transform: translateY(0);
  480. }
  481. }
  482. /* 多语言切换 */
  483. .lang-switch {
  484. position: absolute;
  485. right: 30px;
  486. top: 20px;
  487. z-index: 10;
  488. }
  489. /* 响应式 */
  490. @media screen and (max-width: 860px) {
  491. .login-card {
  492. width: 400px;
  493. height: auto;
  494. flex-direction: column;
  495. }
  496. .login-card-left {
  497. display: none;
  498. }
  499. .login-card-right {
  500. width: 100%;
  501. padding: 40px 40px;
  502. }
  503. }
  504. @media screen and (max-width: 480px) {
  505. .login-card {
  506. width: 90%;
  507. max-width: 360px;
  508. }
  509. .login-card-right {
  510. padding: 35px 25px;
  511. }
  512. .login-form {
  513. .form-title {
  514. font-size: 22px;
  515. }
  516. }
  517. }
  518. </style>