index.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <template>
  2. <div class="login-container">
  3. <!-- 顶部 -->
  4. <!-- <div class="absolute-lt flex-x-end p-3 w-full">
  5. <el-switch
  6. v-model="isDark"
  7. inline-prompt
  8. :active-icon="Moon"
  9. :inactive-icon="Sunny"
  10. @change="toggleTheme"
  11. />
  12. <lang-select class="ml-2 cursor-pointer" />
  13. </div> -->
  14. <!-- 登录表单 -->
  15. <el-card
  16. class="!border-none !bg-transparent !rounded-4% w-100 <sm:w-85"
  17. style="position: relative; right: -20%"
  18. >
  19. <div class="text-center relative">
  20. <h2>{{ defaultSettings.title }}</h2>
  21. <el-tag class="ml-2 absolute-rt">{{ defaultSettings.version }}</el-tag>
  22. </div>
  23. <el-form
  24. ref="loginFormRef"
  25. :model="loginData"
  26. :rules="loginRules"
  27. class="login-form"
  28. >
  29. <!-- 用户名 -->
  30. <el-form-item prop="userName">
  31. <div class="flex-y-center w-full">
  32. <svg-icon class="mx-2" icon-class="user" />
  33. <el-input
  34. ref="username"
  35. v-model="loginData.userName"
  36. :placeholder="$t('login.username')"
  37. class="h-[48px]"
  38. name="userName"
  39. size="large"
  40. />
  41. </div>
  42. </el-form-item>
  43. <!-- 密码 -->
  44. <el-tooltip
  45. :content="$t('login.capsLock')"
  46. :visible="isCapslock"
  47. placement="right"
  48. >
  49. <el-form-item prop="password">
  50. <div class="flex-y-center w-full">
  51. <svg-icon class="mx-2" icon-class="lock" />
  52. <el-input
  53. v-model="loginData.password"
  54. :placeholder="$t('login.password')"
  55. class="h-[48px] pr-2"
  56. name="password"
  57. show-password
  58. size="large"
  59. type="password"
  60. @keyup="checkCapslock"
  61. @keyup.enter="handleLogin"
  62. />
  63. </div>
  64. </el-form-item>
  65. </el-tooltip>
  66. <!-- 组织 -->
  67. <el-form-item prop="orgId">
  68. <div class="flex-y-center w-full">
  69. <svg-icon class="mx-2" icon-class="captcha" />
  70. <el-select
  71. v-model="loginData.orgId"
  72. class="no-border"
  73. placeholder="请选择组织"
  74. size="large"
  75. @keyup.enter="handleLogin"
  76. >
  77. <el-option
  78. v-for="item in orgList"
  79. :key="item.id"
  80. :label="item.deptName"
  81. :value="item.id"
  82. />
  83. </el-select>
  84. </div>
  85. </el-form-item>
  86. <!-- 登录按钮 -->
  87. <el-button
  88. :loading="loading"
  89. class="w-full"
  90. size="large"
  91. type="primary"
  92. @click.prevent="handleLogin"
  93. >{{ $t("login.login") }}
  94. </el-button>
  95. <!-- 账号密码提示 -->
  96. <!-- <div class="mt-10 text-sm">
  97. <span>{{ $t("login.username") }}: admin</span>
  98. <span class="ml-4"> {{ $t("login.password") }}: 123456</span>
  99. </div> -->
  100. </el-form>
  101. </el-card>
  102. <!-- ICP备案 -->
  103. <div v-show="icpVisible" class="absolute bottom-1 text-[10px] text-center">
  104. <p>Copyright © 2022-2025 jgai.com All Rights Reserved.</p>
  105. </div>
  106. </div>
  107. </template>
  108. <script lang="ts" setup>
  109. import { useSettingsStore, useUserStore, useDictionaryStore } from "@/store";
  110. import { getCaptchaApi, getOrgListApi, getUserDicts } from "@/api/auth";
  111. import { LoginData } from "@/api/auth/types";
  112. import { Sunny, Moon } from "@element-plus/icons-vue";
  113. import { LocationQuery, LocationQueryValue, useRoute } from "vue-router";
  114. import defaultSettings from "@/settings";
  115. import { ThemeEnum } from "@/enums/ThemeEnum";
  116. import { usePermissionStore } from "@/store/modules/permission";
  117. // Stores
  118. const userStore = useUserStore();
  119. const settingsStore = useSettingsStore();
  120. // 数据字典相关
  121. const dictStore = useDictionaryStore();
  122. // Internationalization
  123. const { t } = useI18n();
  124. // Reactive states
  125. const isDark = ref(settingsStore.theme === ThemeEnum.DARK);
  126. const icpVisible = ref(true);
  127. const orgList = ref<any>([]);
  128. const loading = ref(false); // 按钮loading
  129. const isCapslock = ref(false); // 是否大写锁定
  130. const captchaBase64 = ref(); // 验证码图片Base64字符串
  131. const loginFormRef = ref(ElForm); // 登录表单ref
  132. const { height } = useWindowSize();
  133. const loginData = ref<any>({
  134. userName: "",
  135. password: "",
  136. });
  137. const loginRules = computed?.(() => {
  138. return {
  139. userName: [
  140. {
  141. required: true,
  142. trigger: "blur",
  143. message: t("login.message.username.required"),
  144. },
  145. ],
  146. password: [
  147. {
  148. required: true,
  149. trigger: "blur",
  150. message: t("login.message.password.required"),
  151. },
  152. {
  153. min: 6,
  154. message: t("login.message.password.min"),
  155. trigger: "blur",
  156. },
  157. ],
  158. orgId: [
  159. {
  160. required: true,
  161. trigger: "blur",
  162. message: t("login.message.orgId.required"),
  163. },
  164. ],
  165. };
  166. });
  167. /**
  168. * 获取验证码
  169. */
  170. function getCaptcha() {
  171. getCaptchaApi().then(({ data }) => {
  172. loginData.value.captchaKey = data.captchaKey;
  173. captchaBase64.value = data.captchaBase64;
  174. });
  175. }
  176. function getOrgList() {
  177. getOrgListApi().then((data: any) => {
  178. orgList.value = data.data;
  179. if (orgList.value) {
  180. loginData.value.orgId = orgList.value[0].id;
  181. }
  182. });
  183. }
  184. /**
  185. * 登录
  186. */
  187. const route = useRoute();
  188. const router = useRouter();
  189. function handleLogin() {
  190. loginFormRef.value.validate((valid: boolean) => {
  191. if (valid) {
  192. //保存用户名和密码
  193. localStorage.setItem("local_name", loginData.value.userName);
  194. localStorage.setItem("local_pwd", loginData.value.password);
  195. loading.value = true;
  196. userStore
  197. .login(loginData.value)
  198. .then(async () => {
  199. const query: LocationQuery = route.query;
  200. const redirect = (query.redirect as LocationQueryValue) ?? "/";
  201. const otherQueryParams = Object.keys(query).reduce(
  202. (acc: any, cur: string) => {
  203. if (cur !== "redirect") {
  204. acc[cur] = query[cur];
  205. }
  206. return acc;
  207. },
  208. {}
  209. );
  210. // 获取字典
  211. const dictStore = useDictionaryStore();
  212. let res = await getUserDicts(dictStore.types);
  213. if (res?.data) {
  214. dictStore.dicts = res?.data ?? {};
  215. }
  216. // router.push({ path: redirect, query: otherQueryParams });
  217. router.push("/welcome");
  218. })
  219. .catch(() => {
  220. // getCaptcha();
  221. console.log("catch");
  222. })
  223. .finally(() => {
  224. loading.value = false;
  225. });
  226. }
  227. });
  228. }
  229. /**
  230. * 主题切换
  231. */
  232. const toggleTheme = () => {
  233. //const newTheme = settingsStore.theme === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
  234. //settingsStore.changeTheme(newTheme);
  235. };
  236. /**
  237. * 根据屏幕宽度切换设备模式
  238. */
  239. watchEffect?.(() => {
  240. if (height.value < 600) {
  241. icpVisible.value = false;
  242. } else {
  243. icpVisible.value = true;
  244. }
  245. });
  246. /**
  247. * 检查输入大小写
  248. */
  249. function checkCapslock(e: any) {
  250. isCapslock.value = e.getModifierState("CapsLock");
  251. }
  252. onMounted?.(() => {
  253. getOrgList();
  254. toggleTheme();
  255. if (
  256. localStorage.getItem("local_name") &&
  257. localStorage.getItem("local_name") !== "null"
  258. ) {
  259. loginData.value.userName = localStorage.getItem("local_name");
  260. loginData.value.password = localStorage.getItem("local_pwd");
  261. }
  262. });
  263. </script>
  264. <style lang="scss" scoped>
  265. .login-container {
  266. overflow-y: auto;
  267. background-image: url("@/assets/images/login-bg.png");
  268. background-size: cover;
  269. background-position: center;
  270. @apply wh-full flex-center;
  271. .login-form {
  272. padding: 30px 10px;
  273. }
  274. }
  275. .el-form-item {
  276. background: var(--el-input-bg-color);
  277. border: 1px solid var(--el-border-color);
  278. border-radius: 5px;
  279. }
  280. :deep(.el-input) {
  281. .el-input__wrapper {
  282. padding: 0;
  283. background-color: transparent;
  284. box-shadow: none;
  285. &.is-focus,
  286. &:hover {
  287. box-shadow: none !important;
  288. }
  289. input:-webkit-autofill {
  290. /* 通过延时渲染背景色变相去除背景颜色 */
  291. transition: background-color 1000s ease-in-out 0s;
  292. }
  293. }
  294. }
  295. :deep(.el-select) {
  296. .el-select__wrapper {
  297. padding: 0;
  298. background-color: transparent;
  299. box-shadow: none;
  300. &.is-focus,
  301. &:hover {
  302. box-shadow: none !important;
  303. }
  304. input:-webkit-autofill {
  305. /* 通过延时渲染背景色变相去除背景颜色 */
  306. transition: background-color 1000s ease-in-out 0s;
  307. }
  308. }
  309. }
  310. </style>