index.vue 9.6 KB

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