mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-11-04 12:18:43 +08:00 
			
		
		
		
	
							
								
								
									
										2
									
								
								.env.dev
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								.env.dev
									
									
									
									
									
								
							@@ -16,7 +16,7 @@ VITE_API_BASEPATH=/dev-api
 | 
			
		||||
VITE_API_URL=/admin-api
 | 
			
		||||
 | 
			
		||||
# 打包路径
 | 
			
		||||
VITE_BASE_PATH=/dist-dev/
 | 
			
		||||
VITE_BASE_PATH=/
 | 
			
		||||
 | 
			
		||||
# 是否删除debugger
 | 
			
		||||
VITE_DROP_DEBUGGER=false
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,19 @@
 | 
			
		||||
import request from '@/config/axios'
 | 
			
		||||
import { getRefreshToken } from '@/utils/auth'
 | 
			
		||||
import type { UserLoginVO } from './types'
 | 
			
		||||
import { service } from '@/config/axios/service'
 | 
			
		||||
 | 
			
		||||
export interface CodeImgResult {
 | 
			
		||||
  captchaOnOff: boolean
 | 
			
		||||
  img: string
 | 
			
		||||
  uuid: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SmsCodeVO {
 | 
			
		||||
  mobile: string
 | 
			
		||||
  scene: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SmsLoginVO {
 | 
			
		||||
  mobile: string
 | 
			
		||||
  code: string
 | 
			
		||||
@@ -71,3 +74,51 @@ export const getCode = (data) => {
 | 
			
		||||
export const reqCheck = (data) => {
 | 
			
		||||
  return request.postOriginal({ url: 'system/captcha/check', data })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ========== OAUTH 2.0 相关 ==========
 | 
			
		||||
export type scopesType = string[]
 | 
			
		||||
export interface paramsType {
 | 
			
		||||
  responseType: string
 | 
			
		||||
  clientId: string
 | 
			
		||||
  redirectUri: string
 | 
			
		||||
  state: string
 | 
			
		||||
  scopes: scopesType
 | 
			
		||||
}
 | 
			
		||||
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: scopesType,
 | 
			
		||||
  uncheckedScopes: scopesType
 | 
			
		||||
) {
 | 
			
		||||
  // 构建 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, {
 | 
			
		||||
  AxiosError,
 | 
			
		||||
  AxiosInstance,
 | 
			
		||||
  AxiosRequestHeaders,
 | 
			
		||||
  AxiosResponse,
 | 
			
		||||
  AxiosError,
 | 
			
		||||
  InternalAxiosRequestConfig
 | 
			
		||||
} from 'axios'
 | 
			
		||||
 | 
			
		||||
@@ -230,7 +230,7 @@ const handleAuthorized = () => {
 | 
			
		||||
      wsCache.clear()
 | 
			
		||||
      removeToken()
 | 
			
		||||
      isRelogin.show = false
 | 
			
		||||
      window.location.href = import.meta.env.VITE_BASE_PATH
 | 
			
		||||
      window.location.href = '/login?redirect=/sso?' + window.location.href.split('?')[1]
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  return Promise.reject(t('sys.api.timeoutMessage'))
 | 
			
		||||
 
 | 
			
		||||
@@ -129,6 +129,12 @@ export default {
 | 
			
		||||
    btnMobile: '手机登录',
 | 
			
		||||
    btnQRCode: '二维码登录',
 | 
			
		||||
    qrcode: '扫描二维码登录',
 | 
			
		||||
    sso: {
 | 
			
		||||
      user: {
 | 
			
		||||
        read: '访问你的个人信息',
 | 
			
		||||
        write: '修改你的个人信息'
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    btnRegister: '注册',
 | 
			
		||||
    SmsSendMsg: '验证码已发送'
 | 
			
		||||
  },
 | 
			
		||||
@@ -352,6 +358,7 @@ export default {
 | 
			
		||||
    login: {
 | 
			
		||||
      backSignIn: '返回',
 | 
			
		||||
      signInFormTitle: '登录',
 | 
			
		||||
      ssoFormTitle: '三方授权',
 | 
			
		||||
      mobileSignInFormTitle: '手机登录',
 | 
			
		||||
      qrSignInFormTitle: '二维码登录',
 | 
			
		||||
      signUpFormTitle: '注册',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
import type { App } from 'vue'
 | 
			
		||||
import type { RouteRecordRaw } from 'vue-router'
 | 
			
		||||
import { createRouter, createWebHashHistory } from 'vue-router'
 | 
			
		||||
import { createRouter, createWebHistory } from 'vue-router'
 | 
			
		||||
import remainingRouter from './modules/remaining'
 | 
			
		||||
 | 
			
		||||
// 创建路由实例
 | 
			
		||||
const router = createRouter({
 | 
			
		||||
  history: createWebHashHistory(), // createWebHashHistory URL带#,createWebHistory URL不带#
 | 
			
		||||
  history: createWebHistory(), // createWebHashHistory URL带#,createWebHistory URL不带#
 | 
			
		||||
  strict: true,
 | 
			
		||||
  routes: remainingRouter as RouteRecordRaw[],
 | 
			
		||||
  scrollBehavior: () => ({ left: 0, top: 0 })
 | 
			
		||||
 
 | 
			
		||||
@@ -185,6 +185,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
 | 
			
		||||
      noTagsView: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/sso',
 | 
			
		||||
    component: () => import('@/views/Login/Login.vue'),
 | 
			
		||||
    name: 'SSOLogin',
 | 
			
		||||
    meta: {
 | 
			
		||||
      hidden: true,
 | 
			
		||||
      title: t('router.login'),
 | 
			
		||||
      noTagsView: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/403',
 | 
			
		||||
    component: () => import('@/views/Error/403.vue'),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								src/types/auto-components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								src/types/auto-components.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -21,15 +21,14 @@ declare module '@vue/runtime-core' {
 | 
			
		||||
    Descriptions: typeof import('./../components/Descriptions/src/Descriptions.vue')['default']
 | 
			
		||||
    Dialog: typeof import('./../components/Dialog/src/Dialog.vue')['default']
 | 
			
		||||
    DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default']
 | 
			
		||||
    DocAlert: typeof import('./../components/DocAlert/index.vue')['default']
 | 
			
		||||
    Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
 | 
			
		||||
    Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
 | 
			
		||||
    ElAlert: typeof import('element-plus/es')['ElAlert']
 | 
			
		||||
    ElBadge: typeof import('element-plus/es')['ElBadge']
 | 
			
		||||
    ElButton: typeof import('element-plus/es')['ElButton']
 | 
			
		||||
    ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
 | 
			
		||||
    ElCard: typeof import('element-plus/es')['ElCard']
 | 
			
		||||
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
 | 
			
		||||
    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
 | 
			
		||||
    ElCol: typeof import('element-plus/es')['ElCol']
 | 
			
		||||
    ElCollapse: typeof import('element-plus/es')['ElCollapse']
 | 
			
		||||
    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
 | 
			
		||||
@@ -74,12 +73,8 @@ declare module '@vue/runtime-core' {
 | 
			
		||||
    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
 | 
			
		||||
    ElTabPane: typeof import('element-plus/es')['ElTabPane']
 | 
			
		||||
    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']
 | 
			
		||||
    ElTree: typeof import('element-plus/es')['ElTree']
 | 
			
		||||
    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
 | 
			
		||||
    ElUpload: typeof import('element-plus/es')['ElUpload']
 | 
			
		||||
    Error: typeof import('./../components/Error/src/Error.vue')['default']
 | 
			
		||||
    FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default']
 | 
			
		||||
 
 | 
			
		||||
@@ -9,19 +9,19 @@
 | 
			
		||||
      >
 | 
			
		||||
        <!-- 左上角的 logo + 系统标题 -->
 | 
			
		||||
        <div class="flex items-center relative text-white">
 | 
			
		||||
          <img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
 | 
			
		||||
          <img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
 | 
			
		||||
          <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- 左边的背景图 + 欢迎语 -->
 | 
			
		||||
        <div class="flex justify-center items-center h-[calc(100%-60px)]">
 | 
			
		||||
          <TransitionGroup
 | 
			
		||||
            appear
 | 
			
		||||
            tag="div"
 | 
			
		||||
            enter-active-class="animate__animated animate__bounceInLeft"
 | 
			
		||||
            tag="div"
 | 
			
		||||
          >
 | 
			
		||||
            <img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
 | 
			
		||||
            <div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
 | 
			
		||||
            <div class="mt-5 font-normal text-white text-14px" key="3">
 | 
			
		||||
            <img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
 | 
			
		||||
            <div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
 | 
			
		||||
            <div key="3" class="mt-5 font-normal text-white text-14px">
 | 
			
		||||
              {{ t('login.message') }}
 | 
			
		||||
            </div>
 | 
			
		||||
          </TransitionGroup>
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
        <!-- 右上角的主题、语言选择 -->
 | 
			
		||||
        <div class="flex justify-between items-center text-white @2xl:justify-end @xl:justify-end">
 | 
			
		||||
          <div class="flex items-center @2xl:hidden @xl:hidden">
 | 
			
		||||
            <img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
 | 
			
		||||
            <img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
 | 
			
		||||
            <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="flex justify-end items-center space-x-10px">
 | 
			
		||||
@@ -52,20 +52,23 @@
 | 
			
		||||
            <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)" />
 | 
			
		||||
            <!-- 三方登录 -->
 | 
			
		||||
            <SSOLoginVue class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </Transition>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { underlineToHump } from '@/utils'
 | 
			
		||||
 | 
			
		||||
import { useDesign } from '@/hooks/web/useDesign'
 | 
			
		||||
import { useAppStore } from '@/store/modules/app'
 | 
			
		||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
 | 
			
		||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
 | 
			
		||||
import { LoginForm, MobileForm, RegisterForm, QrCodeForm } from './components'
 | 
			
		||||
 | 
			
		||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n()
 | 
			
		||||
const appStore = useAppStore()
 | 
			
		||||
 
 | 
			
		||||
@@ -137,7 +137,7 @@ import { useIcon } from '@/hooks/web/useIcon'
 | 
			
		||||
import * as authUtil from '@/utils/auth'
 | 
			
		||||
import { usePermissionStore } from '@/store/modules/permission'
 | 
			
		||||
import * as LoginApi from '@/api/login'
 | 
			
		||||
import { LoginStateEnum, useLoginState, useFormValid } from './useLogin'
 | 
			
		||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n()
 | 
			
		||||
const message = useMessage()
 | 
			
		||||
@@ -240,7 +240,12 @@ const handleLogin = async (params) => {
 | 
			
		||||
    if (!redirect.value) {
 | 
			
		||||
      redirect.value = '/'
 | 
			
		||||
    }
 | 
			
		||||
    // 判断是否为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 {
 | 
			
		||||
    loginLoading.value = false
 | 
			
		||||
  } finally {
 | 
			
		||||
@@ -291,6 +296,7 @@ onMounted(() => {
 | 
			
		||||
    color: var(--el-color-primary) !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.login-code {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 38px;
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,8 @@ const getFormTitle = computed(() => {
 | 
			
		||||
    [LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
 | 
			
		||||
    [LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
 | 
			
		||||
    [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)]
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										187
									
								
								src/views/Login/components/SSOLogin.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/views/Login/components/SSOLogin.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <!-- 表单 -->
 | 
			
		||||
  <div v-show="getShow" class="form-cont">
 | 
			
		||||
    <!--    <LoginFormTitle style="width: 100%" />-->
 | 
			
		||||
    <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"
 | 
			
		||||
              :key="scope"
 | 
			
		||||
              :label="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"
 | 
			
		||||
            style="width: 60%"
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @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 LoginFormTitle from './LoginFormTitle.vue' // TODO 艿艿你看看要不要这个表头
 | 
			
		||||
import { authorize, getAuthorize, paramsType, scopesType } from '@/api/login'
 | 
			
		||||
import { LoginStateEnum, useLoginState } from './useLogin'
 | 
			
		||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n()
 | 
			
		||||
const ssoForm = ref() // 表单Ref
 | 
			
		||||
const { getLoginState, setLoginState } = useLoginState()
 | 
			
		||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.SSO)
 | 
			
		||||
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 都是一条字典数据。
 | 
			
		||||
  // TODO 这个之做了中文部分
 | 
			
		||||
  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) => {
 | 
			
		||||
    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)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
// =======SSO======
 | 
			
		||||
const { currentRoute } = useRouter()
 | 
			
		||||
// 监听当前路由
 | 
			
		||||
watch(
 | 
			
		||||
  () => currentRoute.value,
 | 
			
		||||
  (route: RouteLocationNormalizedLoaded) => {
 | 
			
		||||
    if (route.name === 'SSOLogin') {
 | 
			
		||||
      setLoginState(LoginStateEnum.SSO)
 | 
			
		||||
      init()
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
)
 | 
			
		||||
init()
 | 
			
		||||
</script>
 | 
			
		||||
@@ -3,5 +3,6 @@ import MobileForm from './MobileForm.vue'
 | 
			
		||||
import LoginFormTitle from './LoginFormTitle.vue'
 | 
			
		||||
import RegisterForm from './RegisterForm.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,
 | 
			
		||||
  RESET_PASSWORD,
 | 
			
		||||
  MOBILE,
 | 
			
		||||
  QR_CODE
 | 
			
		||||
  QR_CODE,
 | 
			
		||||
  SSO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const currentState = ref(LoginStateEnum.LOGIN)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user