MiniLogin.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. <template>
  2. <div :class="prefixCls" class="login-background-img">
  3. <AppLocalePicker class="absolute top-4 right-4 enter-x xl:text-gray-600" :showText="false"/>
  4. <AppDarkModeToggle class="absolute top-3 right-7 enter-x" />
  5. <div class="aui-logo" v-if="!getIsMobile">
  6. <div>
  7. <h3>
  8. <img :src="logoImg" alt="jeecg" />
  9. </h3>
  10. </div>
  11. </div>
  12. <div v-else class="aui-phone-logo">
  13. <img :src="logoImg" alt="jeecg" />
  14. </div>
  15. <div v-show="type === 'login'">
  16. <div class="aui-content">
  17. <div class="aui-container">
  18. <div class="aui-form">
  19. <div class="aui-image">
  20. <div class="aui-image-text">
  21. <img :src="adTextImg" />
  22. </div>
  23. </div>
  24. <div class="aui-formBox">
  25. <div class="aui-formWell">
  26. <div class="aui-flex aui-form-nav investment_title">
  27. <div class="aui-flex-box" :class="activeIndex === 'accountLogin' ? 'activeNav on' : ''" @click="loginClick('accountLogin')"
  28. >{{ t('sys.login.signInFormTitle') }}
  29. </div>
  30. <div class="aui-flex-box" :class="activeIndex === 'phoneLogin' ? 'activeNav on' : ''" @click="loginClick('phoneLogin')"
  31. >{{ t('sys.login.mobileSignInFormTitle') }}
  32. </div>
  33. </div>
  34. <div class="aui-form-box" style="height: 180px">
  35. <a-form ref="loginRef" :model="formData" v-if="activeIndex === 'accountLogin'" @keyup.enter.native="loginHandleClick">
  36. <div class="aui-account">
  37. <div class="aui-inputClear">
  38. <i class="icon icon-code"></i>
  39. <a-form-item>
  40. <a-input class="fix-auto-fill" :placeholder="t('sys.login.userName')" v-model:value="formData.username" />
  41. </a-form-item>
  42. </div>
  43. <div class="aui-inputClear">
  44. <i class="icon icon-password"></i>
  45. <a-form-item>
  46. <a-input class="fix-auto-fill" type="password" :placeholder="t('sys.login.password')" v-model:value="formData.password" />
  47. </a-form-item>
  48. </div>
  49. <div class="aui-inputClear">
  50. <i class="icon icon-code"></i>
  51. <a-form-item>
  52. <a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.inputCode')" v-model:value="formData.inputCode" />
  53. </a-form-item>
  54. <div class="aui-code">
  55. <img v-if="randCodeData.requestCodeSuccess" :src="randCodeData.randCodeImage" @click="handleChangeCheckCode" />
  56. <img v-else style="margin-top: 2px; max-width: initial" :src="codeImg" @click="handleChangeCheckCode" />
  57. </div>
  58. </div>
  59. <div class="aui-flex">
  60. <div class="aui-flex-box">
  61. <div class="aui-choice">
  62. <a-input class="fix-auto-fill" type="checkbox" v-model:value="rememberMe" />
  63. <span style="margin-left: 5px">{{ t('sys.login.rememberMe') }}</span>
  64. </div>
  65. </div>
  66. <div class="aui-forget">
  67. <a @click="forgetHandelClick"> {{ t('sys.login.forgetPassword') }}</a>
  68. </div>
  69. </div>
  70. </div>
  71. </a-form>
  72. <a-form v-else ref="phoneFormRef" :model="phoneFormData" @keyup.enter.native="loginHandleClick">
  73. <div class="aui-account phone">
  74. <div class="aui-inputClear phoneClear">
  75. <a-input class="fix-auto-fill" :placeholder="t('sys.login.mobile')" v-model:value="phoneFormData.mobile" />
  76. </div>
  77. <div class="aui-inputClear">
  78. <a-input class="fix-auto-fill" :maxlength="6" :placeholder="t('sys.login.smsCode')" v-model:value="phoneFormData.smscode" />
  79. <div v-if="showInterval" class="aui-code" @click="getLoginCode">
  80. <a>{{ t('component.countdown.normalText') }}</a>
  81. </div>
  82. <div v-else class="aui-code">
  83. <span class="aui-get-code code-shape">{{ t('component.countdown.sendText', [unref(timeRuning)]) }}</span>
  84. </div>
  85. </div>
  86. </div>
  87. </a-form>
  88. </div>
  89. <div class="aui-formButton">
  90. <div class="aui-flex">
  91. <a-button :loading="loginLoading" class="aui-link-login aui-flex-box" type="primary" @click="loginHandleClick">
  92. {{ t('sys.login.loginButton') }}</a-button>
  93. </div>
  94. <div class="aui-flex">
  95. <a class="aui-linek-code aui-flex-box" @click="codeHandleClick">{{ t('sys.login.qrSignInFormTitle') }}</a>
  96. </div>
  97. <div class="aui-flex">
  98. <a class="aui-linek-code aui-flex-box" @click="registerHandleClick">{{ t('sys.login.registerButton') }}</a>
  99. </div>
  100. </div>
  101. </div>
  102. <a-form @keyup.enter.native="loginHandleClick">
  103. <div class="aui-flex aui-third-text">
  104. <div class="aui-flex-box aui-third-border">
  105. <span>{{ t('sys.login.otherSignIn') }}</span>
  106. </div>
  107. </div>
  108. <div class="aui-flex" :class="`${prefixCls}-sign-in-way`">
  109. <div class="aui-flex-box">
  110. <div class="aui-third-login">
  111. <a title="github" @click="onThirdLogin('github')"><GithubFilled /></a>
  112. </div>
  113. </div>
  114. <div class="aui-flex-box">
  115. <div class="aui-third-login">
  116. <a title="企业微信" @click="onThirdLogin('wechat_enterprise')"><icon-font class="item-icon" type="icon-qiyeweixin3" /></a>
  117. </div>
  118. </div>
  119. <div class="aui-flex-box">
  120. <div class="aui-third-login">
  121. <a title="钉钉" @click="onThirdLogin('dingtalk')"><DingtalkCircleFilled /></a>
  122. </div>
  123. </div>
  124. <div class="aui-flex-box">
  125. <div class="aui-third-login">
  126. <a title="微信" @click="onThirdLogin('wechat_open')"><WechatFilled /></a>
  127. </div>
  128. </div>
  129. </div>
  130. </a-form>
  131. </div>
  132. </div>
  133. </div>
  134. </div>
  135. </div>
  136. <div v-show="type === 'forgot'" :class="`${prefixCls}-form`">
  137. <MiniForgotpad ref="forgotRef" @go-back="goBack" @success="handleSuccess" />
  138. </div>
  139. <div v-show="type === 'register'" :class="`${prefixCls}-form`">
  140. <MiniRegister ref="registerRef" @go-back="goBack" @success="handleSuccess" />
  141. </div>
  142. <div v-show="type === 'codeLogin'" :class="`${prefixCls}-form`">
  143. <MiniCodelogin ref="codeRef" @go-back="goBack" @success="handleSuccess" />
  144. </div>
  145. <!-- 第三方登录相关弹框 -->
  146. <ThirdModal ref="thirdModalRef"></ThirdModal>
  147. </div>
  148. </template>
  149. <script lang="ts" setup name="login-mini">
  150. import { getCaptcha, getCodeInfo } from '/@/api/sys/user';
  151. import { computed, onMounted, reactive, ref, toRaw, unref } from 'vue';
  152. import codeImg from '/@/assets/images/checkcode.png';
  153. import { Rule } from '/@/components/Form';
  154. import { useUserStore } from '/@/store/modules/user';
  155. import { useMessage } from '/@/hooks/web/useMessage';
  156. import { useI18n } from '/@/hooks/web/useI18n';
  157. import { SmsEnum } from '/@/views/sys/login/useLogin';
  158. import ThirdModal from '/@/views/sys/login/ThirdModal.vue';
  159. import MiniForgotpad from './MiniForgotpad.vue';
  160. import MiniRegister from './MiniRegister.vue';
  161. import MiniCodelogin from './MiniCodelogin.vue';
  162. import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
  163. import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
  164. import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application';
  165. import { useLocaleStore } from '/@/store/modules/locale';
  166. import { useDesign } from "/@/hooks/web/useDesign";
  167. import { useAppInject } from "/@/hooks/web/useAppInject";
  168. import { GithubFilled, WechatFilled, DingtalkCircleFilled, createFromIconfontCN } from '@ant-design/icons-vue';
  169. const IconFont = createFromIconfontCN({
  170. scriptUrl: '//at.alicdn.com/t/font_2316098_umqusozousr.js',
  171. });
  172. const { prefixCls } = useDesign('mini-login');
  173. const { notification, createMessage } = useMessage();
  174. const userStore = useUserStore();
  175. const { t } = useI18n();
  176. const localeStore = useLocaleStore();
  177. const showLocale = localeStore.getShowPicker;
  178. const randCodeData = reactive<any>({
  179. randCodeImage: '',
  180. requestCodeSuccess: false,
  181. checkKey: null,
  182. });
  183. const rememberMe = ref<string>('0');
  184. //手机号登录还是账号登录
  185. const activeIndex = ref<string>('accountLogin');
  186. const type = ref<string>('login');
  187. //账号登录表单字段
  188. const formData = reactive<any>({
  189. inputCode: '',
  190. username: 'admin',
  191. password: '123456',
  192. });
  193. //手机登录表单字段
  194. const phoneFormData = reactive<any>({
  195. mobile: '',
  196. smscode: '',
  197. });
  198. const loginRef = ref();
  199. //第三方登录弹窗
  200. const thirdModalRef = ref();
  201. //扫码登录
  202. const codeRef = ref();
  203. //是否显示获取验证码
  204. const showInterval = ref<boolean>(true);
  205. //60s
  206. const timeRuning = ref<number>(60);
  207. //定时器
  208. const timer = ref<any>(null);
  209. //忘记密码
  210. const forgotRef = ref();
  211. //注册
  212. const registerRef = ref();
  213. const loginLoading = ref<boolean>(false);
  214. const { getIsMobile } = useAppInject();
  215. defineProps({
  216. sessionTimeout: {
  217. type: Boolean,
  218. },
  219. });
  220. /**
  221. * 获取验证码
  222. */
  223. function handleChangeCheckCode() {
  224. formData.inputCode = '';
  225. randCodeData.checkKey = 1629428467008;
  226. getCodeInfo(randCodeData.checkKey).then((res) => {
  227. randCodeData.randCodeImage = res;
  228. randCodeData.requestCodeSuccess = true;
  229. });
  230. }
  231. /**
  232. * 切换登录方式
  233. */
  234. function loginClick(type) {
  235. activeIndex.value = type;
  236. }
  237. /**
  238. * 账号或者手机登录
  239. */
  240. async function loginHandleClick() {
  241. if (unref(activeIndex) === 'accountLogin') {
  242. accountLogin();
  243. } else {
  244. //手机号登录
  245. phoneLogin();
  246. }
  247. }
  248. async function accountLogin() {
  249. if (!formData.username) {
  250. createMessage.warn(t('sys.login.accountPlaceholder'));
  251. return;
  252. }
  253. if (!formData.password) {
  254. createMessage.warn(t('sys.login.passwordPlaceholder'));
  255. return;
  256. }
  257. try {
  258. loginLoading.value = true;
  259. const { userInfo } = await userStore.login(
  260. toRaw({
  261. password: formData.password,
  262. username: formData.username,
  263. captcha: formData.inputCode,
  264. checkKey: randCodeData.checkKey,
  265. mode: 'none', //不要默认的错误提示
  266. })
  267. );
  268. if (userInfo) {
  269. notification.success({
  270. message: t('sys.login.loginSuccessTitle'),
  271. description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
  272. duration: 3,
  273. });
  274. }
  275. } catch (error) {
  276. notification.error({
  277. message: t('sys.api.errorTip'),
  278. description: error.message || t('sys.login.networkExceptionMsg'),
  279. duration: 3,
  280. });
  281. handleChangeCheckCode();
  282. } finally {
  283. loginLoading.value = false;
  284. }
  285. }
  286. /**
  287. * 手机号登录
  288. */
  289. async function phoneLogin() {
  290. if (!phoneFormData.mobile) {
  291. createMessage.warn(t('sys.login.mobilePlaceholder'));
  292. return;
  293. }
  294. if (!phoneFormData.smscode) {
  295. createMessage.warn(t('sys.login.smsPlaceholder'));
  296. return;
  297. }
  298. try {
  299. loginLoading.value = true;
  300. const { userInfo }: any = await userStore.phoneLogin({
  301. mobile: phoneFormData.mobile,
  302. captcha: phoneFormData.smscode,
  303. mode: 'none', //不要默认的错误提示
  304. });
  305. if (userInfo) {
  306. notification.success({
  307. message: t('sys.login.loginSuccessTitle'),
  308. description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
  309. duration: 3,
  310. });
  311. }
  312. } catch (error) {
  313. notification.error({
  314. message: t('sys.api.errorTip'),
  315. description: error.message || t('sys.login.networkExceptionMsg'),
  316. duration: 3,
  317. });
  318. } finally {
  319. loginLoading.value = false;
  320. }
  321. }
  322. /**
  323. * 获取手机验证码
  324. */
  325. async function getLoginCode() {
  326. if (!phoneFormData.mobile) {
  327. createMessage.warn(t('sys.login.mobilePlaceholder'));
  328. return;
  329. }
  330. const result = await getCaptcha({ mobile: phoneFormData.mobile, smsmode: SmsEnum.FORGET_PASSWORD });
  331. if (result) {
  332. const TIME_COUNT = 60;
  333. if (!unref(timer)) {
  334. timeRuning.value = TIME_COUNT;
  335. showInterval.value = false;
  336. timer.value = setInterval(() => {
  337. if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
  338. timeRuning.value = timeRuning.value - 1;
  339. } else {
  340. showInterval.value = true;
  341. clearInterval(unref(timer));
  342. timer.value = null;
  343. }
  344. }, 1000);
  345. }
  346. }
  347. }
  348. /**
  349. * 第三方登录
  350. * @param type
  351. */
  352. function onThirdLogin(type) {
  353. thirdModalRef.value.onThirdLogin(type);
  354. }
  355. /**
  356. * 忘记密码
  357. */
  358. function forgetHandelClick() {
  359. type.value = 'forgot';
  360. setTimeout(() => {
  361. forgotRef.value.initForm();
  362. }, 300);
  363. }
  364. /**
  365. * 返回登录页面
  366. */
  367. function goBack() {
  368. activeIndex.value = 'accountLogin';
  369. type.value = 'login';
  370. }
  371. /**
  372. * 忘记密码/注册账号回调事件
  373. * @param value
  374. */
  375. function handleSuccess(value) {
  376. Object.assign(formData, value);
  377. Object.assign(phoneFormData, { mobile: "", smscode: "" });
  378. type.value = 'login';
  379. activeIndex.value = 'accountLogin';
  380. handleChangeCheckCode();
  381. }
  382. /**
  383. * 注册
  384. */
  385. function registerHandleClick() {
  386. type.value = 'register';
  387. setTimeout(() => {
  388. registerRef.value.initForm();
  389. }, 300);
  390. }
  391. /**
  392. * 注册
  393. */
  394. function codeHandleClick() {
  395. type.value = 'codeLogin';
  396. setTimeout(() => {
  397. codeRef.value.initFrom();
  398. }, 300);
  399. }
  400. onMounted(() => {
  401. //加载验证码
  402. handleChangeCheckCode();
  403. });
  404. </script>
  405. <style lang="less" scoped>
  406. @import '/@/assets/loginmini/style/home.less';
  407. @import '/@/assets/loginmini/style/base.less';
  408. :deep(.ant-input:focus) {
  409. box-shadow: none;
  410. }
  411. .aui-get-code {
  412. float: right;
  413. position: relative;
  414. z-index: 3;
  415. background: #ffffff;
  416. color: #1573e9;
  417. border-radius: 100px;
  418. padding: 5px 16px;
  419. margin: 7px;
  420. border: 1px solid #1573e9;
  421. top: 12px;
  422. }
  423. .aui-get-code:hover {
  424. color: #1573e9;
  425. }
  426. .code-shape {
  427. border-color: #dadada !important;
  428. color: #aaa !important;
  429. }
  430. :deep(.jeecg-dark-switch){
  431. position:absolute;
  432. margin-right: 10px;
  433. }
  434. .aui-link-login{
  435. height: 42px;
  436. padding: 10px 15px;
  437. font-size: 14px;
  438. border-radius: 8px;
  439. margin-top: 15px;
  440. margin-bottom: 8px;
  441. }
  442. .aui-phone-logo{
  443. position: absolute;
  444. margin-left: 10px;
  445. width: 60px;
  446. top:2px;
  447. z-index: 4;
  448. }
  449. .top-3{
  450. top: 0.45rem;
  451. }
  452. </style>
  453. <style lang="less">
  454. @prefix-cls: ~'@{namespace}-mini-login';
  455. @dark-bg: #293146;
  456. html[data-theme='dark'] {
  457. .@{prefix-cls} {
  458. background-color: @dark-bg !important;
  459. background-image: none;
  460. &::before {
  461. background-image: url(/@/assets/svg/login-bg-dark.svg);
  462. }
  463. .aui-inputClear{
  464. background-color: #232a3b !important;
  465. }
  466. .ant-input,
  467. .ant-input-password {
  468. background-color: #232a3b !important;
  469. }
  470. .ant-btn:not(.ant-btn-link):not(.ant-btn-primary) {
  471. border: 1px solid #4a5569 !important;
  472. }
  473. &-form {
  474. background: @dark-bg !important;
  475. }
  476. .app-iconify {
  477. color: #fff !important;
  478. }
  479. .aui-inputClear input,.aui-input-line input,.aui-choice{
  480. color: #c9d1d9 !important;
  481. }
  482. .aui-formBox{
  483. background-color: @dark-bg !important;
  484. }
  485. .aui-third-text span{
  486. background-color: @dark-bg !important;
  487. }
  488. .aui-form-nav .aui-flex-box{
  489. color: #c9d1d9 !important;
  490. }
  491. .aui-formButton .aui-linek-code{
  492. background: @dark-bg !important;
  493. color: white !important;
  494. }
  495. .aui-code-line{
  496. border-left: none !important;
  497. }
  498. .ant-checkbox-inner,.aui-success h3{
  499. border-color: #c9d1d9;
  500. }
  501. //update-begin---author:wangshuai ---date:20230828 for:【QQYUN-6363】这个样式代码有问题,不在里面,导致表达式有问题------------
  502. &-sign-in-way {
  503. .anticon {
  504. font-size: 22px !important;
  505. color: #888 !important;
  506. cursor: pointer !important;
  507. &:hover {
  508. color: @primary-color !important;
  509. }
  510. }
  511. }
  512. //update-end---author:wangshuai ---date:20230828 for:【QQYUN-6363】这个样式代码有问题,不在里面,导致表达式有问题------------
  513. }
  514. input.fix-auto-fill,
  515. .fix-auto-fill input {
  516. -webkit-text-fill-color: #c9d1d9 !important;
  517. box-shadow: inherit !important;
  518. }
  519. .ant-divider-inner-text {
  520. font-size: 12px !important;
  521. color: @text-color-secondary !important;
  522. }
  523. .aui-third-login a{
  524. background: transparent;
  525. }
  526. }
  527. </style>