index.vue 8.9 KB

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