index.vue 13 KB

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