index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. <script setup lang="ts">
  2. // @ts-check
  3. import Motion from "./utils/motion";
  4. import { useRoute, useRouter } from "vue-router";
  5. import { message } from "@/utils/message";
  6. import { loginRules, smsRules } from "./utils/rule";
  7. import { onMounted, reactive, ref, toRaw } from "vue";
  8. import { debounce } from "@pureadmin/utils";
  9. import { useNav } from "@/layout/hooks/useNav";
  10. import { useEventListener } from "@vueuse/core";
  11. import type { FormInstance } from "element-plus";
  12. import { ElMessage } from "element-plus";
  13. import { useLayout } from "@/layout/hooks/useLayout";
  14. import { useUserStoreHook } from "@/store/modules/user";
  15. import { addPathMatch, getTopMenu, initRouter } from "@/router/utils";
  16. import { avatar, bg, illustration } from "./utils/static";
  17. import { useRenderIcon } from "@/components/ReIcon/src/hooks";
  18. import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
  19. import dayIcon from "@/assets/svg/day.svg?component";
  20. import darkIcon from "@/assets/svg/dark.svg?component";
  21. import Lock from "~icons/ri/lock-fill";
  22. import User from "~icons/ri/user-3-fill";
  23. import MaterialSymbolsLightDomainVerificationRounded from "~icons/material-symbols-light/domain-verification-rounded";
  24. import EpIphone from "~icons/ep/iphone";
  25. import { getVerificationCode, getVerifySmsApi } from "@/api/user";
  26. import { usePermissionStoreHook } from "@/store/modules/permission";
  27. import { AesEncode } from "@/utils/crypto";
  28. import { md5 } from "js-md5";
  29. import { rule } from "postcss";
  30. defineOptions({
  31. name: "Login"
  32. });
  33. const route = useRoute();
  34. const router = useRouter();
  35. const loading = ref(false);
  36. const disabled = ref(false);
  37. const showVerifySms = ref(false);
  38. const canSendSecond = ref(true);
  39. const ruleFormRef = ref<FormInstance>();
  40. const smsFormRef = ref<FormInstance>();
  41. const sendVerifySmsCodeText = ref<string>("点击发送验证码");
  42. const sendInterval = ref();
  43. const countdown = ref<number>(60);
  44. const imgSrc = ref<string>(null);
  45. const { initStorage } = useLayout();
  46. initStorage();
  47. const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange();
  48. dataThemeChange(overallStyle.value);
  49. const { title } = useNav();
  50. const ruleForm = reactive({
  51. username: "admin",
  52. password: "admin123",
  53. code: "",
  54. uuid: ""
  55. });
  56. const smsRuleForm = reactive({
  57. verifySmsCode: ""
  58. });
  59. const uuid = ref("");
  60. const getVerifyCode = async () => {
  61. try {
  62. let res = await getVerificationCode();
  63. if (res.code === 200) {
  64. ruleForm.code = "";
  65. imgSrc.value = `data:image/png;base64,${res.img}`;
  66. ruleForm.uuid = res.uuid;
  67. }
  68. } catch (e) {
  69. console.log(e);
  70. }
  71. };
  72. //发送短信验证码
  73. const getVerifySmsCode = () => {
  74. if (!canSendSecond.value) return;
  75. canSendSecond.value = false;
  76. getVerifySmsApi({
  77. username: ruleForm.username,
  78. password: ruleForm.password
  79. })
  80. .then(() => {
  81. sendInterval.value = setInterval(function () {
  82. if (countdown.value === 0) {
  83. clearInterval(sendInterval.value);
  84. sendVerifySmsCodeText.value = "点击发送验证码";
  85. canSendSecond.value = true;
  86. countdown.value = 60;
  87. sendInterval.value = null;
  88. } else {
  89. countdown.value = countdown.value - 1;
  90. sendVerifySmsCodeText.value = `${countdown.value}秒后重试`;
  91. }
  92. }, 1000);
  93. })
  94. .catch(() => {
  95. canSendSecond.value = true;
  96. });
  97. };
  98. const onLogin = async (formEl: FormInstance | undefined) => {
  99. if (!formEl) return;
  100. const submitType = showVerifySms.value ? 2 : 1;
  101. // 1普通登录 2短信登录
  102. const ref = submitType === 1 ? ruleFormRef : smsFormRef;
  103. await ref.value.validate(valid => {
  104. if (valid) {
  105. loading.value = true;
  106. let params = {
  107. ...ruleForm
  108. };
  109. useUserStoreHook()
  110. .loginByUsername(params, submitType)
  111. .then(res => {
  112. console.log(res);
  113. let { code, msg } = res;
  114. if (code === 200 || code === 3) {
  115. // 存储输入信息
  116. /*const { storageExpires } = commonSetting;
  117. if (formData.agree) {
  118. storage.setCookie(
  119. "loginRemember",
  120. "1",
  121. storageExpires * 24 * 60 * 60
  122. );
  123. storage.setCookie(
  124. "loginRememberName",
  125. username,
  126. storageExpires * 24 * 60 * 60
  127. );
  128. storage.setCookie(
  129. "loginRememberPwd",
  130. password,
  131. storageExpires * 24 * 60 * 60
  132. );
  133. } else {
  134. storage.setCookie(
  135. "loginRemember",
  136. "0",
  137. storageExpires * 24 * 60 * 60
  138. );
  139. }*/
  140. message("登录成功,即将进入系统", {
  141. type: "success",
  142. duration: 1000,
  143. onClose() {
  144. if (code === 3) {
  145. ElMessage.warning(
  146. "为了保障您的账号信息安全,请修改登录密码,需包含8位数字、字母、特殊字符"
  147. );
  148. // 去修改密码页
  149. router.replace("/system/pwd_reset");
  150. } else {
  151. const toPath = decodeURIComponent(
  152. (route.query?.redirect || "/") as string
  153. );
  154. console.log(route.name);
  155. if (route.name === "Login") {
  156. initRouter().then(() => {
  157. router.push(getTopMenu(true).path);
  158. });
  159. // 全部采取静态路由模式
  160. /*usePermissionStoreHook().handleWholeMenus([]);
  161. addPathMatch();
  162. router.push(getTopMenu(true).path);*/
  163. } else router.replace(toPath);
  164. }
  165. }
  166. });
  167. return;
  168. }
  169. if (submitType === 1) {
  170. // 普通登录
  171. if (
  172. msg === "短信锁已过期" ||
  173. msg === "不是常用IP登录" ||
  174. msg === "上次登录时间超过7天" ||
  175. msg === "错误次数超过3次"
  176. ) {
  177. showVerifySms.value = true;
  178. canSendSecond.value = true;
  179. } else {
  180. ElMessage.warning(msg);
  181. getVerifyCode();
  182. }
  183. } else {
  184. // sms登录
  185. ElMessage.warning(msg);
  186. getVerifyCode();
  187. showVerifySms.value = false;
  188. }
  189. })
  190. .catch(e => {
  191. ElMessage.error("登录失败");
  192. getVerifyCode();
  193. })
  194. .finally(() => (loading.value = false));
  195. }
  196. });
  197. };
  198. const immediateDebounce: any = debounce(
  199. formRef => onLogin(formRef),
  200. 1000,
  201. true
  202. );
  203. useEventListener(document, "keydown", ({ code }) => {
  204. if (
  205. ["Enter", "NumpadEnter"].includes(code) &&
  206. !disabled.value &&
  207. !loading.value
  208. )
  209. immediateDebounce(ruleFormRef.value);
  210. });
  211. onMounted(() => {
  212. getVerifyCode();
  213. });
  214. </script>
  215. <template>
  216. <div class="select-none">
  217. <img :src="bg" class="wave" />
  218. <div class="flex-c absolute right-5 top-3">
  219. <!-- 主题 -->
  220. <el-switch
  221. v-model="dataTheme"
  222. inline-prompt
  223. :active-icon="dayIcon"
  224. :inactive-icon="darkIcon"
  225. @change="dataThemeChange"
  226. />
  227. </div>
  228. <div class="login-container">
  229. <div class="img">
  230. <component :is="toRaw(illustration)" />
  231. </div>
  232. <div class="login-box">
  233. <div class="login-form">
  234. <avatar class="avatar" />
  235. <Motion>
  236. <h2 class="outline-hidden">{{ title }}</h2>
  237. </Motion>
  238. <el-form
  239. v-show="!showVerifySms"
  240. ref="ruleFormRef"
  241. :model="ruleForm"
  242. :rules="loginRules"
  243. size="large"
  244. >
  245. <Motion :delay="100">
  246. <el-form-item prop="username">
  247. <el-input
  248. v-model="ruleForm.username"
  249. clearable
  250. placeholder="账号"
  251. :prefix-icon="useRenderIcon(User)"
  252. />
  253. </el-form-item>
  254. </Motion>
  255. <Motion :delay="150">
  256. <el-form-item prop="password">
  257. <el-input
  258. v-model="ruleForm.password"
  259. clearable
  260. show-password
  261. placeholder="密码"
  262. :prefix-icon="useRenderIcon(Lock)"
  263. />
  264. </el-form-item>
  265. </Motion>
  266. <Motion :delay="200">
  267. <el-form-item prop="code">
  268. <el-row class="w-full">
  269. <el-col :span="17">
  270. <el-input
  271. v-model="ruleForm.code"
  272. clearable
  273. :prefix-icon="
  274. useRenderIcon(
  275. MaterialSymbolsLightDomainVerificationRounded
  276. )
  277. "
  278. />
  279. </el-col>
  280. <el-col :span="6" :offset="1">
  281. <div
  282. v-optimize="{
  283. event: 'click',
  284. fn: getVerifyCode,
  285. immediate: true,
  286. timeout: 1000
  287. }"
  288. class="verify-code"
  289. >
  290. <img class="w-full! h-full!" :src="imgSrc" alt="" />
  291. </div>
  292. </el-col>
  293. </el-row>
  294. </el-form-item>
  295. </Motion>
  296. <Motion :delay="250">
  297. <el-form-item>
  298. <el-checkbox v-model="ruleForm.agree">
  299. 下次登录记住我的身份
  300. </el-checkbox>
  301. </el-form-item>
  302. </Motion>
  303. <Motion :delay="300">
  304. <el-button
  305. class="w-full"
  306. size="default"
  307. type="primary"
  308. :loading="loading"
  309. :disabled="disabled"
  310. @click="onLogin(ruleFormRef)"
  311. >
  312. 登录
  313. </el-button>
  314. </Motion>
  315. </el-form>
  316. <!-- 短信验证码表单 -->
  317. <el-form
  318. v-show="showVerifySms"
  319. ref="smsFormRef"
  320. :model="smsRuleForm"
  321. :rules="smsRules"
  322. size="large"
  323. @submit.prevent
  324. >
  325. <Motion :delay="100">
  326. <el-form-item prop="username">
  327. <el-row class="w-full">
  328. <el-col :span="15">
  329. <el-input
  330. v-model="smsRuleForm.verifySmsCode"
  331. clearable
  332. placeholder="请输入验证码"
  333. :prefix-icon="useRenderIcon(EpIphone)"
  334. />
  335. </el-col>
  336. <el-col :span="8" :offset="1">
  337. <el-button
  338. v-optimize="{
  339. event: 'click',
  340. fn: getVerifySmsCode,
  341. immediate: true,
  342. timeout: 1000
  343. }"
  344. class="w-full"
  345. type="primary"
  346. :disabled="!canSendSecond"
  347. >{{ sendVerifySmsCodeText }}
  348. </el-button>
  349. </el-col>
  350. </el-row>
  351. </el-form-item>
  352. </Motion>
  353. <Motion :delay="150">
  354. <el-form-item class="default-color">
  355. <el-checkbox v-model="ruleForm.agree">
  356. 下次登录记住我的身份
  357. </el-checkbox>
  358. </el-form-item>
  359. </Motion>
  360. <Motion :delay="200">
  361. <el-button
  362. class="w-full"
  363. size="default"
  364. type="primary"
  365. :loading="loading"
  366. :disabled="disabled"
  367. @click="onLogin(smsFormRef)"
  368. >
  369. 登录
  370. </el-button>
  371. </Motion>
  372. </el-form>
  373. </div>
  374. </div>
  375. </div>
  376. </div>
  377. </template>
  378. <style scoped>
  379. @import url("@/style/login.css");
  380. </style>
  381. <style lang="scss" scoped>
  382. :deep(.el-input-group__append, .el-input-group__prepend) {
  383. padding: 0;
  384. }
  385. .verify-code {
  386. float: right;
  387. width: 100%;
  388. height: 40px;
  389. margin-top: 1px;
  390. font-size: 16px;
  391. line-height: 40px;
  392. text-align: center;
  393. cursor: pointer;
  394. user-select: none;
  395. user-select: none;
  396. user-select: none;
  397. background: #d8d8d8;
  398. border-radius: 5px;
  399. }
  400. </style>