mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-10-31 10:18:43 +08:00 
			
		
		
		
	增加SSO单点登录
This commit is contained in:
		| @@ -1,16 +1,19 @@ | |||||||
| import request from '@/config/axios' | import request from '@/config/axios' | ||||||
| import { getRefreshToken } from '@/utils/auth' | import { getRefreshToken } from '@/utils/auth' | ||||||
| import type { UserLoginVO } from './types' | import type { UserLoginVO } from './types' | ||||||
|  | import { service } from '@/config/axios/service' | ||||||
|  |  | ||||||
| export interface CodeImgResult { | export interface CodeImgResult { | ||||||
|   captchaOnOff: boolean |   captchaOnOff: boolean | ||||||
|   img: string |   img: string | ||||||
|   uuid: string |   uuid: string | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface SmsCodeVO { | export interface SmsCodeVO { | ||||||
|   mobile: string |   mobile: string | ||||||
|   scene: number |   scene: number | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface SmsLoginVO { | export interface SmsLoginVO { | ||||||
|   mobile: string |   mobile: string | ||||||
|   code: string |   code: string | ||||||
| @@ -71,3 +74,44 @@ export const getCodeApi = (data) => { | |||||||
| export const reqCheckApi = (data) => { | export const reqCheckApi = (data) => { | ||||||
|   return request.postOriginal({ url: 'system/captcha/check', data }) |   return request.postOriginal({ url: 'system/captcha/check', data }) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ========== OAUTH 2.0 相关 ========== | ||||||
|  |  | ||||||
|  | export const getAuthorize = (clientId) => { | ||||||
|  |   return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function authorize( | ||||||
|  |   responseType: string, | ||||||
|  |   clientId: string, | ||||||
|  |   redirectUri: string, | ||||||
|  |   state: string, | ||||||
|  |   autoApprove: boolean, | ||||||
|  |   checkedScopes: any, | ||||||
|  |   uncheckedScopes: any | ||||||
|  | ) { | ||||||
|  |   // 构建 scopes | ||||||
|  |   const scopes = {} | ||||||
|  |   for (const scope of checkedScopes) { | ||||||
|  |     scopes[scope] = true | ||||||
|  |   } | ||||||
|  |   for (const scope of uncheckedScopes) { | ||||||
|  |     scopes[scope] = false | ||||||
|  |   } | ||||||
|  |   // 发起请求 | ||||||
|  |   return service({ | ||||||
|  |     url: '/system/oauth2/authorize', | ||||||
|  |     headers: { | ||||||
|  |       'Content-type': 'application/x-www-form-urlencoded' | ||||||
|  |     }, | ||||||
|  |     params: { | ||||||
|  |       response_type: responseType, | ||||||
|  |       client_id: clientId, | ||||||
|  |       redirect_uri: redirectUri, | ||||||
|  |       state: state, | ||||||
|  |       auto_approve: autoApprove, | ||||||
|  |       scope: JSON.stringify(scopes) | ||||||
|  |     }, | ||||||
|  |     method: 'post' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| import axios, { | import axios, { | ||||||
|  |   AxiosError, | ||||||
|   AxiosInstance, |   AxiosInstance, | ||||||
|   AxiosRequestHeaders, |   AxiosRequestHeaders, | ||||||
|   AxiosResponse, |   AxiosResponse, | ||||||
|   AxiosError, |  | ||||||
|   InternalAxiosRequestConfig |   InternalAxiosRequestConfig | ||||||
| } from 'axios' | } from 'axios' | ||||||
|  |  | ||||||
| @@ -230,7 +230,7 @@ const handleAuthorized = () => { | |||||||
|       wsCache.clear() |       wsCache.clear() | ||||||
|       removeToken() |       removeToken() | ||||||
|       isRelogin.show = false |       isRelogin.show = false | ||||||
|       window.location.href = '/' |       window.location.href = '/login?redirect=/sso?' + window.location.href.split('?')[1] | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|   return Promise.reject(t('sys.api.timeoutMessage')) |   return Promise.reject(t('sys.api.timeoutMessage')) | ||||||
|   | |||||||
| @@ -129,6 +129,12 @@ export default { | |||||||
|     btnMobile: '手机登录', |     btnMobile: '手机登录', | ||||||
|     btnQRCode: '二维码登录', |     btnQRCode: '二维码登录', | ||||||
|     qrcode: '扫描二维码登录', |     qrcode: '扫描二维码登录', | ||||||
|  |     sso: { | ||||||
|  |       user: { | ||||||
|  |         read: '访问你的个人信息', | ||||||
|  |         write: '修改你的个人信息' | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     btnRegister: '注册', |     btnRegister: '注册', | ||||||
|     SmsSendMsg: '验证码已发送' |     SmsSendMsg: '验证码已发送' | ||||||
|   }, |   }, | ||||||
| @@ -353,6 +359,7 @@ export default { | |||||||
|     login: { |     login: { | ||||||
|       backSignIn: '返回', |       backSignIn: '返回', | ||||||
|       signInFormTitle: '登录', |       signInFormTitle: '登录', | ||||||
|  |       ssoFormTitle: '三方授权', | ||||||
|       mobileSignInFormTitle: '手机登录', |       mobileSignInFormTitle: '手机登录', | ||||||
|       qrSignInFormTitle: '二维码登录', |       qrSignInFormTitle: '二维码登录', | ||||||
|       signUpFormTitle: '注册', |       signUpFormTitle: '注册', | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| import type { App } from 'vue' | import type { App } from 'vue' | ||||||
| import type { RouteRecordRaw } from 'vue-router' | import type { RouteRecordRaw } from 'vue-router' | ||||||
| import { createRouter, createWebHashHistory } from 'vue-router' | import { createRouter, createWebHistory } from 'vue-router' | ||||||
| import remainingRouter from './modules/remaining' | import remainingRouter from './modules/remaining' | ||||||
|  |  | ||||||
| // 创建路由实例 | // 创建路由实例 | ||||||
| const router = createRouter({ | const router = createRouter({ | ||||||
|   history: createWebHashHistory(), // createWebHashHistory URL带#,createWebHistory URL不带# |   history: createWebHistory(), // createWebHashHistory URL带#,createWebHistory URL不带# | ||||||
|   strict: true, |   strict: true, | ||||||
|   routes: remainingRouter as RouteRecordRaw[], |   routes: remainingRouter as RouteRecordRaw[], | ||||||
|   scrollBehavior: () => ({ left: 0, top: 0 }) |   scrollBehavior: () => ({ left: 0, top: 0 }) | ||||||
|   | |||||||
| @@ -185,6 +185,16 @@ const remainingRouter: AppRouteRecordRaw[] = [ | |||||||
|       noTagsView: true |       noTagsView: true | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     path: '/sso', | ||||||
|  |     component: () => import('@/views/Login/Login.vue'), | ||||||
|  |     name: 'SSOLogin', | ||||||
|  |     meta: { | ||||||
|  |       hidden: true, | ||||||
|  |       title: t('router.login'), | ||||||
|  |       noTagsView: true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     path: '/403', |     path: '/403', | ||||||
|     component: () => import('@/views/Error/403.vue'), |     component: () => import('@/views/Error/403.vue'), | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								src/types/auto-components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								src/types/auto-components.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -23,13 +23,12 @@ declare module '@vue/runtime-core' { | |||||||
|     DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] |     DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] | ||||||
|     Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] |     Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] | ||||||
|     Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] |     Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] | ||||||
|     ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer'] |  | ||||||
|     ElAvatar: typeof import('element-plus/es')['ElAvatar'] |  | ||||||
|     ElBadge: typeof import('element-plus/es')['ElBadge'] |     ElBadge: typeof import('element-plus/es')['ElBadge'] | ||||||
|     ElButton: typeof import('element-plus/es')['ElButton'] |     ElButton: typeof import('element-plus/es')['ElButton'] | ||||||
|     ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] |     ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] | ||||||
|     ElCard: typeof import('element-plus/es')['ElCard'] |     ElCard: typeof import('element-plus/es')['ElCard'] | ||||||
|     ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] |     ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] | ||||||
|  |     ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] | ||||||
|     ElCol: typeof import('element-plus/es')['ElCol'] |     ElCol: typeof import('element-plus/es')['ElCol'] | ||||||
|     ElCollapse: typeof import('element-plus/es')['ElCollapse'] |     ElCollapse: typeof import('element-plus/es')['ElCollapse'] | ||||||
|     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] |     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] | ||||||
| @@ -54,34 +53,23 @@ declare module '@vue/runtime-core' { | |||||||
|     ElForm: typeof import('element-plus/es')['ElForm'] |     ElForm: typeof import('element-plus/es')['ElForm'] | ||||||
|     ElFormItem: typeof import('element-plus/es')['ElFormItem'] |     ElFormItem: typeof import('element-plus/es')['ElFormItem'] | ||||||
|     ElIcon: typeof import('element-plus/es')['ElIcon'] |     ElIcon: typeof import('element-plus/es')['ElIcon'] | ||||||
|     ElImage: typeof import('element-plus/es')['ElImage'] |  | ||||||
|     ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] |     ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] | ||||||
|     ElInput: typeof import('element-plus/es')['ElInput'] |     ElInput: typeof import('element-plus/es')['ElInput'] | ||||||
|     ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] |  | ||||||
|     ElLink: typeof import('element-plus/es')['ElLink'] |     ElLink: typeof import('element-plus/es')['ElLink'] | ||||||
|     ElOption: typeof import('element-plus/es')['ElOption'] |     ElOption: typeof import('element-plus/es')['ElOption'] | ||||||
|     ElPagination: typeof import('element-plus/es')['ElPagination'] |     ElPagination: typeof import('element-plus/es')['ElPagination'] | ||||||
|     ElPopover: typeof import('element-plus/es')['ElPopover'] |     ElPopover: typeof import('element-plus/es')['ElPopover'] | ||||||
|     ElRadio: typeof import('element-plus/es')['ElRadio'] |  | ||||||
|     ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] |  | ||||||
|     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] |  | ||||||
|     ElRow: typeof import('element-plus/es')['ElRow'] |     ElRow: typeof import('element-plus/es')['ElRow'] | ||||||
|     ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] |     ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] | ||||||
|     ElSelect: typeof import('element-plus/es')['ElSelect'] |     ElSelect: typeof import('element-plus/es')['ElSelect'] | ||||||
|     ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] |     ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] | ||||||
|     ElSpace: typeof import('element-plus/es')['ElSpace'] |  | ||||||
|     ElSwitch: typeof import('element-plus/es')['ElSwitch'] |     ElSwitch: typeof import('element-plus/es')['ElSwitch'] | ||||||
|     ElTable: typeof import('element-plus/es')['ElTable'] |     ElTable: typeof import('element-plus/es')['ElTable'] | ||||||
|     ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] |     ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] | ||||||
|     ElTableV2: typeof import('element-plus/es')['ElTableV2'] |  | ||||||
|     ElTabPane: typeof import('element-plus/es')['ElTabPane'] |     ElTabPane: typeof import('element-plus/es')['ElTabPane'] | ||||||
|     ElTabs: typeof import('element-plus/es')['ElTabs'] |     ElTabs: typeof import('element-plus/es')['ElTabs'] | ||||||
|     ElTag: typeof import('element-plus/es')['ElTag'] |  | ||||||
|     ElTimeline: typeof import('element-plus/es')['ElTimeline'] |  | ||||||
|     ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] |  | ||||||
|     ElTooltip: typeof import('element-plus/es')['ElTooltip'] |     ElTooltip: typeof import('element-plus/es')['ElTooltip'] | ||||||
|     ElTree: typeof import('element-plus/es')['ElTree'] |     ElTree: typeof import('element-plus/es')['ElTree'] | ||||||
|     ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] |  | ||||||
|     ElUpload: typeof import('element-plus/es')['ElUpload'] |     ElUpload: typeof import('element-plus/es')['ElUpload'] | ||||||
|     Error: typeof import('./../components/Error/src/Error.vue')['default'] |     Error: typeof import('./../components/Error/src/Error.vue')['default'] | ||||||
|     FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default'] |     FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default'] | ||||||
|   | |||||||
| @@ -52,6 +52,11 @@ | |||||||
|             <QrCodeForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> |             <QrCodeForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> | ||||||
|             <!-- 注册 --> |             <!-- 注册 --> | ||||||
|             <RegisterForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> |             <RegisterForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> | ||||||
|  |             <!-- 三方登录 v-if触发组件初始化 --> | ||||||
|  |             <SSOLoginVue | ||||||
|  |               v-if="isSSO" | ||||||
|  |               class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" | ||||||
|  |             /> | ||||||
|           </div> |           </div> | ||||||
|         </Transition> |         </Transition> | ||||||
|       </div> |       </div> | ||||||
| @@ -65,12 +70,25 @@ import { useDesign } from '@/hooks/web/useDesign' | |||||||
| import { useAppStore } from '@/store/modules/app' | import { useAppStore } from '@/store/modules/app' | ||||||
| import { ThemeSwitch } from '@/layout/components/ThemeSwitch' | import { ThemeSwitch } from '@/layout/components/ThemeSwitch' | ||||||
| import { LocaleDropdown } from '@/layout/components/LocaleDropdown' | import { LocaleDropdown } from '@/layout/components/LocaleDropdown' | ||||||
| import { LoginForm, MobileForm, RegisterForm, QrCodeForm } from './components' |  | ||||||
|  | import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components' | ||||||
|  | import { RouteLocationNormalizedLoaded } from 'vue-router' | ||||||
|  |  | ||||||
| const { t } = useI18n() | const { t } = useI18n() | ||||||
| const appStore = useAppStore() | const appStore = useAppStore() | ||||||
| const { getPrefixCls } = useDesign() | const { getPrefixCls } = useDesign() | ||||||
| const prefixCls = getPrefixCls('login') | const prefixCls = getPrefixCls('login') | ||||||
|  | // =======SSO====== | ||||||
|  | const isSSO = ref(false) | ||||||
|  | const router = useRouter() | ||||||
|  | // 监听当前路由 | ||||||
|  | watch( | ||||||
|  |   () => router.currentRoute.value, | ||||||
|  |   (route: RouteLocationNormalizedLoaded) => { | ||||||
|  |     if (route.name === 'SSOLogin') isSSO.value = true | ||||||
|  |   }, | ||||||
|  |   { immediate: true } | ||||||
|  | ) | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
|   | |||||||
| @@ -137,7 +137,7 @@ import { useIcon } from '@/hooks/web/useIcon' | |||||||
| import * as authUtil from '@/utils/auth' | import * as authUtil from '@/utils/auth' | ||||||
| import { usePermissionStore } from '@/store/modules/permission' | import { usePermissionStore } from '@/store/modules/permission' | ||||||
| import * as LoginApi from '@/api/login' | import * as LoginApi from '@/api/login' | ||||||
| import { LoginStateEnum, useLoginState, useFormValid } from './useLogin' | import { LoginStateEnum, useFormValid, useLoginState } from './useLogin' | ||||||
|  |  | ||||||
| const { t } = useI18n() | const { t } = useI18n() | ||||||
| const message = useMessage() | const message = useMessage() | ||||||
| @@ -240,7 +240,12 @@ const handleLogin = async (params) => { | |||||||
|     if (!redirect.value) { |     if (!redirect.value) { | ||||||
|       redirect.value = '/' |       redirect.value = '/' | ||||||
|     } |     } | ||||||
|     push({ path: redirect.value || permissionStore.addRouters[0].path }) |     // 判断是否为SSO登录 | ||||||
|  |     if (redirect.value.indexOf('sso') !== -1) { | ||||||
|  |       window.location.href = window.location.href.replace('/login?redirect=', '') | ||||||
|  |     } else { | ||||||
|  |       push({ path: redirect.value || permissionStore.addRouters[0].path }) | ||||||
|  |     } | ||||||
|   } catch { |   } catch { | ||||||
|     loginLoading.value = false |     loginLoading.value = false | ||||||
|   } finally { |   } finally { | ||||||
| @@ -274,6 +279,7 @@ const doSocialLogin = async (type: number) => { | |||||||
| watch( | watch( | ||||||
|   () => currentRoute.value, |   () => currentRoute.value, | ||||||
|   (route: RouteLocationNormalizedLoaded) => { |   (route: RouteLocationNormalizedLoaded) => { | ||||||
|  |     if (route.name === 'SSOLogin') setLoginState(LoginStateEnum.SSO) | ||||||
|     redirect.value = route?.query?.redirect as string |     redirect.value = route?.query?.redirect as string | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
| @@ -291,6 +297,7 @@ onMounted(() => { | |||||||
|     color: var(--el-color-primary) !important; |     color: var(--el-color-primary) !important; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| .login-code { | .login-code { | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   height: 38px; |   height: 38px; | ||||||
|   | |||||||
| @@ -16,7 +16,8 @@ const getFormTitle = computed(() => { | |||||||
|     [LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'), |     [LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'), | ||||||
|     [LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'), |     [LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'), | ||||||
|     [LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'), |     [LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'), | ||||||
|     [LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle') |     [LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'), | ||||||
|  |     [LoginStateEnum.SSO]: t('sys.login.ssoFormTitle') | ||||||
|   } |   } | ||||||
|   return titleObj[unref(getLoginState)] |   return titleObj[unref(getLoginState)] | ||||||
| }) | }) | ||||||
|   | |||||||
							
								
								
									
										177
									
								
								src/views/Login/components/SSOLogin.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/views/Login/components/SSOLogin.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | |||||||
|  | <template> | ||||||
|  |   <!-- 表单 --> | ||||||
|  |   <div class="form-cont"> | ||||||
|  |     <el-tabs class="form" style="float: none" value="uname"> | ||||||
|  |       <el-tab-pane :label="'三方授权(' + client.name + ')'" name="uname" /> | ||||||
|  |     </el-tabs> | ||||||
|  |     <div> | ||||||
|  |       <el-form ref="ssoForm" :model="loginForm" class="login-form"> | ||||||
|  |         <!-- 授权范围的选择 --> | ||||||
|  |         此第三方应用请求获得以下权限: | ||||||
|  |         <el-form-item prop="scopes"> | ||||||
|  |           <el-checkbox-group v-model="loginForm.scopes"> | ||||||
|  |             <el-checkbox | ||||||
|  |               v-for="scope in params.scopes" | ||||||
|  |               :label="scope" | ||||||
|  |               :key="scope" | ||||||
|  |               style="display: block; margin-bottom: -10px" | ||||||
|  |               >{{ formatScope(scope) }} | ||||||
|  |             </el-checkbox> | ||||||
|  |           </el-checkbox-group> | ||||||
|  |         </el-form-item> | ||||||
|  |         <!-- 下方的登录按钮 --> | ||||||
|  |         <el-form-item style="width: 100%"> | ||||||
|  |           <el-button | ||||||
|  |             :loading="loading" | ||||||
|  |             size="small" | ||||||
|  |             type="primary" | ||||||
|  |             style="width: 60%" | ||||||
|  |             @click.prevent="handleAuthorize(true)" | ||||||
|  |           > | ||||||
|  |             <span v-if="!loading">同意授权</span> | ||||||
|  |             <span v-else>授 权 中...</span> | ||||||
|  |           </el-button> | ||||||
|  |           <el-button size="small" style="width: 36%" @click.prevent="handleAuthorize(false)" | ||||||
|  |             >拒绝 | ||||||
|  |           </el-button> | ||||||
|  |         </el-form-item> | ||||||
|  |       </el-form> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | <script lang="ts" name="SSOLogin" setup> | ||||||
|  | import { authorize, getAuthorize } from '@/api/login' | ||||||
|  |  | ||||||
|  | const { t } = useI18n() | ||||||
|  | const ssoForm = ref() // 表单Ref | ||||||
|  |  | ||||||
|  | type scopesType = string[] | ||||||
|  | interface paramsType { | ||||||
|  |   responseType: string | ||||||
|  |   clientId: string | ||||||
|  |   redirectUri: string | ||||||
|  |   state: string | ||||||
|  |   scopes: scopesType | ||||||
|  | } | ||||||
|  | const loginForm = reactive<{ scopes: scopesType }>({ | ||||||
|  |   scopes: [] // 已选中的 scope 数组 | ||||||
|  | }) | ||||||
|  | const params = reactive<paramsType>({ | ||||||
|  |   // URL 上的 client_id、scope 等参数 | ||||||
|  |   responseType: '', | ||||||
|  |   clientId: '', | ||||||
|  |   redirectUri: '', | ||||||
|  |   state: '', | ||||||
|  |   scopes: [] // 优先从 query 参数获取;如果未传递,从后端获取 | ||||||
|  | }) // 表单Ref | ||||||
|  | const client = ref({ | ||||||
|  |   // 客户端信息 | ||||||
|  |   name: '', | ||||||
|  |   logo: '' | ||||||
|  | }) | ||||||
|  | const loading = ref(false) | ||||||
|  | const handleAuthorize = (approved) => { | ||||||
|  |   ssoForm.value.validate((valid) => { | ||||||
|  |     if (!valid) { | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  |     loading.value = true | ||||||
|  |     // 计算 checkedScopes + uncheckedScopes | ||||||
|  |     let checkedScopes | ||||||
|  |     let uncheckedScopes | ||||||
|  |     if (approved) { | ||||||
|  |       // 同意授权,按照用户的选择 | ||||||
|  |       checkedScopes = loginForm.scopes | ||||||
|  |       uncheckedScopes = params.scopes.filter((item) => checkedScopes.indexOf(item) === -1) | ||||||
|  |     } else { | ||||||
|  |       // 拒绝,则都是取消 | ||||||
|  |       checkedScopes = [] | ||||||
|  |       uncheckedScopes = params.scopes | ||||||
|  |     } | ||||||
|  |     // 提交授权的请求 | ||||||
|  |     doAuthorize(false, checkedScopes, uncheckedScopes) | ||||||
|  |       .then((res) => { | ||||||
|  |         const href = res.data | ||||||
|  |         if (!href) { | ||||||
|  |           return | ||||||
|  |         } | ||||||
|  |         location.href = href | ||||||
|  |       }) | ||||||
|  |       .finally(() => { | ||||||
|  |         loading.value = false | ||||||
|  |       }) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => { | ||||||
|  |   return authorize( | ||||||
|  |     params.responseType, | ||||||
|  |     params.clientId, | ||||||
|  |     params.redirectUri, | ||||||
|  |     params.state, | ||||||
|  |     autoApprove, | ||||||
|  |     checkedScopes, | ||||||
|  |     uncheckedScopes | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | const formatScope = (scope) => { | ||||||
|  |   // 格式化 scope 授权范围,方便用户理解。 | ||||||
|  |   // 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。 | ||||||
|  |   return t(`login.sso.${scope}`) | ||||||
|  | } | ||||||
|  | const route = useRoute() | ||||||
|  | const init = () => { | ||||||
|  |   // 防止在没有登录的情况下循环弹窗 | ||||||
|  |   if (typeof route.query.client_id === 'undefined') return | ||||||
|  |   // 解析参数 | ||||||
|  |   // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write | ||||||
|  |   // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read | ||||||
|  |   params.responseType = route.query.response_type as string | ||||||
|  |   params.clientId = route.query.client_id as string | ||||||
|  |   params.redirectUri = route.query.redirect_uri as string | ||||||
|  |   params.state = route.query.state as string | ||||||
|  |   if (route.query.scope) { | ||||||
|  |     params.scopes = (route.query.scope as string).split(' ') | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。 | ||||||
|  |   if (params.scopes.length > 0) { | ||||||
|  |     doAuthorize(true, params.scopes, []).then((res) => { | ||||||
|  |       if (!res) { | ||||||
|  |         console.log('自动授权未通过!') | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  |       location.href = res.data | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // 获取授权页的基本信息 | ||||||
|  |   getAuthorize(params.clientId).then((res) => { | ||||||
|  |     console.log(res) | ||||||
|  |     client.value = res.client | ||||||
|  |     // 解析 scope | ||||||
|  |     let scopes | ||||||
|  |     // 1.1 如果 params.scope 非空,则过滤下返回的 scopes | ||||||
|  |     if (params.scopes.length > 0) { | ||||||
|  |       scopes = [] | ||||||
|  |       for (const scope of res.scopes) { | ||||||
|  |         if (params.scopes.indexOf(scope.key) >= 0) { | ||||||
|  |           scopes.push(scope) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       // 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它 | ||||||
|  |     } else { | ||||||
|  |       scopes = res.scopes | ||||||
|  |       for (const scope of scopes) { | ||||||
|  |         params.scopes.push(scope.key) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     // 生成已选中的 checkedScopes | ||||||
|  |     for (const scope of scopes) { | ||||||
|  |       if (scope.value) { | ||||||
|  |         loginForm.scopes.push(scope.key) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | init() | ||||||
|  | </script> | ||||||
| @@ -3,5 +3,6 @@ import MobileForm from './MobileForm.vue' | |||||||
| import LoginFormTitle from './LoginFormTitle.vue' | import LoginFormTitle from './LoginFormTitle.vue' | ||||||
| import RegisterForm from './RegisterForm.vue' | import RegisterForm from './RegisterForm.vue' | ||||||
| import QrCodeForm from './QrCodeForm.vue' | import QrCodeForm from './QrCodeForm.vue' | ||||||
|  | import SSOLoginVue from './SSOLogin.vue' | ||||||
|  |  | ||||||
| export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm } | export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue } | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ export enum LoginStateEnum { | |||||||
|   REGISTER, |   REGISTER, | ||||||
|   RESET_PASSWORD, |   RESET_PASSWORD, | ||||||
|   MOBILE, |   MOBILE, | ||||||
|   QR_CODE |   QR_CODE, | ||||||
|  |   SSO | ||||||
| } | } | ||||||
|  |  | ||||||
| const currentState = ref(LoginStateEnum.LOGIN) | const currentState = ref(LoginStateEnum.LOGIN) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 puhui999
					puhui999