index.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <script setup lang="ts">
  2. import {
  3. ref,
  4. toRaw,
  5. reactive,
  6. watch,
  7. computed,
  8. onMounted,
  9. onBeforeUnmount
  10. } from "vue";
  11. import { useI18n } from "vue-i18n";
  12. import Motion from "./utils/motion";
  13. import { useRouter } from "vue-router";
  14. import { message } from "@/utils/message";
  15. import { loginRules } from "./utils/rule";
  16. import phone from "./components/phone.vue";
  17. import TypeIt from "@/components/ReTypeit";
  18. import qrCode from "./components/qrCode.vue";
  19. import regist from "./components/regist.vue";
  20. import update from "./components/update.vue";
  21. import { useNav } from "@/layout/hooks/useNav";
  22. import type { FormInstance } from "element-plus";
  23. import { $t, transformI18n } from "@/plugins/i18n";
  24. import { operates, thirdParty } from "./utils/enums";
  25. import { useLayout } from "@/layout/hooks/useLayout";
  26. import { useUserStoreHook } from "@/store/modules/user";
  27. import { initRouter, getTopMenu } from "@/router/utils";
  28. import { bg, avatar, illustration } from "./utils/static";
  29. import { ReImageVerify } from "@/components/ReImageVerify";
  30. import { useRenderIcon } from "@/components/ReIcon/src/hooks";
  31. import { useTranslationLang } from "@/layout/hooks/useTranslationLang";
  32. import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
  33. import dayIcon from "@/assets/svg/day.svg?component";
  34. import darkIcon from "@/assets/svg/dark.svg?component";
  35. import globalization from "@/assets/svg/globalization.svg?component";
  36. import Lock from "@iconify-icons/ri/lock-fill";
  37. import Check from "@iconify-icons/ep/check";
  38. import User from "@iconify-icons/ri/user-3-fill";
  39. defineOptions({
  40. name: "Login"
  41. });
  42. const imgCode = ref("");
  43. const router = useRouter();
  44. const loading = ref(false);
  45. const checked = ref(false);
  46. const ruleFormRef = ref<FormInstance>();
  47. const currentPage = computed(() => {
  48. return useUserStoreHook().currentPage;
  49. });
  50. const { t } = useI18n();
  51. const { initStorage } = useLayout();
  52. initStorage();
  53. const { dataTheme, dataThemeChange } = useDataThemeChange();
  54. dataThemeChange();
  55. const { title, getDropdownItemStyle, getDropdownItemClass } = useNav();
  56. const { locale, translationCh, translationEn } = useTranslationLang();
  57. const ruleForm = reactive({
  58. username: "admin",
  59. password: "admin123",
  60. verifyCode: ""
  61. });
  62. const onLogin = async (formEl: FormInstance | undefined) => {
  63. loading.value = true;
  64. if (!formEl) return;
  65. await formEl.validate((valid, fields) => {
  66. if (valid) {
  67. useUserStoreHook()
  68. .loginByUsername({ username: ruleForm.username, password: "admin123" })
  69. .then(res => {
  70. if (res.success) {
  71. // 获取后端路由
  72. initRouter().then(() => {
  73. router.push(getTopMenu(true).path);
  74. message("登录成功", { type: "success" });
  75. });
  76. }
  77. })
  78. .finally(() => (loading.value = false));
  79. } else {
  80. loading.value = false;
  81. return fields;
  82. }
  83. });
  84. };
  85. /** 使用公共函数,避免`removeEventListener`失效 */
  86. function onkeypress({ code }: KeyboardEvent) {
  87. if (code === "Enter") {
  88. onLogin(ruleFormRef.value);
  89. }
  90. }
  91. onMounted(() => {
  92. window.document.addEventListener("keypress", onkeypress);
  93. });
  94. onBeforeUnmount(() => {
  95. window.document.removeEventListener("keypress", onkeypress);
  96. });
  97. watch(imgCode, value => {
  98. useUserStoreHook().SET_VERIFYCODE(value);
  99. });
  100. </script>
  101. <template>
  102. <div class="select-none">
  103. <img :src="bg" class="wave" />
  104. <div class="flex-c absolute right-5 top-3">
  105. <!-- 主题 -->
  106. <el-switch
  107. v-model="dataTheme"
  108. inline-prompt
  109. :active-icon="dayIcon"
  110. :inactive-icon="darkIcon"
  111. @change="dataThemeChange"
  112. />
  113. <!-- 国际化 -->
  114. <el-dropdown trigger="click">
  115. <globalization
  116. class="hover:text-primary hover:!bg-[transparent] w-[20px] h-[20px] ml-1.5 cursor-pointer outline-none duration-300"
  117. />
  118. <template #dropdown>
  119. <el-dropdown-menu class="translation">
  120. <el-dropdown-item
  121. :style="getDropdownItemStyle(locale, 'zh')"
  122. :class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
  123. @click="translationCh"
  124. >
  125. <IconifyIconOffline
  126. class="check-zh"
  127. v-show="locale === 'zh'"
  128. :icon="Check"
  129. />
  130. 简体中文
  131. </el-dropdown-item>
  132. <el-dropdown-item
  133. :style="getDropdownItemStyle(locale, 'en')"
  134. :class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
  135. @click="translationEn"
  136. >
  137. <span class="check-en" v-show="locale === 'en'">
  138. <IconifyIconOffline :icon="Check" />
  139. </span>
  140. English
  141. </el-dropdown-item>
  142. </el-dropdown-menu>
  143. </template>
  144. </el-dropdown>
  145. </div>
  146. <div class="login-container">
  147. <div class="img">
  148. <component :is="toRaw(illustration)" />
  149. </div>
  150. <div class="login-box">
  151. <div class="login-form">
  152. <avatar class="avatar" />
  153. <Motion>
  154. <h2 class="outline-none">
  155. <TypeIt :values="[title]" :cursor="false" :speed="150" />
  156. </h2>
  157. </Motion>
  158. <el-form
  159. v-if="currentPage === 0"
  160. ref="ruleFormRef"
  161. :model="ruleForm"
  162. :rules="loginRules"
  163. size="large"
  164. >
  165. <Motion :delay="100">
  166. <el-form-item
  167. :rules="[
  168. {
  169. required: true,
  170. message: transformI18n($t('login.usernameReg')),
  171. trigger: 'blur'
  172. }
  173. ]"
  174. prop="username"
  175. >
  176. <el-input
  177. clearable
  178. v-model="ruleForm.username"
  179. :placeholder="t('login.username')"
  180. :prefix-icon="useRenderIcon(User)"
  181. />
  182. </el-form-item>
  183. </Motion>
  184. <Motion :delay="150">
  185. <el-form-item prop="password">
  186. <el-input
  187. clearable
  188. show-password
  189. v-model="ruleForm.password"
  190. :placeholder="t('login.password')"
  191. :prefix-icon="useRenderIcon(Lock)"
  192. />
  193. </el-form-item>
  194. </Motion>
  195. <Motion :delay="200">
  196. <el-form-item prop="verifyCode">
  197. <el-input
  198. clearable
  199. v-model="ruleForm.verifyCode"
  200. :placeholder="t('login.verifyCode')"
  201. :prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
  202. >
  203. <template v-slot:append>
  204. <ReImageVerify v-model:code="imgCode" />
  205. </template>
  206. </el-input>
  207. </el-form-item>
  208. </Motion>
  209. <Motion :delay="250">
  210. <el-form-item>
  211. <div class="w-full h-[20px] flex justify-between items-center">
  212. <el-checkbox v-model="checked">
  213. {{ t("login.remember") }}
  214. </el-checkbox>
  215. <el-button
  216. link
  217. type="primary"
  218. @click="useUserStoreHook().SET_CURRENTPAGE(4)"
  219. >
  220. {{ t("login.forget") }}
  221. </el-button>
  222. </div>
  223. <el-button
  224. class="w-full mt-4"
  225. size="default"
  226. type="primary"
  227. :loading="loading"
  228. @click="onLogin(ruleFormRef)"
  229. >
  230. {{ t("login.login") }}
  231. </el-button>
  232. </el-form-item>
  233. </Motion>
  234. <Motion :delay="300">
  235. <el-form-item>
  236. <div class="w-full h-[20px] flex justify-between items-center">
  237. <el-button
  238. v-for="(item, index) in operates"
  239. :key="index"
  240. class="w-full mt-4"
  241. size="default"
  242. @click="useUserStoreHook().SET_CURRENTPAGE(index + 1)"
  243. >
  244. {{ t(item.title) }}
  245. </el-button>
  246. </div>
  247. </el-form-item>
  248. </Motion>
  249. </el-form>
  250. <Motion v-if="currentPage === 0" :delay="350">
  251. <el-form-item>
  252. <el-divider>
  253. <p class="text-gray-500 text-xs">{{ t("login.thirdLogin") }}</p>
  254. </el-divider>
  255. <div class="w-full flex justify-evenly">
  256. <span
  257. v-for="(item, index) in thirdParty"
  258. :key="index"
  259. :title="t(item.title)"
  260. >
  261. <IconifyIconOnline
  262. :icon="`ri:${item.icon}-fill`"
  263. width="20"
  264. class="cursor-pointer text-gray-500 hover:text-blue-400"
  265. />
  266. </span>
  267. </div>
  268. </el-form-item>
  269. </Motion>
  270. <!-- 手机号登录 -->
  271. <phone v-if="currentPage === 1" />
  272. <!-- 二维码登录 -->
  273. <qrCode v-if="currentPage === 2" />
  274. <!-- 注册 -->
  275. <regist v-if="currentPage === 3" />
  276. <!-- 忘记密码 -->
  277. <update v-if="currentPage === 4" />
  278. </div>
  279. </div>
  280. </div>
  281. </div>
  282. </template>
  283. <style scoped>
  284. @import url("@/style/login.css");
  285. </style>
  286. <style lang="scss" scoped>
  287. :deep(.el-input-group__append, .el-input-group__prepend) {
  288. padding: 0;
  289. }
  290. .translation {
  291. ::v-deep(.el-dropdown-menu__item) {
  292. padding: 5px 40px;
  293. }
  294. .check-zh {
  295. position: absolute;
  296. left: 20px;
  297. }
  298. .check-en {
  299. position: absolute;
  300. left: 20px;
  301. }
  302. }
  303. </style>