login.vue 7.8 KB

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