|
@@ -1,6 +1,5 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="min-h-screen relative flex items-center justify-center">
|
|
<div class="min-h-screen relative flex items-center justify-center">
|
|
|
- <!-- Background image -->
|
|
|
|
|
<img
|
|
<img
|
|
|
:src="bgImage"
|
|
:src="bgImage"
|
|
|
alt="background"
|
|
alt="background"
|
|
@@ -8,60 +7,80 @@
|
|
|
/>
|
|
/>
|
|
|
<div class="absolute inset-0 bg-black/10"></div>
|
|
<div class="absolute inset-0 bg-black/10"></div>
|
|
|
|
|
|
|
|
- <!-- Logo and title at top -->
|
|
|
|
|
<div class="absolute top-6 left-6 z-10 flex items-center gap-3">
|
|
<div class="absolute top-6 left-6 z-10 flex items-center gap-3">
|
|
|
- <img :src="logo" alt="logo" class="h-9" />
|
|
|
|
|
- <span class="text-white text-lg font-semibold">智慧经营平台</span>
|
|
|
|
|
|
|
+ <a href="/" class="flex justify-center gap-2">
|
|
|
|
|
+ <img :src="logo" alt="logo" class="h-9" />
|
|
|
|
|
+ <span class="text-white text-lg font-semibold mt-1"
|
|
|
|
|
+ >DeepOil智慧经营平台</span
|
|
|
|
|
+ >
|
|
|
|
|
+ </a>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- Login form centered -->
|
|
|
|
|
<div class="relative z-10 w-full max-w-[420px] px-6">
|
|
<div class="relative z-10 w-full max-w-[420px] px-6">
|
|
|
<div class="bg-white/95 backdrop-blur-sm rounded-lg p-8 shadow-2xl">
|
|
<div class="bg-white/95 backdrop-blur-sm rounded-lg p-8 shadow-2xl">
|
|
|
<h1 class="text-2xl font-bold text-center mb-8">登录</h1>
|
|
<h1 class="text-2xl font-bold text-center mb-8">登录</h1>
|
|
|
|
|
|
|
|
- <div id="login_container" style="width: 200px; height: 200px;"></div>
|
|
|
|
|
- <el-form
|
|
|
|
|
- :model="form"
|
|
|
|
|
- :rules="rules"
|
|
|
|
|
- ref="formRef"
|
|
|
|
|
- label-position="top"
|
|
|
|
|
- >
|
|
|
|
|
- <el-form-item prop="username">
|
|
|
|
|
- <el-input v-model="form.username" placeholder="请输入用户名" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-form-item prop="password">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="form.password"
|
|
|
|
|
- type="password"
|
|
|
|
|
- placeholder="请输入密码"
|
|
|
|
|
- show-password
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <div class="flex items-center justify-between mb-3">
|
|
|
|
|
- <el-checkbox v-model="form.remember">记住我</el-checkbox>
|
|
|
|
|
- <a class="text-[#409EFF] text-sm cursor-pointer">忘记密码?</a>
|
|
|
|
|
|
|
+ <!-- 用户名密码登陆 -->
|
|
|
|
|
+ <div v-if="!showQrDialog && !showQrOnly">
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ :model="form"
|
|
|
|
|
+ :rules="rules"
|
|
|
|
|
+ ref="formRef"
|
|
|
|
|
+ label-position="top"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item prop="username">
|
|
|
|
|
+ <el-input v-model="form.username" placeholder="请输入用户名" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item prop="password">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.password"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ placeholder="请输入密码"
|
|
|
|
|
+ show-password
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="flex items-center justify-between mb-3">
|
|
|
|
|
+ <el-checkbox v-model="form.remember">记住我</el-checkbox>
|
|
|
|
|
+ <a class="text-[#409EFF] text-sm cursor-pointer">忘记密码?</a>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="flex flex-col justify-center items-center">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ class="w-full"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ @click="onSubmit"
|
|
|
|
|
+ >登录</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+
|
|
|
|
|
+ <div class="w-full mt-3">
|
|
|
|
|
+ <el-button class="w-full" @click="qrLogin"
|
|
|
|
|
+ >钉钉扫码登录</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <el-button
|
|
|
|
|
- type="primary"
|
|
|
|
|
- class="w-full"
|
|
|
|
|
- :loading="loading"
|
|
|
|
|
- @click="onSubmit"
|
|
|
|
|
- >登录</el-button
|
|
|
|
|
- >
|
|
|
|
|
- <el-button class="w-full mt-3" @click="qrLogin"
|
|
|
|
|
- >钉钉扫码登录</el-button
|
|
|
|
|
- >
|
|
|
|
|
- </el-form>
|
|
|
|
|
|
|
+ <!-- 钉钉登陆 -->
|
|
|
|
|
+ <div v-show="showQrOnly" class="text-center">
|
|
|
|
|
+ <div id="login_container" class="pb-2"></div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="flex gap-3 justify-center mt-4">
|
|
|
|
|
+ <el-button class="w-full" @click="backToPasswordLogin"
|
|
|
|
|
+ >返回用户名密码登录</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
|
-import { reactive, ref } from "vue";
|
|
|
|
|
|
|
+import { nextTick, onMounted, reactive, ref } from "vue";
|
|
|
import { ElMessage } from "element-plus";
|
|
import { ElMessage } from "element-plus";
|
|
|
import logo from "@/assets/images/logo.png";
|
|
import logo from "@/assets/images/logo.png";
|
|
|
import bgImage from "@/assets/images/bg.png";
|
|
import bgImage from "@/assets/images/bg.png";
|
|
@@ -99,7 +118,197 @@ const onSubmit = async () => {
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+// QR 登录相关状态与方法
|
|
|
|
|
+const showQrDialog = ref(false);
|
|
|
|
|
+const showQrOnly = ref(false);
|
|
|
|
|
+
|
|
|
|
|
+const _getRandomString = (len: number) => {
|
|
|
|
|
+ len = len || 10;
|
|
|
|
|
+ let $chars = "ABCDEFGHIJKMNOPQRSTUVWXYZ";
|
|
|
|
|
+ let maxPos = $chars.length;
|
|
|
|
|
+ let pwd = "";
|
|
|
|
|
+ for (let i = 0; i < len; i++) {
|
|
|
|
|
+ pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
|
|
|
|
|
+ }
|
|
|
|
|
+ return pwd;
|
|
|
|
|
+};
|
|
|
|
|
+const initDingLogin = () => {
|
|
|
|
|
+ let state = _getRandomString(10);
|
|
|
|
|
+ const gotoUrl = encodeURIComponent(
|
|
|
|
|
+ "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?" +
|
|
|
|
|
+ "appid=" +
|
|
|
|
|
+ import.meta.env.VITE_DINGTALK_APP_ID +
|
|
|
|
|
+ "&response_type=code" +
|
|
|
|
|
+ "&scope=snsapi_login" +
|
|
|
|
|
+ "&state=" +
|
|
|
|
|
+ state +
|
|
|
|
|
+ "&redirect_uri=" +
|
|
|
|
|
+ import.meta.env.VITE_DINGTALK_REDIRECT_URI
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ window.DDLogin({
|
|
|
|
|
+ id: "login_container",
|
|
|
|
|
+ goto: gotoUrl,
|
|
|
|
|
+ style: "border:none;background-color:#FFFFFF;",
|
|
|
|
|
+ width: "100%",
|
|
|
|
|
+ height: "290",
|
|
|
|
|
+ });
|
|
|
|
|
+ // 重置扫码登录框的样式,让登录框居中
|
|
|
|
|
+ const box = document.getElementById("login_container");
|
|
|
|
|
+ if (box) {
|
|
|
|
|
+ const iframe = box.querySelector("iframe");
|
|
|
|
|
+ if (iframe) {
|
|
|
|
|
+ iframe.style.top = "0";
|
|
|
|
|
+ iframe.style.bottom = "0";
|
|
|
|
|
+ iframe.style.left = "0";
|
|
|
|
|
+ iframe.style.right = "0";
|
|
|
|
|
+ iframe.style.margin = "auto";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const handleMessage = function (event: MessageEvent) {
|
|
|
|
|
+ if (event.origin === "https://login.dingtalk.com") {
|
|
|
|
|
+ let loginTmpCode = event.data;
|
|
|
|
|
+ window.location.href =
|
|
|
|
|
+ "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=" +
|
|
|
|
|
+ import.meta.env.VITE_DINGTALK_APP_ID +
|
|
|
|
|
+ "&response_type=code&scope=snsapi_login&state=" +
|
|
|
|
|
+ state +
|
|
|
|
|
+ "&redirect_uri=" +
|
|
|
|
|
+ import.meta.env.VITE_DINGTALK_REDIRECT_URI +
|
|
|
|
|
+ "&loginTmpCode=" +
|
|
|
|
|
+ loginTmpCode;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ window.addEventListener("message", handleMessage, false);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// const handleDirectLogin = async (loginTmpCode: string): Promise<void> => {
|
|
|
|
|
+// try {
|
|
|
|
|
+// // 调用后端接口,让后端处理钉钉认证
|
|
|
|
|
+// const response = await fetch("/api/auth/dingtalk/login", {
|
|
|
|
|
+// method: "POST",
|
|
|
|
|
+// headers: {
|
|
|
|
|
+// "Content-Type": "application/json",
|
|
|
|
|
+// },
|
|
|
|
|
+// body: JSON.stringify({
|
|
|
|
|
+// loginTmpCode,
|
|
|
|
|
+// state: currentState,
|
|
|
|
|
+// }),
|
|
|
|
|
+// });
|
|
|
|
|
+
|
|
|
|
|
+// if (!response.ok) {
|
|
|
|
|
+// throw new Error("登录请求失败");
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const result = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+// if (result.success) {
|
|
|
|
|
+// // 登录成功,处理返回的 token 和用户信息
|
|
|
|
|
+// onLoginSuccess(result.data);
|
|
|
|
|
+// } else {
|
|
|
|
|
+// onLoginFailed(result.message || "登录失败");
|
|
|
|
|
+// }
|
|
|
|
|
+// } catch (error) {
|
|
|
|
|
+// console.error("钉钉登录失败:", error);
|
|
|
|
|
+
|
|
|
|
|
+// }
|
|
|
|
|
+// };
|
|
|
|
|
+
|
|
|
const qrLogin = () => {
|
|
const qrLogin = () => {
|
|
|
-
|
|
|
|
|
|
|
+ showQrOnly.value = true;
|
|
|
|
|
+ // initDingLogin();
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
|
|
+const backToPasswordLogin = () => {
|
|
|
|
|
+ showQrOnly.value = false;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ initDingLogin();
|
|
|
|
|
+});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.qr-dialog {
|
|
|
|
|
+ max-height: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qr-dialog .el-dialog__body {
|
|
|
|
|
+ overflow: visible;
|
|
|
|
|
+ padding: 20px 32px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qr-dialog .el-dialog__footer {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 12px 24px 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.qr-dialog img {
|
|
|
|
|
+ max-width: 100%;
|
|
|
|
|
+ height: auto;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 在非常小的视窗内减小二维码尺寸并防止滚动 */
|
|
|
|
|
+@media (max-width: 420px) {
|
|
|
|
|
+ .qr-dialog .el-dialog {
|
|
|
|
|
+ width: calc(100% - 32px) !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ .qr-dialog img {
|
|
|
|
|
+ width: 160px;
|
|
|
|
|
+ height: 160px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保扫码登录容器居中 */
|
|
|
|
|
+#login_container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保 iframe 正确居中 */
|
|
|
|
|
+#login_container iframe {
|
|
|
|
|
+ position: static !important;
|
|
|
|
|
+ margin: 0 auto !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 增强毛玻璃效果 */
|
|
|
|
|
+.bg-white\/95 {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.85) !important;
|
|
|
|
|
+ backdrop-filter: blur(10px) !important;
|
|
|
|
|
+ -webkit-backdrop-filter: blur(10px) !important;
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.18);
|
|
|
|
|
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 兼容性处理 */
|
|
|
|
|
+@supports not (backdrop-filter: blur(10px)) {
|
|
|
|
|
+ .bg-white\/95 {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.95) !important;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保扫码登录容器居中 */
|
|
|
|
|
+#login_container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 确保 iframe 正确居中 */
|
|
|
|
|
+#login_container iframe {
|
|
|
|
|
+ position: static !important;
|
|
|
|
|
+ margin: 0 auto !important;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|