index.vue 14 KB

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