mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-29 22:56:41 +08:00
sso login
This commit is contained in:
parent
f23e1f7ea4
commit
a556457aba
6
.env
6
.env
@ -1,8 +1,8 @@
|
|||||||
VITE_BASE_URL=/
|
VITE_BASE_URL=/
|
||||||
|
|
||||||
VITE_APP_TITLE=SoybeanAdmin
|
VITE_APP_TITLE=COCO
|
||||||
|
|
||||||
VITE_APP_DESC=SoybeanAdmin is a fresh and elegant admin template
|
VITE_APP_DESC=COCO is a Config Center
|
||||||
|
|
||||||
# the prefix of the icon name
|
# the prefix of the icon name
|
||||||
VITE_ICON_PREFIX=icon
|
VITE_ICON_PREFIX=icon
|
||||||
@ -27,7 +27,7 @@ VITE_HTTP_PROXY=Y
|
|||||||
VITE_ROUTER_HISTORY_MODE=history
|
VITE_ROUTER_HISTORY_MODE=history
|
||||||
|
|
||||||
# success code of backend service, when the code is received, the request is successful
|
# success code of backend service, when the code is received, the request is successful
|
||||||
VITE_SERVICE_SUCCESS_CODE=0000
|
VITE_SERVICE_SUCCESS_CODE=0
|
||||||
|
|
||||||
# logout codes of backend service, when the code is received, the user will be logged out and redirected to login page
|
# logout codes of backend service, when the code is received, the user will be logged out and redirected to login page
|
||||||
VITE_SERVICE_LOGOUT_CODES=8888,8889
|
VITE_SERVICE_LOGOUT_CODES=8888,8889
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# backend service base url, prod environment
|
# backend service base url, prod environment
|
||||||
VITE_SERVICE_BASE_URL=https://mock.apifox.com/m1/3109515-0-default
|
VITE_SERVICE_BASE_URL=https://api.959617.xyz/coco
|
||||||
|
|
||||||
# other backend service base url, prod environment
|
# other backend service base url, prod environment
|
||||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# backend service base url, test environment
|
# backend service base url, test environment
|
||||||
VITE_SERVICE_BASE_URL=https://mock.apifox.com/m1/3109515-0-default
|
VITE_SERVICE_BASE_URL=http://127.0.0.1:8000
|
||||||
|
|
||||||
# other backend service base url, test environment
|
# other backend service base url, test environment
|
||||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||||
|
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@ -23,3 +23,23 @@ jobs:
|
|||||||
- run: npx githublogen
|
- run: npx githublogen
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
|
||||||
|
- uses: softprops/action-gh-release@v2
|
||||||
|
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@latest
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@latest
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@latest
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: v1.0.0
|
||||||
|
@ -15,7 +15,7 @@ export function setupElegantRouter() {
|
|||||||
const key = routeName as RouteKey;
|
const key = routeName as RouteKey;
|
||||||
|
|
||||||
if (key === 'login') {
|
if (key === 'login') {
|
||||||
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
|
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat', 'sso-login', 'sso-callback'];
|
||||||
|
|
||||||
const moduleReg = modules.join('|');
|
const moduleReg = modules.join('|');
|
||||||
|
|
||||||
|
17
docker/Dockerfile
Normal file
17
docker/Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# 安装Node.js
|
||||||
|
FROM node:20-alpine3.17 as build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY .. /app
|
||||||
|
|
||||||
|
RUN npm install -g pnpm \
|
||||||
|
&& pnpm install \
|
||||||
|
&& pnpm run build
|
||||||
|
|
||||||
|
FROM nginx:stable-alpine
|
||||||
|
COPY --from=build /app/dist /usr/share/nginx/html/
|
||||||
|
# COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx","-g","daemon off;"]
|
||||||
|
|
50
docker/nginx.conf
Normal file
50
docker/nginx.conf
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
client_max_body_size 100m;
|
||||||
|
client_body_buffer_size 128k;
|
||||||
|
proxy_connect_timeout 5;
|
||||||
|
proxy_send_timeout 1800;
|
||||||
|
proxy_read_timeout 1800;
|
||||||
|
proxy_buffer_size 4k;
|
||||||
|
proxy_buffers 4 32k;
|
||||||
|
proxy_busy_buffers_size 64k;
|
||||||
|
proxy_temp_file_write_size 64k;
|
||||||
|
auth_basic "status";
|
||||||
|
#开启gzip
|
||||||
|
gzip on;
|
||||||
|
#低于1kb的资源不压缩
|
||||||
|
gzip_min_length 1k;
|
||||||
|
#压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多,建议设置在5左右。
|
||||||
|
gzip_comp_level 5;
|
||||||
|
#需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
|
||||||
|
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
|
||||||
|
#配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
|
||||||
|
gzip_disable "MSIE [1-6]\.";
|
||||||
|
#是否添加“Vary: Accept-Encoding”响应头
|
||||||
|
gzip_vary on;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html; #VUE项目,配置路由(必须)
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /assessh5 {
|
||||||
|
alias /usr/share/nginx/html; # inflow uni-app H5编译文件的目录,index.html所在目录
|
||||||
|
try_files $uri $uri/ /index.html last;
|
||||||
|
index index.html index.htm;
|
||||||
|
}
|
||||||
|
|
||||||
|
# location /inflow {
|
||||||
|
# try_files $uri $uri/ /inflow/index.html;
|
||||||
|
# root /usr/share/nginx/html/inflow/;
|
||||||
|
# index index.html;
|
||||||
|
# }
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
@ -35,12 +35,14 @@ export interface RequestOption<ResponseData = any> {
|
|||||||
response: AxiosResponse<ResponseData>,
|
response: AxiosResponse<ResponseData>,
|
||||||
instance: AxiosInstance
|
instance: AxiosInstance
|
||||||
) => Promise<AxiosResponse | null> | Promise<void>;
|
) => Promise<AxiosResponse | null> | Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* transform backend response when the responseType is json
|
* transform backend response when the responseType is json
|
||||||
*
|
*
|
||||||
* @param response Axios response
|
* @param response Axios response
|
||||||
*/
|
*/
|
||||||
transformBackendResponse(response: AxiosResponse<ResponseData>): any | Promise<any>;
|
transformBackendResponse(response: AxiosResponse<ResponseData>): any | Promise<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hook to handle error
|
* The hook to handle error
|
||||||
*
|
*
|
||||||
@ -51,6 +53,13 @@ export interface RequestOption<ResponseData = any> {
|
|||||||
onError: (error: AxiosError<ResponseData>) => void | Promise<void>;
|
onError: (error: AxiosError<ResponseData>) => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ErrorCodeHandle<ResponseData = any> {
|
||||||
|
handle: (
|
||||||
|
response: AxiosResponse<ResponseData>,
|
||||||
|
instance: AxiosInstance
|
||||||
|
) => Promise<AxiosResponse | null> | Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
interface ResponseMap {
|
interface ResponseMap {
|
||||||
blob: Blob;
|
blob: Blob;
|
||||||
text: string;
|
text: string;
|
||||||
@ -58,6 +67,7 @@ interface ResponseMap {
|
|||||||
stream: ReadableStream<Uint8Array>;
|
stream: ReadableStream<Uint8Array>;
|
||||||
document: Document;
|
document: Document;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResponseType = keyof ResponseMap | 'json';
|
export type ResponseType = keyof ResponseMap | 'json';
|
||||||
|
|
||||||
export type MappedType<R extends ResponseType, JsonType = any> = R extends keyof ResponseMap
|
export type MappedType<R extends ResponseType, JsonType = any> = R extends keyof ResponseMap
|
||||||
|
@ -10,6 +10,8 @@ export const themeSchemaOptions = transformRecordToOption(themeSchemaRecord);
|
|||||||
|
|
||||||
export const loginModuleRecord: Record<UnionKey.LoginModule, App.I18n.I18nKey> = {
|
export const loginModuleRecord: Record<UnionKey.LoginModule, App.I18n.I18nKey> = {
|
||||||
'pwd-login': 'page.login.pwdLogin.title',
|
'pwd-login': 'page.login.pwdLogin.title',
|
||||||
|
'sso-login': 'page.login.ssoLogin.title',
|
||||||
|
'sso-callback': 'page.login.ssoLogin.title',
|
||||||
'code-login': 'page.login.codeLogin.title',
|
'code-login': 'page.login.codeLogin.title',
|
||||||
register: 'page.login.register.title',
|
register: 'page.login.register.title',
|
||||||
'reset-pwd': 'page.login.resetPwd.title',
|
'reset-pwd': 'page.login.resetPwd.title',
|
||||||
|
@ -5,3 +5,7 @@ export enum SetupStoreId {
|
|||||||
Route = 'route-store',
|
Route = 'route-store',
|
||||||
Tab = 'tab-store'
|
Tab = 'tab-store'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SsoAuthor {
|
||||||
|
Authing = 'authing'
|
||||||
|
}
|
||||||
|
@ -52,7 +52,7 @@ export function useRouterPush(inSetup = true) {
|
|||||||
* @param redirectUrl The redirect url, if not specified, it will be the current route fullPath
|
* @param redirectUrl The redirect url, if not specified, it will be the current route fullPath
|
||||||
*/
|
*/
|
||||||
async function toLogin(loginModule?: UnionKey.LoginModule, redirectUrl?: string) {
|
async function toLogin(loginModule?: UnionKey.LoginModule, redirectUrl?: string) {
|
||||||
const module = loginModule || 'pwd-login';
|
const module = loginModule || 'sso-login';
|
||||||
|
|
||||||
const options: RouterPushOptions = {
|
const options: RouterPushOptions = {
|
||||||
params: {
|
params: {
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import type {VNode} from 'vue';
|
||||||
import type { VNode } from 'vue';
|
import {computed} from 'vue';
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import {useAuthStore} from '@/store/modules/auth';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import {useRouterPush} from '@/hooks/common/router';
|
||||||
import { useSvgIcon } from '@/hooks/common/icon';
|
import {useSvgIcon} from '@/hooks/common/icon';
|
||||||
import { $t } from '@/locales';
|
import {$t} from '@/locales';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'UserAvatar'
|
name: 'UserAvatar'
|
||||||
});
|
});
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const { routerPushByKey, toLogin } = useRouterPush();
|
const {routerPushByKey, toLogin} = useRouterPush();
|
||||||
const { SvgIconVNode } = useSvgIcon();
|
const {SvgIconVNode} = useSvgIcon();
|
||||||
|
|
||||||
function loginOrRegister() {
|
function loginOrRegister() {
|
||||||
toLogin();
|
toLogin();
|
||||||
@ -22,21 +22,21 @@ type DropdownKey = 'user-center' | 'logout';
|
|||||||
|
|
||||||
type DropdownOption =
|
type DropdownOption =
|
||||||
| {
|
| {
|
||||||
key: DropdownKey;
|
key: DropdownKey;
|
||||||
label: string;
|
label: string;
|
||||||
icon?: () => VNode;
|
icon?: () => VNode;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'divider';
|
type: 'divider';
|
||||||
key: string;
|
key: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
const opts: DropdownOption[] = [
|
const opts: DropdownOption[] = [
|
||||||
{
|
{
|
||||||
label: $t('common.userCenter'),
|
label: $t('common.userCenter'),
|
||||||
key: 'user-center',
|
key: 'user-center',
|
||||||
icon: SvgIconVNode({ icon: 'ph:user-circle', fontSize: 18 })
|
icon: SvgIconVNode({icon: 'ph:user-circle', fontSize: 18})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
@ -45,7 +45,7 @@ const options = computed(() => {
|
|||||||
{
|
{
|
||||||
label: $t('common.logout'),
|
label: $t('common.logout'),
|
||||||
key: 'logout',
|
key: 'logout',
|
||||||
icon: SvgIconVNode({ icon: 'ph:sign-out', fontSize: 18 })
|
icon: SvgIconVNode({icon: 'ph:sign-out', fontSize: 18})
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ function logout() {
|
|||||||
positiveText: $t('common.confirm'),
|
positiveText: $t('common.confirm'),
|
||||||
negativeText: $t('common.cancel'),
|
negativeText: $t('common.cancel'),
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
authStore.resetStore();
|
authStore.logoutSso();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ function handleDropdown(key: DropdownKey) {
|
|||||||
<NDropdown v-else placement="bottom" trigger="click" :options="options" @select="handleDropdown">
|
<NDropdown v-else placement="bottom" trigger="click" :options="options" @select="handleDropdown">
|
||||||
<div>
|
<div>
|
||||||
<ButtonIcon>
|
<ButtonIcon>
|
||||||
<SvgIcon icon="ph:user-circle" class="text-icon-large" />
|
<SvgIcon icon="ph:user-circle" class="text-icon-large"/>
|
||||||
<span class="text-16px font-medium">{{ authStore.userInfo.userName }}</span>
|
<span class="text-16px font-medium">{{ authStore.userInfo.userName }}</span>
|
||||||
</ButtonIcon>
|
</ButtonIcon>
|
||||||
</div>
|
</div>
|
||||||
|
@ -193,6 +193,9 @@ const local: App.I18n.Schema = {
|
|||||||
admin: 'Admin',
|
admin: 'Admin',
|
||||||
user: 'User'
|
user: 'User'
|
||||||
},
|
},
|
||||||
|
ssoLogin: {
|
||||||
|
title: 'SSO Login',
|
||||||
|
},
|
||||||
codeLogin: {
|
codeLogin: {
|
||||||
title: 'Verification Code Login',
|
title: 'Verification Code Login',
|
||||||
getCode: 'Get verification code',
|
getCode: 'Get verification code',
|
||||||
|
@ -193,6 +193,9 @@ const local: App.I18n.Schema = {
|
|||||||
admin: '管理员',
|
admin: '管理员',
|
||||||
user: '普通用户'
|
user: '普通用户'
|
||||||
},
|
},
|
||||||
|
ssoLogin: {
|
||||||
|
title: 'SSO登录',
|
||||||
|
},
|
||||||
codeLogin: {
|
codeLogin: {
|
||||||
title: '验证码登录',
|
title: '验证码登录',
|
||||||
getCode: '获取验证码',
|
getCode: '获取验证码',
|
||||||
|
@ -181,7 +181,7 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'login',
|
name: 'login',
|
||||||
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
|
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat|sso-login|sso-callback)?/:author?',
|
||||||
component: 'layout.blank$view.login',
|
component: 'layout.blank$view.login',
|
||||||
props: true,
|
props: true,
|
||||||
meta: {
|
meta: {
|
||||||
|
@ -162,7 +162,7 @@ const routeMap: RouteMap = {
|
|||||||
"function_tab": "/function/tab",
|
"function_tab": "/function/tab",
|
||||||
"function_toggle-auth": "/function/toggle-auth",
|
"function_toggle-auth": "/function/toggle-auth",
|
||||||
"home": "/home",
|
"home": "/home",
|
||||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
|
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat|sso-login|sso-callback)?/:author?",
|
||||||
"manage": "/manage",
|
"manage": "/manage",
|
||||||
"manage_menu": "/manage/menu",
|
"manage_menu": "/manage/menu",
|
||||||
"manage_role": "/manage/role",
|
"manage_role": "/manage/role",
|
||||||
|
@ -30,7 +30,7 @@ export function createRouteGuard(router: Router) {
|
|||||||
const loginRoute: RouteKey = 'login';
|
const loginRoute: RouteKey = 'login';
|
||||||
const noAuthorizationRoute: RouteKey = '403';
|
const noAuthorizationRoute: RouteKey = '403';
|
||||||
|
|
||||||
const isLogin = Boolean(localStg.get('token'));
|
const isLogin = Boolean(await checkLogin());
|
||||||
const needLogin = !to.meta.constant;
|
const needLogin = !to.meta.constant;
|
||||||
const routeRoles = to.meta.roles || [];
|
const routeRoles = to.meta.roles || [];
|
||||||
|
|
||||||
@ -182,12 +182,21 @@ async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkLogin() {
|
||||||
|
return await useAuthStore().checkLogin();
|
||||||
|
}
|
||||||
|
|
||||||
function handleRouteSwitch(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
|
function handleRouteSwitch(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||||
// route with href
|
// route with href
|
||||||
if (to.meta.href) {
|
if (to.meta.href) {
|
||||||
window.open(to.meta.href, '_blank');
|
window.open(to.meta.href, '_blank');
|
||||||
|
|
||||||
next({ path: from.fullPath, replace: true, query: from.query, hash: to.hash });
|
next({
|
||||||
|
path: from.fullPath,
|
||||||
|
replace: true,
|
||||||
|
query: from.query,
|
||||||
|
hash: to.hash
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './route';
|
export * from './route';
|
||||||
export * from './system-manage';
|
export * from './system-manage';
|
||||||
|
export * from './sso'
|
||||||
|
31
src/service/api/sso.ts
Normal file
31
src/service/api/sso.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import {requestCoco} from '../request';
|
||||||
|
|
||||||
|
export function fetchLoginSsoUrl(author: string) {
|
||||||
|
return requestCoco<string>({
|
||||||
|
url: `/auth/login/${author}`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doLoginSso(author: string, code: string, state: string) {
|
||||||
|
return requestCoco<string>({
|
||||||
|
url: `/auth/login/${author}/callback`,
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
code,
|
||||||
|
state
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doLogoutSso(author: string) {
|
||||||
|
return requestCoco<string>({url: `/logout/${author}`});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchLoginSsoCheck(author: string) {
|
||||||
|
return requestCoco<boolean>({url: `/auth/login/${author}/check`});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchSsoUserInfo(author: string) {
|
||||||
|
return requestCoco<Api.Auth.UserInfo>({url: `/userInfo/${author}`});
|
||||||
|
}
|
7
src/service/request/action-type.ts
Normal file
7
src/service/request/action-type.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type {ErrorCodeHandle} from '~/packages/axios';
|
||||||
|
import {actionIdempotent} from '@/service/request/action';
|
||||||
|
|
||||||
|
export const codeActions: Map<Api.ErrorCode.Code, ErrorCodeHandle> = new Map([
|
||||||
|
['1', actionIdempotent],
|
||||||
|
['2', actionIdempotent]
|
||||||
|
]);
|
24
src/service/request/action.ts
Normal file
24
src/service/request/action.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type {ErrorCodeHandle} from '~/packages/axios';
|
||||||
|
|
||||||
|
export const actionIdempotent = createAction({
|
||||||
|
async handle(response, instance) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const action2 = createAction({
|
||||||
|
async handle(response, instance) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function createAction(options?: Partial<ErrorCodeHandle>) {
|
||||||
|
const opts: ErrorCodeHandle<any> = {
|
||||||
|
handle: async () => {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(opts, options);
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
import type { AxiosResponse } from 'axios';
|
import type {AxiosResponse} from 'axios';
|
||||||
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
|
import {BACKEND_ERROR_CODE, createFlatRequest, createRequest} from '@sa/axios';
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import {useAuthStore} from '@/store/modules/auth';
|
||||||
import { localStg } from '@/utils/storage';
|
import {localStg} from '@/utils/storage';
|
||||||
import { getServiceBaseURL } from '@/utils/service';
|
import {getServiceBaseURL} from '@/utils/service';
|
||||||
import { $t } from '@/locales';
|
import {$t} from '@/locales';
|
||||||
import { handleRefreshToken } from './shared';
|
import {codeActions} from '@/service/request/action-type';
|
||||||
|
import {handleRefreshToken} from './shared';
|
||||||
|
|
||||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||||
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
const {baseURL, otherBaseURL} = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||||
|
|
||||||
interface InstanceState {
|
interface InstanceState {
|
||||||
/** whether the request is refreshing token */
|
/** whether the request is refreshing token */
|
||||||
@ -23,12 +24,12 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
async onRequest(config) {
|
async onRequest(config) {
|
||||||
const { headers } = config;
|
const {headers} = config;
|
||||||
|
|
||||||
// set token
|
// set token
|
||||||
const token = localStg.get('token');
|
const token = localStg.get('token');
|
||||||
const Authorization = token ? `Bearer ${token}` : null;
|
const Authorization = token ? `Bearer ${token}` : null;
|
||||||
Object.assign(headers, { Authorization });
|
Object.assign(headers, {Authorization});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
@ -133,12 +134,12 @@ export const demoRequest = createRequest<App.Service.DemoResponse>(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
async onRequest(config) {
|
async onRequest(config) {
|
||||||
const { headers } = config;
|
const {headers} = config;
|
||||||
|
|
||||||
// set token
|
// set token
|
||||||
const token = localStg.get('token');
|
const token = localStg.get('token');
|
||||||
const Authorization = token ? `Bearer ${token}` : null;
|
const Authorization = token ? `Bearer ${token}` : null;
|
||||||
Object.assign(headers, { Authorization });
|
Object.assign(headers, {Authorization});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
@ -168,3 +169,60 @@ export const demoRequest = createRequest<App.Service.DemoResponse>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const requestCoco = createFlatRequest<App.Service.Response, InstanceState>(
|
||||||
|
{
|
||||||
|
baseURL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
async onRequest(config) {
|
||||||
|
// const { headers } = config;
|
||||||
|
|
||||||
|
// set token
|
||||||
|
// const token = localStg.get('token');
|
||||||
|
// const Authorization = token ? `Bearer ${token}` : null;
|
||||||
|
// Object.assign(headers, { Authorization });
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
isBackendSuccess(response) {
|
||||||
|
// when the backend response code is "0000"(default), it means the request is success
|
||||||
|
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
|
||||||
|
return String(response.data.code) === String(import.meta.env.VITE_SERVICE_SUCCESS_CODE);
|
||||||
|
},
|
||||||
|
async onBackendFail(response, instance) {
|
||||||
|
const key: string = String(response.data.code);
|
||||||
|
const act = codeActions.get(key as Api.ErrorCode.Code);
|
||||||
|
return act?.handle(response, instance) as Promise<AxiosResponse | any>;
|
||||||
|
},
|
||||||
|
transformBackendResponse(response) {
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
// when the request is fail, you can show error message
|
||||||
|
|
||||||
|
let message = error.message;
|
||||||
|
let backendErrorCode = '';
|
||||||
|
|
||||||
|
// get backend error message and code
|
||||||
|
if (error.code === BACKEND_ERROR_CODE) {
|
||||||
|
message = error.response?.data?.msg || message;
|
||||||
|
backendErrorCode = error.response?.data?.code || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// the error message is displayed in the modal
|
||||||
|
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
||||||
|
if (modalLogoutCodes.includes(backendErrorCode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when the token is expired, refresh token and retry request, so no need to show error message
|
||||||
|
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
|
||||||
|
if (expiredTokenCodes.includes(backendErrorCode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.$message?.error?.(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, reactive, ref } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useLoading } from '@sa/hooks';
|
import { useLoading } from '@sa/hooks';
|
||||||
import { SetupStoreId } from '@/enum';
|
import {SetupStoreId, SsoAuthor} from '@/enum';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { fetchGetUserInfo, fetchLogin } from '@/service/api';
|
import {
|
||||||
|
doLoginSso,
|
||||||
|
doLogoutSso,
|
||||||
|
fetchGetUserInfo,
|
||||||
|
fetchLogin,
|
||||||
|
fetchLoginSsoCheck,
|
||||||
|
fetchLoginSsoUrl, fetchSsoUserInfo
|
||||||
|
} from '@/service/api';
|
||||||
import { localStg } from '@/utils/storage';
|
import { localStg } from '@/utils/storage';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { useRouteStore } from '../route';
|
import { useRouteStore } from '../route';
|
||||||
@ -101,6 +108,85 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchSsoUrl(author: string) {
|
||||||
|
startLoading();
|
||||||
|
|
||||||
|
const {data: url, error} = await fetchLoginSsoUrl(author);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
resetStore();
|
||||||
|
endLoading();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 若发生幂等
|
||||||
|
if (!url) {
|
||||||
|
const res = await fetchUser(author);
|
||||||
|
if (res) {
|
||||||
|
await redirectFromLogin();
|
||||||
|
endLoading();
|
||||||
|
if (routeStore.isInitAuthRoute) {
|
||||||
|
window.$notification?.success({
|
||||||
|
title: $t('page.login.common.loginSuccess'),
|
||||||
|
content: $t('page.login.common.welcomeBack', {userName: userInfo.userName}),
|
||||||
|
duration: 4500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endLoading();
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginSso(author: string, code: string, state: string) {
|
||||||
|
startLoading();
|
||||||
|
const {error} = await doLoginSso(author, code, state);
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
await fetchUser(author);
|
||||||
|
endLoading();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
await resetStore();
|
||||||
|
endLoading();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUser(author: string) {
|
||||||
|
const {data: info, error: err} = await fetchSsoUserInfo(author);
|
||||||
|
if (!err && info !== null) {
|
||||||
|
info.roles = ['R_SUPER'];
|
||||||
|
localStg.set('token', info.userId);
|
||||||
|
// 2. store user info
|
||||||
|
localStg.set('userInfo', info);
|
||||||
|
localStg.set('author', author);
|
||||||
|
|
||||||
|
// 3. update auth route
|
||||||
|
token.value = info.userId;
|
||||||
|
Object.assign(userInfo, info);
|
||||||
|
|
||||||
|
await routeStore.initAuthRoute();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logoutSso() {
|
||||||
|
await resetStore();
|
||||||
|
await doLogoutSso(localStg.get('author') || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkLogin() {
|
||||||
|
if (!token.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const {data: check, error} = await fetchLoginSsoCheck(localStg.get('author') || SsoAuthor.Authing);
|
||||||
|
if (!error) {
|
||||||
|
return check;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
userInfo,
|
userInfo,
|
||||||
@ -108,6 +194,10 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
|||||||
isLogin,
|
isLogin,
|
||||||
loginLoading,
|
loginLoading,
|
||||||
resetStore,
|
resetStore,
|
||||||
login
|
login,
|
||||||
|
fetchSsoUrl,
|
||||||
|
loginSso,
|
||||||
|
logoutSso,
|
||||||
|
checkLogin
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
3
src/typings/api.d.ts
vendored
3
src/typings/api.d.ts
vendored
@ -4,6 +4,9 @@
|
|||||||
* All backend api type
|
* All backend api type
|
||||||
*/
|
*/
|
||||||
declare namespace Api {
|
declare namespace Api {
|
||||||
|
namespace ErrorCode {
|
||||||
|
type Code = '1' | '2';
|
||||||
|
}
|
||||||
namespace Common {
|
namespace Common {
|
||||||
/** common params of paginating */
|
/** common params of paginating */
|
||||||
interface PaginatingCommonParams {
|
interface PaginatingCommonParams {
|
||||||
|
3
src/typings/app.d.ts
vendored
3
src/typings/app.d.ts
vendored
@ -373,6 +373,9 @@ declare namespace App {
|
|||||||
admin: string;
|
admin: string;
|
||||||
user: string;
|
user: string;
|
||||||
};
|
};
|
||||||
|
ssoLogin: {
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
codeLogin: {
|
codeLogin: {
|
||||||
title: string;
|
title: string;
|
||||||
getCode: string;
|
getCode: string;
|
||||||
|
2
src/typings/elegant-router.d.ts
vendored
2
src/typings/elegant-router.d.ts
vendored
@ -36,7 +36,7 @@ declare module "@elegant-router/types" {
|
|||||||
"function_tab": "/function/tab";
|
"function_tab": "/function/tab";
|
||||||
"function_toggle-auth": "/function/toggle-auth";
|
"function_toggle-auth": "/function/toggle-auth";
|
||||||
"home": "/home";
|
"home": "/home";
|
||||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
|
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat|sso-login|sso-callback)?/:author?";
|
||||||
"manage": "/manage";
|
"manage": "/manage";
|
||||||
"manage_menu": "/manage/menu";
|
"manage_menu": "/manage/menu";
|
||||||
"manage_role": "/manage/role";
|
"manage_role": "/manage/role";
|
||||||
|
1
src/typings/storage.d.ts
vendored
1
src/typings/storage.d.ts
vendored
@ -12,6 +12,7 @@ declare namespace StorageType {
|
|||||||
interface Local {
|
interface Local {
|
||||||
/** The i18n language */
|
/** The i18n language */
|
||||||
lang: App.I18n.LangType;
|
lang: App.I18n.LangType;
|
||||||
|
author: string;
|
||||||
/** The token */
|
/** The token */
|
||||||
token: string;
|
token: string;
|
||||||
/** The refresh token */
|
/** The refresh token */
|
||||||
|
9
src/typings/union-key.d.ts
vendored
9
src/typings/union-key.d.ts
vendored
@ -9,7 +9,14 @@ declare namespace UnionKey {
|
|||||||
* - reset-pwd: reset password
|
* - reset-pwd: reset password
|
||||||
* - bind-wechat: bind wechat
|
* - bind-wechat: bind wechat
|
||||||
*/
|
*/
|
||||||
type LoginModule = 'pwd-login' | 'code-login' | 'register' | 'reset-pwd' | 'bind-wechat';
|
type LoginModule =
|
||||||
|
'pwd-login'
|
||||||
|
| 'code-login'
|
||||||
|
| 'register'
|
||||||
|
| 'reset-pwd'
|
||||||
|
| 'bind-wechat'
|
||||||
|
| 'sso-login'
|
||||||
|
| 'sso-callback';
|
||||||
|
|
||||||
/** Theme scheme */
|
/** Theme scheme */
|
||||||
type ThemeScheme = 'light' | 'dark' | 'auto';
|
type ThemeScheme = 'light' | 'dark' | 'auto';
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import type {Component} from 'vue';
|
||||||
import type { Component } from 'vue';
|
import {computed} from 'vue';
|
||||||
import { getColorPalette, mixColor } from '@sa/utils';
|
import {getColorPalette, mixColor} from '@sa/utils';
|
||||||
import { $t } from '@/locales';
|
import {$t} from '@/locales';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import {useAppStore} from '@/store/modules/app';
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import {useThemeStore} from '@/store/modules/theme';
|
||||||
import { loginModuleRecord } from '@/constants/app';
|
import {loginModuleRecord} from '@/constants/app';
|
||||||
import PwdLogin from './modules/pwd-login.vue';
|
import PwdLogin from './modules/pwd-login.vue';
|
||||||
import CodeLogin from './modules/code-login.vue';
|
import CodeLogin from './modules/code-login.vue';
|
||||||
import Register from './modules/register.vue';
|
import Register from './modules/register.vue';
|
||||||
import ResetPwd from './modules/reset-pwd.vue';
|
import ResetPwd from './modules/reset-pwd.vue';
|
||||||
import BindWechat from './modules/bind-wechat.vue';
|
import BindWechat from './modules/bind-wechat.vue';
|
||||||
|
import SsoLogin from "@/views/_builtin/login/modules/sso-login.vue";
|
||||||
|
import SsoCallback from "@/views/_builtin/login/modules/sso-callback.vue";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** The login module */
|
/** The login module */
|
||||||
@ -28,14 +30,16 @@ interface LoginModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
|
const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
|
||||||
'pwd-login': { label: loginModuleRecord['pwd-login'], component: PwdLogin },
|
'pwd-login': {label: loginModuleRecord['pwd-login'], component: PwdLogin},
|
||||||
'code-login': { label: loginModuleRecord['code-login'], component: CodeLogin },
|
'sso-login': {label: loginModuleRecord['sso-login'], component: SsoLogin},
|
||||||
register: { label: loginModuleRecord.register, component: Register },
|
'sso-callback': {label: loginModuleRecord['sso-callback'], component: SsoCallback},
|
||||||
'reset-pwd': { label: loginModuleRecord['reset-pwd'], component: ResetPwd },
|
'code-login': {label: loginModuleRecord['code-login'], component: CodeLogin},
|
||||||
'bind-wechat': { label: loginModuleRecord['bind-wechat'], component: BindWechat }
|
register: {label: loginModuleRecord.register, component: Register},
|
||||||
|
'reset-pwd': {label: loginModuleRecord['reset-pwd'], component: ResetPwd},
|
||||||
|
'bind-wechat': {label: loginModuleRecord['bind-wechat'], component: BindWechat}
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
|
const activeModule = computed(() => moduleMap[props.module || 'sso-login']);
|
||||||
|
|
||||||
const bgThemeColor = computed(() =>
|
const bgThemeColor = computed(() =>
|
||||||
themeStore.darkMode ? getColorPalette(themeStore.themeColor, 7) : themeStore.themeColor
|
themeStore.darkMode ? getColorPalette(themeStore.themeColor, 7) : themeStore.themeColor
|
||||||
@ -52,11 +56,11 @@ const bgColor = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative size-full flex-center overflow-hidden" :style="{ backgroundColor: bgColor }">
|
<div class="relative size-full flex-center overflow-hidden" :style="{ backgroundColor: bgColor }">
|
||||||
<WaveBg :theme-color="bgThemeColor" />
|
<WaveBg :theme-color="bgThemeColor"/>
|
||||||
<NCard :bordered="false" class="relative z-4 w-auto rd-12px">
|
<NCard :bordered="false" class="relative z-4 w-auto rd-12px">
|
||||||
<div class="w-400px lt-sm:w-300px">
|
<div class="w-400px lt-sm:w-300px">
|
||||||
<header class="flex-y-center justify-between">
|
<header class="flex-y-center justify-between">
|
||||||
<SystemLogo class="text-64px text-primary lt-sm:text-48px" />
|
<SystemLogo class="text-64px text-primary lt-sm:text-48px"/>
|
||||||
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3>
|
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3>
|
||||||
<div class="i-flex-col">
|
<div class="i-flex-col">
|
||||||
<ThemeSchemaSwitch
|
<ThemeSchemaSwitch
|
||||||
@ -77,7 +81,7 @@ const bgColor = computed(() => {
|
|||||||
<h3 class="text-18px text-primary font-medium">{{ $t(activeModule.label) }}</h3>
|
<h3 class="text-18px text-primary font-medium">{{ $t(activeModule.label) }}</h3>
|
||||||
<div class="pt-24px">
|
<div class="pt-24px">
|
||||||
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
||||||
<component :is="activeModule.component" />
|
<component :is="activeModule.component"/>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive } from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import { $t } from '@/locales';
|
import {$t} from '@/locales';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import {useRouterPush} from '@/hooks/common/router';
|
||||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
import {useFormRules, useNaiveForm} from '@/hooks/common/form';
|
||||||
import { useCaptcha } from '@/hooks/business/captcha';
|
import {useCaptcha} from '@/hooks/business/captcha';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'CodeLogin'
|
name: 'CodeLogin'
|
||||||
});
|
});
|
||||||
|
|
||||||
const { toggleLoginModule } = useRouterPush();
|
const {toggleLoginModule} = useRouterPush();
|
||||||
const { formRef, validate } = useNaiveForm();
|
const {formRef, validate} = useNaiveForm();
|
||||||
const { label, isCounting, loading, getCaptcha } = useCaptcha();
|
const {label, isCounting, loading, getCaptcha} = useCaptcha();
|
||||||
|
|
||||||
interface FormModel {
|
interface FormModel {
|
||||||
phone: string;
|
phone: string;
|
||||||
@ -24,7 +24,7 @@ const model: FormModel = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
|
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
|
||||||
const { formRules } = useFormRules();
|
const {formRules} = useFormRules();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phone: formRules.phone,
|
phone: formRules.phone,
|
||||||
@ -40,27 +40,27 @@ async function handleSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
<!-- <NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">-->
|
||||||
<NFormItem path="phone">
|
<!-- <NFormItem path="phone">-->
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<!-- <NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NFormItem path="code">
|
<!-- <NFormItem path="code">-->
|
||||||
<div class="w-full flex-y-center gap-16px">
|
<!-- <div class="w-full flex-y-center gap-16px">-->
|
||||||
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
<!-- <NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />-->
|
||||||
<NButton size="large" :disabled="isCounting" :loading="loading" @click="getCaptcha(model.phone)">
|
<!-- <NButton size="large" :disabled="isCounting" :loading="loading" @click="getCaptcha(model.phone)">-->
|
||||||
{{ label }}
|
<!-- {{ label }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NSpace vertical :size="18" class="w-full">
|
<!-- <NSpace vertical :size="18" class="w-full">-->
|
||||||
<NButton type="primary" size="large" round block @click="handleSubmit">
|
<!-- <NButton type="primary" size="large" round block @click="handleSubmit">-->
|
||||||
{{ $t('common.confirm') }}
|
<!-- {{ $t('common.confirm') }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
<NButton size="large" round block @click="toggleLoginModule('pwd-login')">
|
<!-- <NButton size="large" round block @click="toggleLoginModule('pwd-login')">-->
|
||||||
{{ $t('page.login.common.back') }}
|
<!-- {{ $t('page.login.common.back') }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
</NSpace>
|
<!-- </NSpace>-->
|
||||||
</NForm>
|
<!-- </NForm>-->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive } from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import { $t } from '@/locales';
|
import {$t} from '@/locales';
|
||||||
import { loginModuleRecord } from '@/constants/app';
|
import {useRouterPush} from '@/hooks/common/router';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import {useFormRules, useNaiveForm} from '@/hooks/common/form';
|
||||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
import {useAuthStore} from '@/store/modules/auth';
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'PwdLogin'
|
name: 'PwdLogin'
|
||||||
});
|
});
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const { toggleLoginModule } = useRouterPush();
|
const {toggleLoginModule} = useRouterPush();
|
||||||
const { formRef, validate } = useNaiveForm();
|
const {formRef, validate} = useNaiveForm();
|
||||||
|
|
||||||
interface FormModel {
|
interface FormModel {
|
||||||
userName: string;
|
userName: string;
|
||||||
@ -26,7 +25,7 @@ const model: FormModel = reactive({
|
|||||||
|
|
||||||
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
|
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
|
||||||
// inside computed to make locale reactive, if not apply i18n, you can define it without computed
|
// inside computed to make locale reactive, if not apply i18n, you can define it without computed
|
||||||
const { formRules } = useFormRules();
|
const {formRules} = useFormRules();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userName: formRules.userName,
|
userName: formRules.userName,
|
||||||
@ -75,44 +74,44 @@ async function handleAccountLogin(account: Account) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
<!-- <NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">-->
|
||||||
<NFormItem path="userName">
|
<!-- <NFormItem path="userName">-->
|
||||||
<NInput v-model:value="model.userName" :placeholder="$t('page.login.common.userNamePlaceholder')" />
|
<!-- <NInput v-model:value="model.userName" :placeholder="$t('page.login.common.userNamePlaceholder')" />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NFormItem path="password">
|
<!-- <NFormItem path="password">-->
|
||||||
<NInput
|
<!-- <NInput-->
|
||||||
v-model:value="model.password"
|
<!-- v-model:value="model.password"-->
|
||||||
type="password"
|
<!-- type="password"-->
|
||||||
show-password-on="click"
|
<!-- show-password-on="click"-->
|
||||||
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
<!-- :placeholder="$t('page.login.common.passwordPlaceholder')"-->
|
||||||
/>
|
<!-- />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NSpace vertical :size="24">
|
<!-- <NSpace vertical :size="24">-->
|
||||||
<div class="flex-y-center justify-between">
|
<!-- <div class="flex-y-center justify-between">-->
|
||||||
<NCheckbox>{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
|
<!-- <NCheckbox>{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>-->
|
||||||
<NButton quaternary @click="toggleLoginModule('reset-pwd')">
|
<!-- <NButton quaternary @click="toggleLoginModule('reset-pwd')">-->
|
||||||
{{ $t('page.login.pwdLogin.forgetPassword') }}
|
<!-- {{ $t('page.login.pwdLogin.forgetPassword') }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<NButton type="primary" size="large" round block :loading="authStore.loginLoading" @click="handleSubmit">
|
<!-- <NButton type="primary" size="large" round block :loading="authStore.loginLoading" @click="handleSubmit">-->
|
||||||
{{ $t('common.confirm') }}
|
<!-- {{ $t('common.confirm') }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
<div class="flex-y-center justify-between gap-12px">
|
<!-- <div class="flex-y-center justify-between gap-12px">-->
|
||||||
<NButton class="flex-1" block @click="toggleLoginModule('code-login')">
|
<!-- <NButton class="flex-1" block @click="toggleLoginModule('code-login')">-->
|
||||||
{{ $t(loginModuleRecord['code-login']) }}
|
<!-- {{ $t(loginModuleRecord['code-login']) }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
<NButton class="flex-1" block @click="toggleLoginModule('register')">
|
<!-- <NButton class="flex-1" block @click="toggleLoginModule('register')">-->
|
||||||
{{ $t(loginModuleRecord.register) }}
|
<!-- {{ $t(loginModuleRecord.register) }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<NDivider class="text-14px text-#666 !m-0">{{ $t('page.login.pwdLogin.otherAccountLogin') }}</NDivider>
|
<!-- <NDivider class="text-14px text-#666 !m-0">{{ $t('page.login.pwdLogin.otherAccountLogin') }}</NDivider>-->
|
||||||
<div class="flex-center gap-12px">
|
<!-- <div class="flex-center gap-12px">-->
|
||||||
<NButton v-for="item in accounts" :key="item.key" type="primary" @click="handleAccountLogin(item)">
|
<!-- <NButton v-for="item in accounts" :key="item.key" type="primary" @click="handleAccountLogin(item)">-->
|
||||||
{{ item.label }}
|
<!-- {{ item.label }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</NSpace>
|
<!-- </NSpace>-->
|
||||||
</NForm>
|
<!-- </NForm>-->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive } from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import { $t } from '@/locales';
|
import {$t} from '@/locales';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import {useRouterPush} from '@/hooks/common/router';
|
||||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
import {useFormRules, useNaiveForm} from '@/hooks/common/form';
|
||||||
import { useCaptcha } from '@/hooks/business/captcha';
|
import {useCaptcha} from '@/hooks/business/captcha';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'CodeLogin'
|
name: 'CodeLogin'
|
||||||
});
|
});
|
||||||
|
|
||||||
const { toggleLoginModule } = useRouterPush();
|
const {toggleLoginModule} = useRouterPush();
|
||||||
const { formRef, validate } = useNaiveForm();
|
const {formRef, validate} = useNaiveForm();
|
||||||
const { label, isCounting, loading, getCaptcha } = useCaptcha();
|
const {label, isCounting, loading, getCaptcha} = useCaptcha();
|
||||||
|
|
||||||
interface FormModel {
|
interface FormModel {
|
||||||
phone: string;
|
phone: string;
|
||||||
@ -28,7 +28,7 @@ const model: FormModel = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
|
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
|
||||||
const { formRules, createConfirmPwdRule } = useFormRules();
|
const {formRules, createConfirmPwdRule} = useFormRules();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phone: formRules.phone,
|
phone: formRules.phone,
|
||||||
@ -46,43 +46,43 @@ async function handleSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
<!-- <NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">-->
|
||||||
<NFormItem path="phone">
|
<!-- <NFormItem path="phone">-->
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<!-- <NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NFormItem path="code">
|
<!-- <NFormItem path="code">-->
|
||||||
<div class="w-full flex-y-center gap-16px">
|
<!-- <div class="w-full flex-y-center gap-16px">-->
|
||||||
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
<!-- <NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />-->
|
||||||
<NButton size="large" :disabled="isCounting" :loading="loading" @click="getCaptcha(model.phone)">
|
<!-- <NButton size="large" :disabled="isCounting" :loading="loading" @click="getCaptcha(model.phone)">-->
|
||||||
{{ label }}
|
<!-- {{ label }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NFormItem path="password">
|
<!-- <NFormItem path="password">-->
|
||||||
<NInput
|
<!-- <NInput-->
|
||||||
v-model:value="model.password"
|
<!-- v-model:value="model.password"-->
|
||||||
type="password"
|
<!-- type="password"-->
|
||||||
show-password-on="click"
|
<!-- show-password-on="click"-->
|
||||||
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
<!-- :placeholder="$t('page.login.common.passwordPlaceholder')"-->
|
||||||
/>
|
<!-- />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NFormItem path="confirmPassword">
|
<!-- <NFormItem path="confirmPassword">-->
|
||||||
<NInput
|
<!-- <NInput-->
|
||||||
v-model:value="model.confirmPassword"
|
<!-- v-model:value="model.confirmPassword"-->
|
||||||
type="password"
|
<!-- type="password"-->
|
||||||
show-password-on="click"
|
<!-- show-password-on="click"-->
|
||||||
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
<!-- :placeholder="$t('page.login.common.confirmPasswordPlaceholder')"-->
|
||||||
/>
|
<!-- />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NSpace vertical :size="18" class="w-full">
|
<!-- <NSpace vertical :size="18" class="w-full">-->
|
||||||
<NButton type="primary" size="large" round block @click="handleSubmit">
|
<!-- <NButton type="primary" size="large" round block @click="handleSubmit">-->
|
||||||
{{ $t('common.confirm') }}
|
<!-- {{ $t('common.confirm') }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
<NButton size="large" round block @click="toggleLoginModule('pwd-login')">
|
<!-- <NButton size="large" round block @click="toggleLoginModule('pwd-login')">-->
|
||||||
{{ $t('page.login.common.back') }}
|
<!-- {{ $t('page.login.common.back') }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
</NSpace>
|
<!-- </NSpace>-->
|
||||||
</NForm>
|
<!-- </NForm>-->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive } from 'vue';
|
import {computed, reactive} from 'vue';
|
||||||
import { $t } from '@/locales';
|
import {$t} from '@/locales';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import {useRouterPush} from '@/hooks/common/router';
|
||||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
import {useFormRules, useNaiveForm} from '@/hooks/common/form';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ResetPwd'
|
name: 'ResetPwd'
|
||||||
});
|
});
|
||||||
|
|
||||||
const { toggleLoginModule } = useRouterPush();
|
const {toggleLoginModule} = useRouterPush();
|
||||||
const { formRef, validate } = useNaiveForm();
|
const {formRef, validate} = useNaiveForm();
|
||||||
|
|
||||||
interface FormModel {
|
interface FormModel {
|
||||||
phone: string;
|
phone: string;
|
||||||
@ -28,7 +28,7 @@ const model: FormModel = reactive({
|
|||||||
type RuleRecord = Partial<Record<keyof FormModel, App.Global.FormRule[]>>;
|
type RuleRecord = Partial<Record<keyof FormModel, App.Global.FormRule[]>>;
|
||||||
|
|
||||||
const rules = computed<RuleRecord>(() => {
|
const rules = computed<RuleRecord>(() => {
|
||||||
const { formRules, createConfirmPwdRule } = useFormRules();
|
const {formRules, createConfirmPwdRule} = useFormRules();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phone: formRules.phone,
|
phone: formRules.phone,
|
||||||
@ -45,38 +45,38 @@ async function handleSubmit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
|
<!-- <NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">-->
|
||||||
<NFormItem path="phone">
|
<!-- <NFormItem path="phone">-->
|
||||||
<NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
|
<!-- <NInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NFormItem path="code">
|
<!-- <NFormItem path="code">-->
|
||||||
<NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
|
<!-- <NInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NFormItem path="password">
|
<!-- <NFormItem path="password">-->
|
||||||
<NInput
|
<!-- <NInput-->
|
||||||
v-model:value="model.password"
|
<!-- v-model:value="model.password"-->
|
||||||
type="password"
|
<!-- type="password"-->
|
||||||
show-password-on="click"
|
<!-- show-password-on="click"-->
|
||||||
:placeholder="$t('page.login.common.passwordPlaceholder')"
|
<!-- :placeholder="$t('page.login.common.passwordPlaceholder')"-->
|
||||||
/>
|
<!-- />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NFormItem path="confirmPassword">
|
<!-- <NFormItem path="confirmPassword">-->
|
||||||
<NInput
|
<!-- <NInput-->
|
||||||
v-model:value="model.confirmPassword"
|
<!-- v-model:value="model.confirmPassword"-->
|
||||||
type="password"
|
<!-- type="password"-->
|
||||||
show-password-on="click"
|
<!-- show-password-on="click"-->
|
||||||
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
|
<!-- :placeholder="$t('page.login.common.confirmPasswordPlaceholder')"-->
|
||||||
/>
|
<!-- />-->
|
||||||
</NFormItem>
|
<!-- </NFormItem>-->
|
||||||
<NSpace vertical :size="18" class="w-full">
|
<!-- <NSpace vertical :size="18" class="w-full">-->
|
||||||
<NButton type="primary" size="large" round block @click="handleSubmit">
|
<!-- <NButton type="primary" size="large" round block @click="handleSubmit">-->
|
||||||
{{ $t('common.confirm') }}
|
<!-- {{ $t('common.confirm') }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
<NButton size="large" round block @click="toggleLoginModule('pwd-login')">
|
<!-- <NButton size="large" round block @click="toggleLoginModule('pwd-login')">-->
|
||||||
{{ $t('page.login.common.back') }}
|
<!-- {{ $t('page.login.common.back') }}-->
|
||||||
</NButton>
|
<!-- </NButton>-->
|
||||||
</NSpace>
|
<!-- </NSpace>-->
|
||||||
</NForm>
|
<!-- </NForm>-->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
68
src/views/_builtin/login/modules/sso-callback.vue
Normal file
68
src/views/_builtin/login/modules/sso-callback.vue
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {onBeforeMount} from 'vue';
|
||||||
|
import {useRoute} from 'vue-router';
|
||||||
|
import {useRouterPush} from '@/hooks/common/router';
|
||||||
|
import {useAuthStore} from '@/store/modules/auth';
|
||||||
|
import {$t} from '@/locales';
|
||||||
|
import {SsoAuthor} from '@/enum';
|
||||||
|
import {useRouteStore} from '@/store/modules/route';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'SsoCallback'
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const routeStore = useRouteStore();
|
||||||
|
const routerPush = useRouterPush();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
const success = await authStore.loginSso(
|
||||||
|
String(route.params.author),
|
||||||
|
String(route.query.code),
|
||||||
|
String(route.query.state)
|
||||||
|
);
|
||||||
|
if (success) {
|
||||||
|
routerPush.redirectFromLogin();
|
||||||
|
if (routeStore.isInitAuthRoute) {
|
||||||
|
window.$notification?.success({
|
||||||
|
title: $t('page.login.common.loginSuccess'),
|
||||||
|
content: $t('page.login.common.welcomeBack', {userName: authStore.userInfo.userName}),
|
||||||
|
duration: 4500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
routerPush.toLogin();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {toggleLoginModule} = useRouterPush();
|
||||||
|
|
||||||
|
async function toggleSsoLogin(author: string) {
|
||||||
|
const url = await authStore.fetchSsoUrl(author);
|
||||||
|
if (url) {
|
||||||
|
window.open(url, '_self');
|
||||||
|
} else {
|
||||||
|
routerPush.toLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NSpace vertical :size="18" class="w-full">
|
||||||
|
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
|
<NGi v-for="item in SsoAuthor" :key="item">
|
||||||
|
<NButton size="large" type="info" round secondary block @click="toggleSsoLogin(item)">
|
||||||
|
{{ item }}
|
||||||
|
</NButton>
|
||||||
|
</NGi>
|
||||||
|
</NGrid>
|
||||||
|
<NButton size="large" round block @click="toggleLoginModule('pwd-login')">
|
||||||
|
{{ $t('page.login.common.back') }}
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
||||||
|
<style scoped></style>
|
43
src/views/_builtin/login/modules/sso-login.vue
Normal file
43
src/views/_builtin/login/modules/sso-login.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useRouterPush} from '@/hooks/common/router';
|
||||||
|
|
||||||
|
import {SsoAuthor} from '@/enum';
|
||||||
|
import {useAuthStore} from '@/store/modules/auth';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'SsoLogin'
|
||||||
|
});
|
||||||
|
|
||||||
|
const {toggleLoginModule} = useRouterPush();
|
||||||
|
|
||||||
|
const ssoAuthor = SsoAuthor;
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const {toLogin} = useRouterPush();
|
||||||
|
|
||||||
|
async function toggleSsoLogin(author: string) {
|
||||||
|
const url = await authStore.fetchSsoUrl(author);
|
||||||
|
console.log(url)
|
||||||
|
if (url) {
|
||||||
|
window.open(url, '_self');
|
||||||
|
} else {
|
||||||
|
toLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NSpace vertical :size="18" class="w-full">
|
||||||
|
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
|
<NGi v-for="item in ssoAuthor" :key="item">
|
||||||
|
<NButton size="large" type="info" round secondary block @click="toggleSsoLogin(item)">
|
||||||
|
{{ item }}
|
||||||
|
</NButton>
|
||||||
|
</NGi>
|
||||||
|
</NGrid>
|
||||||
|
<!-- <NButton size="large" round block @click="toggleLoginModule('pwd-login')">-->
|
||||||
|
<!-- {{ $t('page.login.common.back') }}-->
|
||||||
|
<!-- </NButton>-->
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
Loading…
Reference in New Issue
Block a user