index.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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 } from "@/store";
  107. import { getCaptchaApi, getOrgListApi } 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. // Internationalization
  118. const { t } = useI18n();
  119. // Reactive states
  120. const isDark = ref(settingsStore.theme === ThemeEnum.DARK);
  121. const icpVisible = ref(true);
  122. const orgList = ref([]);
  123. const loading = ref(false); // 按钮loading
  124. const isCapslock = ref(false); // 是否大写锁定
  125. const captchaBase64 = ref(); // 验证码图片Base64字符串
  126. const loginFormRef = ref(ElForm); // 登录表单ref
  127. const { height } = useWindowSize();
  128. const loginData = ref<LoginData>({
  129. userName: "",
  130. password: "",
  131. });
  132. const loginRules = computed?.(() => {
  133. return {
  134. userName: [
  135. {
  136. required: true,
  137. trigger: "blur",
  138. message: t("login.message.username.required"),
  139. },
  140. ],
  141. password: [
  142. {
  143. required: true,
  144. trigger: "blur",
  145. message: t("login.message.password.required"),
  146. },
  147. {
  148. min: 6,
  149. message: t("login.message.password.min"),
  150. trigger: "blur",
  151. },
  152. ],
  153. orgId: [
  154. {
  155. required: true,
  156. trigger: "blur",
  157. message: t("login.message.orgId.required"),
  158. },
  159. ],
  160. };
  161. });
  162. /**
  163. * 获取验证码
  164. */
  165. function getCaptcha() {
  166. getCaptchaApi().then(({ data }) => {
  167. loginData.value.captchaKey = data.captchaKey;
  168. captchaBase64.value = data.captchaBase64;
  169. });
  170. }
  171. function getOrgList() {
  172. getOrgListApi().then((data: any) => {
  173. orgList.value = data.data;
  174. if (orgList.value) {
  175. loginData.value.orgId = orgList.value[0].id;
  176. }
  177. });
  178. }
  179. /**
  180. * 登录
  181. */
  182. const route = useRoute();
  183. const router = useRouter();
  184. function handleLogin() {
  185. loginFormRef.value.validate((valid: boolean) => {
  186. if (valid) {
  187. loading.value = true;
  188. userStore
  189. .login(loginData.value)
  190. .then(async (res) => {
  191. const query: LocationQuery = route?.query;
  192. const redirect = (query?.redirect as LocationQueryValue) ?? "/";
  193. const otherQueryParams = Object.keys(query).reduce(
  194. (acc: any, cur: string) => {
  195. if (cur !== "redirect") {
  196. acc[cur] = query[cur];
  197. }
  198. return acc;
  199. },
  200. {}
  201. );
  202. router.push({
  203. path: redirect,
  204. query: { ...otherQueryParams, date: new Date() },
  205. });
  206. })
  207. // .catch(() => {
  208. // // getCaptcha();
  209. // console.log("catch");
  210. // })
  211. .finally(() => {
  212. loading.value = false;
  213. });
  214. }
  215. });
  216. }
  217. //自动登录
  218. const toLogin = () => {
  219. loading.value = true;
  220. userStore
  221. .login(loginData.value)
  222. .then(async () => {
  223. const query: LocationQuery = route?.query;
  224. const redirect = (query?.redirect as LocationQueryValue) ?? "/";
  225. const otherQueryParams = Object.keys(query).reduce(
  226. (acc: any, cur: string) => {
  227. if (cur !== "redirect") {
  228. acc[cur] = query[cur];
  229. }
  230. return acc;
  231. },
  232. {}
  233. );
  234. router.push({
  235. path: redirect,
  236. query: { ...otherQueryParams, date: new Date() },
  237. });
  238. })
  239. .catch(() => {
  240. // getCaptcha();
  241. console.log("catch");
  242. })
  243. .finally(() => {
  244. loading.value = false;
  245. });
  246. };
  247. /**
  248. * 主题切换
  249. */
  250. const toggleTheme = () => {
  251. //const newTheme = settingsStore.theme === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
  252. //settingsStore.changeTheme(newTheme);
  253. };
  254. /**
  255. * 根据屏幕宽度切换设备模式
  256. */
  257. watchEffect?.(() => {
  258. if (height.value < 600) {
  259. icpVisible.value = false;
  260. } else {
  261. icpVisible.value = true;
  262. }
  263. });
  264. /**
  265. * 检查输入大小写
  266. */
  267. function checkCapslock(e: any) {
  268. //isCapslock.value = e.getModifierState("CapsLock");
  269. }
  270. onMounted?.(() => {
  271. // 处理SSO
  272. const query: LocationQuery = route.query;
  273. if (query.token) {
  274. loginData.value.token = query.token + "";
  275. toLogin();
  276. }
  277. toggleTheme();
  278. });
  279. </script>
  280. <style lang="scss" scoped>
  281. html.dark .login-container {
  282. background: url("@/assets/images/login-bg-dark.jpg") no-repeat center right;
  283. }
  284. .login-container {
  285. overflow-y: auto;
  286. background: url("@/assets/images/login-bg.jpg") no-repeat center right;
  287. @apply wh-full flex-center;
  288. .login-form {
  289. padding: 30px 10px;
  290. }
  291. }
  292. .el-form-item {
  293. background: var(--el-input-bg-color);
  294. border: 1px solid var(--el-border-color);
  295. border-radius: 5px;
  296. }
  297. :deep(.el-input) {
  298. .el-input__wrapper {
  299. padding: 0;
  300. background-color: transparent;
  301. box-shadow: none;
  302. &.is-focus,
  303. &:hover {
  304. box-shadow: none !important;
  305. }
  306. input:-webkit-autofill {
  307. /* 通过延时渲染背景色变相去除背景颜色 */
  308. transition: background-color 1000s ease-in-out 0s;
  309. }
  310. }
  311. }
  312. :deep(.el-select) {
  313. .el-select__wrapper {
  314. padding: 0;
  315. background-color: transparent;
  316. box-shadow: none;
  317. &.is-focus,
  318. &:hover {
  319. box-shadow: none !important;
  320. }
  321. input:-webkit-autofill {
  322. /* 通过延时渲染背景色变相去除背景颜色 */
  323. transition: background-color 1000s ease-in-out 0s;
  324. }
  325. }
  326. }
  327. </style>