|
@@ -9,33 +9,17 @@
|
|
label-width="120px"
|
|
label-width="120px"
|
|
size="large"
|
|
size="large"
|
|
>
|
|
>
|
|
- <el-row style="margin-right: -10px; margin-left: -10px">
|
|
|
|
- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
|
|
|
- <el-form-item>
|
|
|
|
- <LoginFormTitle style="width: 100%" />
|
|
|
|
- </el-form-item>
|
|
|
|
- </el-col>
|
|
|
|
- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
|
|
|
- <el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
|
|
|
- <el-input
|
|
|
|
- v-model="loginData.loginForm.tenantName"
|
|
|
|
- :placeholder="t('login.tenantNamePlaceholder')"
|
|
|
|
- :prefix-icon="iconHouse"
|
|
|
|
- link
|
|
|
|
- type="primary"
|
|
|
|
- />
|
|
|
|
|
|
+ <el-row style="display: flex;justify-content: center">
|
|
|
|
+ <el-image v-if="loginType === 'uname'" :src="imgsaoma" class="clickable-image" alt="扫码登录" title="扫码登录" @click="changeLogin('dingding')"/>
|
|
|
|
+ <el-image v-if="loginType === 'dingding'" :src="imgreturn" class="clickable-image" alt="返回" title="返回" @click="changeLogin('uname')"/>
|
|
|
|
+ <el-image :src="img" class=" h-100px" style="margin-top: 16%"/>
|
|
|
|
+ <div v-if="loginType === 'uname'" style="width: 90%">
|
|
|
|
+ <el-col :span="24" style="padding-right: 10px; padding-left: 10px; margin-top: 85px">
|
|
|
|
+ <el-form-item prop="username" >
|
|
|
|
+ <el-input v-model="loginData.loginForm.username" :placeholder="t('login.usernamePlaceholder')" type="text" auto-complete="off" :prefix-icon="iconAvatar"/>
|
|
</el-form-item>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-col>
|
|
- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
|
|
|
- <el-form-item prop="username">
|
|
|
|
- <el-input
|
|
|
|
- v-model="loginData.loginForm.username"
|
|
|
|
- :placeholder="t('login.usernamePlaceholder')"
|
|
|
|
- :prefix-icon="iconAvatar"
|
|
|
|
- />
|
|
|
|
- </el-form-item>
|
|
|
|
- </el-col>
|
|
|
|
- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
|
|
|
|
|
+ <el-col :span="24" style="padding-right: 10px; padding-left: 10px;margin-top: 15px">
|
|
<el-form-item prop="password">
|
|
<el-form-item prop="password">
|
|
<el-input
|
|
<el-input
|
|
v-model="loginData.loginForm.password"
|
|
v-model="loginData.loginForm.password"
|
|
@@ -49,10 +33,10 @@
|
|
</el-col>
|
|
</el-col>
|
|
<el-col
|
|
<el-col
|
|
:span="24"
|
|
:span="24"
|
|
- style="padding-right: 10px; padding-left: 10px; margin-top: -20px; margin-bottom: -20px"
|
|
|
|
|
|
+ style="padding-right: 10px; padding-left: 10px; margin-top: 20px;"
|
|
>
|
|
>
|
|
<el-form-item>
|
|
<el-form-item>
|
|
- <el-row justify="space-between" style="width: 100%">
|
|
|
|
|
|
+ <el-row justify="space-between" style="width: 100%;">
|
|
<el-col :span="6">
|
|
<el-col :span="6">
|
|
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
|
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
|
{{ t('login.remember') }}
|
|
{{ t('login.remember') }}
|
|
@@ -70,9 +54,10 @@
|
|
</el-row>
|
|
</el-row>
|
|
</el-form-item>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-col>
|
|
- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
|
|
|
|
|
+ <el-col :span="24" style="padding-right: 10px; padding-left: 10px;margin-bottom: 10px">
|
|
<el-form-item>
|
|
<el-form-item>
|
|
<XButton
|
|
<XButton
|
|
|
|
+ style="height: 4em;background-color: rgb(11, 26, 50);border: none;opacity: 1;color: rgb(186, 205, 224)"
|
|
:loading="loginLoading"
|
|
:loading="loginLoading"
|
|
:title="t('login.login')"
|
|
:title="t('login.login')"
|
|
class="w-[100%]"
|
|
class="w-[100%]"
|
|
@@ -89,72 +74,18 @@
|
|
mode="pop"
|
|
mode="pop"
|
|
@success="handleLogin"
|
|
@success="handleLogin"
|
|
/>
|
|
/>
|
|
- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
|
|
|
- <el-form-item>
|
|
|
|
- <el-row :gutter="5" justify="space-between" style="width: 100%">
|
|
|
|
- <el-col :span="8">
|
|
|
|
- <XButton
|
|
|
|
- :title="t('login.btnMobile')"
|
|
|
|
- class="w-[100%]"
|
|
|
|
- @click="setLoginState(LoginStateEnum.MOBILE)"
|
|
|
|
- />
|
|
|
|
- </el-col>
|
|
|
|
- <el-col :span="8">
|
|
|
|
- <XButton
|
|
|
|
- :title="t('login.btnQRCode')"
|
|
|
|
- class="w-[100%]"
|
|
|
|
- @click="setLoginState(LoginStateEnum.QR_CODE)"
|
|
|
|
- />
|
|
|
|
- </el-col>
|
|
|
|
- <el-col :span="8">
|
|
|
|
- <XButton
|
|
|
|
- :title="t('login.btnRegister')"
|
|
|
|
- class="w-[100%]"
|
|
|
|
- @click="setLoginState(LoginStateEnum.REGISTER)"
|
|
|
|
- />
|
|
|
|
- </el-col>
|
|
|
|
- </el-row>
|
|
|
|
- </el-form-item>
|
|
|
|
- </el-col>
|
|
|
|
- <el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
|
|
|
- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
|
|
|
- <el-form-item>
|
|
|
|
- <div class="w-[100%] flex justify-between">
|
|
|
|
- <Icon
|
|
|
|
- v-for="(item, key) in socialList"
|
|
|
|
- :key="key"
|
|
|
|
- :icon="item.icon"
|
|
|
|
- :size="30"
|
|
|
|
- class="anticon cursor-pointer"
|
|
|
|
- color="#999"
|
|
|
|
- @click="doSocialLogin(item.type)"
|
|
|
|
- />
|
|
|
|
- </div>
|
|
|
|
- </el-form-item>
|
|
|
|
- </el-col>
|
|
|
|
- <el-divider content-position="center">萌新必读</el-divider>
|
|
|
|
- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
|
|
|
- <el-form-item>
|
|
|
|
- <div class="w-[100%] flex justify-between">
|
|
|
|
- <el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
|
|
|
|
- <el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link>
|
|
|
|
- <el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank">
|
|
|
|
- ⚡面试手册
|
|
|
|
- </el-link>
|
|
|
|
- <el-link href="http://static.yudao.iocoder.cn/mp/Aix9975.jpeg" target="_blank">
|
|
|
|
- 🤝外包咨询
|
|
|
|
- </el-link>
|
|
|
|
- </div>
|
|
|
|
- </el-form-item>
|
|
|
|
- </el-col>
|
|
|
|
|
|
+ </div>
|
|
|
|
+ <div v-if="loginType === 'dingding'" id="login_container" style="margin-left: 8%;margin-top: 18%">
|
|
|
|
+ </div>
|
|
</el-row>
|
|
</el-row>
|
|
</el-form>
|
|
</el-form>
|
|
</template>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
|
|
+import img from '@/assets/imgs/img.png'
|
|
|
|
+import imgsaoma from '@/assets/imgs/saoma.png'
|
|
|
|
+import imgreturn from '@/assets/imgs/return.png'
|
|
import { ElLoading } from 'element-plus'
|
|
import { ElLoading } from 'element-plus'
|
|
-import LoginFormTitle from './LoginFormTitle.vue'
|
|
|
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|
-
|
|
|
|
import { useIcon } from '@/hooks/web/useIcon'
|
|
import { useIcon } from '@/hooks/web/useIcon'
|
|
|
|
|
|
import * as authUtil from '@/utils/auth'
|
|
import * as authUtil from '@/utils/auth'
|
|
@@ -162,13 +93,94 @@ import { usePermissionStore } from '@/store/modules/permission'
|
|
import * as LoginApi from '@/api/login'
|
|
import * as LoginApi from '@/api/login'
|
|
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
|
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
|
|
|
|
|
|
|
+import { ref,nextTick } from 'vue';
|
|
|
|
+const appId = 'dingik345qmyhtysvs2x'
|
|
|
|
+const dingRedirectUrl = 'http://1.94.244.160:81/login?loginType=dingding&type=20'
|
|
|
|
+
|
|
|
|
+const loginType = ref('uname');
|
|
|
|
+const changeLogin = (type) => {
|
|
|
|
+ loginType.value = type;
|
|
|
|
+ if (type === 'dingding') {
|
|
|
|
+ initDingLogin();
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+const initDingLogin=()=> {
|
|
|
|
+ // if (window.DDLogin) {
|
|
|
|
+ var url = dingRedirectUrl
|
|
|
|
+ var state = _getRandomString(10);
|
|
|
|
+ var gotoUrl = encodeURIComponent(
|
|
|
|
+ "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?" +
|
|
|
|
+ "appid=" + appId +
|
|
|
|
+ "&response_type=code" +
|
|
|
|
+ "&scope=snsapi_login" +
|
|
|
|
+ "&state=" + state +
|
|
|
|
+ "&redirect_uri=" + url
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ var obj = window.DDLogin({
|
|
|
|
+ id: "login_container",
|
|
|
|
+ goto: gotoUrl,
|
|
|
|
+ style: "border:none;background-color:#FFFFFF;",
|
|
|
|
+ width: '90%', // 二维码的宽度
|
|
|
|
+ height: "290", // 二维码的高度
|
|
|
|
+ })
|
|
|
|
+ // 重置扫码登录框的样式,让登录框居中
|
|
|
|
+ let box = document.getElementById('login_container')
|
|
|
|
+ box.querySelector('iframe').style.top = '0'
|
|
|
|
+ box.querySelector('iframe').style.bottom = '0'
|
|
|
|
+ box.querySelector('iframe').style.left = '0'
|
|
|
|
+ box.querySelector('iframe').style.right = '0'
|
|
|
|
+ box.querySelector('iframe').style.margin = 'auto'
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ /* new window.DDLogin({
|
|
|
|
+ id: "login_container",
|
|
|
|
+ goto: gotoUrl,
|
|
|
|
+ style: "border:none;background-color:#FFFFFF;",
|
|
|
|
+ width: "350",
|
|
|
|
+ height: "350",
|
|
|
|
+ }); */
|
|
|
|
+ var handleMessage = function (event) {
|
|
|
|
+ var origin = event.origin;
|
|
|
|
+ if (origin === "https://login.dingtalk.com") {
|
|
|
|
+ var loginTmpCode = event.data;
|
|
|
|
+ console.log(loginTmpCode)
|
|
|
|
+ window.location.href =
|
|
|
|
+ "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=dingik345qmyhtysvs2x&response_type=code&scope=snsapi_login&state=" +
|
|
|
|
+ state +
|
|
|
|
+ "&redirect_uri=" +
|
|
|
|
+ url +
|
|
|
|
+ "&loginTmpCode=" +
|
|
|
|
+ loginTmpCode;
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ if (typeof window.addEventListener != "undefined") {
|
|
|
|
+ window.addEventListener("message", handleMessage, false);
|
|
|
|
+ } else if (typeof window.attachEvent != "undefined") {
|
|
|
|
+ window.attachEvent("onmessage", handleMessage);
|
|
|
|
+ }
|
|
|
|
+ // }
|
|
|
|
+}
|
|
|
|
+const _getRandomString=(len)=> {
|
|
|
|
+ len = len || 10;
|
|
|
|
+ let $chars = "ABCDEFGHIJKMNOPQRSTUVWXYZ"; // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
|
|
|
|
+ let maxPos = $chars.length;
|
|
|
|
+ let pwd = "";
|
|
|
|
+ for (var i = 0; i < len; i++) {
|
|
|
|
+ pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
|
|
|
|
+ }
|
|
|
|
+ return pwd;
|
|
|
|
+}
|
|
defineOptions({ name: 'LoginForm' })
|
|
defineOptions({ name: 'LoginForm' })
|
|
|
|
|
|
const { t } = useI18n()
|
|
const { t } = useI18n()
|
|
const message = useMessage()
|
|
const message = useMessage()
|
|
const iconHouse = useIcon({ icon: 'ep:house' })
|
|
const iconHouse = useIcon({ icon: 'ep:house' })
|
|
-const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
|
|
|
-const iconLock = useIcon({ icon: 'ep:lock' })
|
|
|
|
|
|
+const iconAvatar = useIcon({ icon: 'ep:avatar',size:26 })
|
|
|
|
+const iconLock = useIcon({ icon: 'ep:lock',size:26 })
|
|
const formLogin = ref()
|
|
const formLogin = ref()
|
|
const { validForm } = useFormValid(formLogin)
|
|
const { validForm } = useFormValid(formLogin)
|
|
const { setLoginState, getLoginState } = useLoginState()
|
|
const { setLoginState, getLoginState } = useLoginState()
|
|
@@ -289,40 +301,6 @@ const handleLogin = async (params: any) => {
|
|
}
|
|
}
|
|
|
|
|
|
// 社交登录
|
|
// 社交登录
|
|
-const doSocialLogin = async (type: number) => {
|
|
|
|
- if (type === 0) {
|
|
|
|
- message.error('此方式未配置')
|
|
|
|
- } else {
|
|
|
|
- loginLoading.value = true
|
|
|
|
- if (loginData.tenantEnable === 'true') {
|
|
|
|
- // 尝试先通过 tenantName 获取租户
|
|
|
|
- await getTenantId()
|
|
|
|
- // 如果获取不到,则需要弹出提示,进行处理
|
|
|
|
- if (!authUtil.getTenantId()) {
|
|
|
|
- try {
|
|
|
|
- const data = await message.prompt('请输入租户名称', t('common.reminder'))
|
|
|
|
- if (data?.action !== 'confirm') throw 'cancel'
|
|
|
|
- const res = await LoginApi.getTenantIdByName(data.value)
|
|
|
|
- authUtil.setTenantId(res)
|
|
|
|
- } catch (error) {
|
|
|
|
- if (error === 'cancel') return
|
|
|
|
- } finally {
|
|
|
|
- loginLoading.value = false
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- // 计算 redirectUri
|
|
|
|
- // tricky: type、redirect需要先encode一次,否则钉钉回调会丢失。
|
|
|
|
- // 配合 Login/SocialLogin.vue#getUrlValue() 使用
|
|
|
|
- const redirectUri =
|
|
|
|
- location.origin +
|
|
|
|
- '/social-login?' +
|
|
|
|
- encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
|
|
|
|
-
|
|
|
|
- // 进行跳转
|
|
|
|
- window.location.href = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
watch(
|
|
watch(
|
|
() => currentRoute.value,
|
|
() => currentRoute.value,
|
|
(route: RouteLocationNormalizedLoaded) => {
|
|
(route: RouteLocationNormalizedLoaded) => {
|
|
@@ -358,4 +336,23 @@ onMounted(() => {
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+::v-deep .rqinput .el-input__inner {
|
|
|
|
+ background-color: transparent !important;
|
|
|
|
+ border: none !important;
|
|
|
|
+ border-bottom: 1px solid rgb(186, 205, 224) !important;
|
|
|
|
+ border-radius: 0;
|
|
|
|
+ height: 2.8em;
|
|
|
|
+ //left: 18%;
|
|
|
|
+ padding-left: 3em;
|
|
|
|
+ color: white;
|
|
|
|
+}
|
|
|
|
+.clickable-image {
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ position:absolute;
|
|
|
|
+ //top: 2%;
|
|
|
|
+ //right: 2%;
|
|
|
|
+ width: 6%;
|
|
|
|
+ margin-top: -9%;
|
|
|
|
+ margin-left: 111%;
|
|
|
|
+}
|
|
</style>
|
|
</style>
|