login.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <template>
  2. <div class="min-h-screen relative flex items-center justify-center font-sans">
  3. <img
  4. :src="bgImage"
  5. alt="background"
  6. class="absolute inset-0 w-full h-full object-cover"
  7. />
  8. <div class="absolute inset-0 bg-black/10"></div>
  9. <div class="absolute top-6 left-6 z-10 flex items-center gap-3">
  10. <a href="/" class="flex justify-center gap-2">
  11. <img :src="logo" alt="logo" class="h-9" />
  12. <span class="text-white text-lg font-semibold mt-1"
  13. >DeepOil {{ $t("nav.title") }}</span
  14. >
  15. </a>
  16. </div>
  17. <div class="relative z-10 w-full max-w-[420px] px-6">
  18. <div class="bg-white/95 backdrop-blur-sm rounded-lg p-8 shadow-2xl">
  19. <h1 class="text-2xl font-bold text-center text-black/90">
  20. {{ $t("common.login") }}
  21. </h1>
  22. <!-- 用户名密码登陆 -->
  23. <div>
  24. <el-form
  25. :model="form"
  26. :rules="rules"
  27. ref="formRef"
  28. label-position="top"
  29. >
  30. <el-form-item prop="username">
  31. <el-input v-model="form.username" placeholder="请输入用户名" />
  32. </el-form-item>
  33. <el-form-item prop="password">
  34. <el-input
  35. v-model="form.password"
  36. type="password"
  37. placeholder="请输入密码"
  38. show-password
  39. />
  40. </el-form-item>
  41. <div class="flex items-center justify-between mb-3">
  42. <el-checkbox v-model="form.remember">记住我</el-checkbox>
  43. <a class="text-[#409EFF] text-sm cursor-pointer">忘记密码?</a>
  44. </div>
  45. </el-form>
  46. <div class="flex flex-col justify-center items-center">
  47. <el-button
  48. type="primary"
  49. class="w-full"
  50. :loading="loading"
  51. @click="onSubmit"
  52. >登录</el-button
  53. >
  54. <div class="w-full mt-3">
  55. <el-button class="w-full" @click="qrLogin"
  56. >钉钉扫码登录</el-button
  57. >
  58. </div>
  59. </div>
  60. </div>
  61. <!-- 钉钉登陆 -->
  62. <div class="text-center">
  63. <div id="login_container" class="pb-2"></div>
  64. </div>
  65. </div>
  66. </div>
  67. </div>
  68. </template>
  69. <script lang="ts" setup>
  70. import { nextTick, onMounted, reactive, ref } from "vue";
  71. import { ElMessage } from "element-plus";
  72. import logo from "@/assets/images/logo.png";
  73. import bgImage from "@/assets/images/bg.png";
  74. import { login } from "@/api/user";
  75. import * as authUtil from "@/utils/auth";
  76. import { manualLogoutKey, reloginCancelKey } from "@/config/axios/service";
  77. type LoginForm = {
  78. username: string;
  79. password: string;
  80. remember: boolean;
  81. };
  82. const form = reactive<LoginForm>({
  83. username: "",
  84. password: "",
  85. remember: false,
  86. });
  87. const formRef = ref();
  88. const loading = ref(false);
  89. const rules = {
  90. username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
  91. password: [{ required: true, message: "请输入密码", trigger: "blur" }],
  92. };
  93. const onSubmit = async () => {
  94. await formRef.value?.validate(async (valid: boolean) => {
  95. if (!valid) return;
  96. loading.value = true;
  97. try {
  98. const res = await login({
  99. username: form.username,
  100. password: form.password,
  101. captchaVerification: "",
  102. });
  103. authUtil.setToken(res);
  104. sessionStorage.removeItem(manualLogoutKey);
  105. sessionStorage.removeItem(reloginCancelKey);
  106. if (form.remember) {
  107. authUtil.setLoginForm(form);
  108. } else {
  109. authUtil.removeLoginForm();
  110. }
  111. window.location.href = "/";
  112. ElMessage.success("登录成功");
  113. } finally {
  114. loading.value = false;
  115. }
  116. });
  117. };
  118. // QR 登录相关状态与方法
  119. const showQrDialog = ref(false);
  120. const showQrOnly = ref(false);
  121. const _getRandomString = (len: number) => {
  122. len = len || 10;
  123. let $chars = "ABCDEFGHIJKMNOPQRSTUVWXYZ";
  124. let maxPos = $chars.length;
  125. let pwd = "";
  126. for (let i = 0; i < len; i++) {
  127. pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
  128. }
  129. return pwd;
  130. };
  131. const handleMsg = (state?: string) => {
  132. return async function (event: MessageEvent) {
  133. if (event.origin === "https://login.dingtalk.com") {
  134. let loginTmpCode = event.data;
  135. console.log("收到钉钉扫码登录消息:", loginTmpCode);
  136. window.location.href =
  137. "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=" +
  138. import.meta.env.VITE_DINGTALK_APP_ID +
  139. "&response_type=code&scope=snsapi_login&state=" +
  140. state +
  141. "&redirect_uri=" +
  142. import.meta.env.VITE_DINGTALK_REDIRECT_URI +
  143. "&loginTmpCode=" +
  144. loginTmpCode;
  145. // const res = await socialLogin("20", loginTmpCode as string, "22");
  146. // console.log("登录结果:", res);
  147. // authUtil.setToken(res);
  148. }
  149. };
  150. };
  151. const initDingLogin = () => {
  152. let state = _getRandomString(10);
  153. const gotoUrl = encodeURIComponent(
  154. "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?" +
  155. "appid=" +
  156. import.meta.env.VITE_DINGTALK_APP_ID +
  157. "&response_type=code" +
  158. "&scope=snsapi_login" +
  159. "&state=" +
  160. state +
  161. "&redirect_uri=" +
  162. import.meta.env.VITE_DINGTALK_REDIRECT_URI,
  163. );
  164. nextTick(() => {
  165. window.DDLogin({
  166. id: "login_container",
  167. goto: gotoUrl,
  168. style: "border:none;background-color:transparent;",
  169. width: "100%",
  170. height: "290",
  171. });
  172. // 重置扫码登录框的样式,让登录框居中
  173. const box = document.getElementById("login_container");
  174. if (box) {
  175. const iframe = box.querySelector("iframe");
  176. if (iframe) {
  177. iframe.style.top = "0";
  178. iframe.style.bottom = "0";
  179. iframe.style.left = "0";
  180. iframe.style.right = "0";
  181. iframe.style.margin = "auto";
  182. }
  183. }
  184. });
  185. window.addEventListener("message", handleMsg(state), false);
  186. };
  187. const qrLogin = () => {
  188. showQrOnly.value = true;
  189. initDingLogin();
  190. };
  191. const backToPasswordLogin = () => {
  192. showQrOnly.value = false;
  193. // 卸载钉钉扫码登录组件
  194. let box = document.getElementById("login_container");
  195. if (box) {
  196. box.innerHTML = "";
  197. }
  198. // 移除监听事件
  199. window.removeEventListener("message", handleMsg());
  200. };
  201. onMounted(() => {
  202. qrLogin();
  203. });
  204. </script>
  205. <style scoped>
  206. .qr-dialog {
  207. max-height: none;
  208. }
  209. .qr-dialog .el-dialog__body {
  210. overflow: visible;
  211. padding: 20px 32px;
  212. display: flex;
  213. flex-direction: column;
  214. align-items: center;
  215. justify-content: center;
  216. }
  217. .qr-dialog .el-dialog__footer {
  218. display: flex;
  219. justify-content: center;
  220. gap: 12px;
  221. padding: 12px 24px 20px;
  222. }
  223. .qr-dialog img {
  224. max-width: 100%;
  225. height: auto;
  226. display: block;
  227. }
  228. /* 在非常小的视窗内减小二维码尺寸并防止滚动 */
  229. @media (max-width: 420px) {
  230. .qr-dialog .el-dialog {
  231. width: calc(100% - 32px) !important;
  232. }
  233. .qr-dialog img {
  234. width: 160px;
  235. height: 160px;
  236. }
  237. }
  238. /* 确保扫码登录容器居中 */
  239. #login_container {
  240. display: flex;
  241. justify-content: center;
  242. align-items: center;
  243. }
  244. /* 确保 iframe 正确居中 */
  245. #login_container iframe {
  246. position: static !important;
  247. margin: 0 auto !important;
  248. }
  249. /* 增强毛玻璃效果 */
  250. .bg-white\/95 {
  251. background: rgba(255, 255, 255, 0.85) !important;
  252. backdrop-filter: blur(10px) !important;
  253. -webkit-backdrop-filter: blur(10px) !important;
  254. border: 1px solid rgba(255, 255, 255, 0.18);
  255. box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
  256. }
  257. /* 兼容性处理 */
  258. @supports not (backdrop-filter: blur(10px)) {
  259. .bg-white\/95 {
  260. background: rgba(255, 255, 255, 0.95) !important;
  261. }
  262. }
  263. /* 确保扫码登录容器居中 */
  264. #login_container {
  265. display: flex;
  266. justify-content: center;
  267. align-items: center;
  268. }
  269. /* 确保 iframe 正确居中 */
  270. #login_container iframe {
  271. position: static !important;
  272. margin: 0 auto !important;
  273. }
  274. </style>