|
|
@@ -1,7 +1,7 @@
|
|
1
|
1
|
<template>
|
|
2
|
2
|
<div class="login-container">
|
|
3
|
3
|
<div class="login-card">
|
|
4
|
|
- <img src="@/assets/logo.png" alt="logo" class="login-logo" />
|
|
|
4
|
+ <img src="@/assets/logo.png" alt="logo" class="login-logo"/>
|
|
5
|
5
|
|
|
6
|
6
|
<el-form label-position="top" class="login-form">
|
|
7
|
7
|
<el-form-item :label="T('Username')">
|
|
|
@@ -10,9 +10,15 @@
|
|
10
|
10
|
|
|
11
|
11
|
<el-form-item :label="T('Password')">
|
|
12
|
12
|
<el-input v-model="form.password" type="password" @keyup.enter.native="login" show-password
|
|
13
|
|
- class="login-input"></el-input>
|
|
|
13
|
+ class="login-input"></el-input>
|
|
|
14
|
+ </el-form-item>
|
|
|
15
|
+ <el-form-item :label="T('Captcha')" v-if="captchaCode">
|
|
|
16
|
+ <el-input v-model="form.captcha" @keyup.enter.native="login" class="login-input captcha-input">
|
|
|
17
|
+ <template #append>
|
|
|
18
|
+ <img :src="captchaCode.b64" @click="loadCaptcha" class="captcha" alt="captcha"/>
|
|
|
19
|
+ </template>
|
|
|
20
|
+ </el-input>
|
|
14
|
21
|
</el-form-item>
|
|
15
|
|
-
|
|
16
|
22
|
<el-form-item>
|
|
17
|
23
|
<el-button @click="login" type="primary" class="login-button">{{ T('Login') }}</el-button>
|
|
18
|
24
|
<el-button v-if="allowRegister" @click="register" class="login-button">{{ T('Register') }}</el-button>
|
|
|
@@ -26,7 +32,7 @@
|
|
26
|
32
|
<div class="oidc-options">
|
|
27
|
33
|
<div v-for="(option, index) in options" :key="index" class="oidc-option">
|
|
28
|
34
|
<el-button @click="handleOIDCLogin(option.name)" class="oidc-btn">
|
|
29
|
|
- <img :src="getProviderImage(option.name)" alt="provider" class="oidc-icon" />
|
|
|
35
|
+ <img :src="getProviderImage(option.name)" alt="provider" class="oidc-icon"/>
|
|
30
|
36
|
<span>{{ T(option.name) }}</span>
|
|
31
|
37
|
</el-button>
|
|
32
|
38
|
</div>
|
|
|
@@ -36,106 +42,121 @@
|
|
36
|
42
|
</template>
|
|
37
|
43
|
|
|
38
|
44
|
<script setup>
|
|
39
|
|
-import { reactive, onMounted, ref } from 'vue';
|
|
40
|
|
-import { useUserStore } from '@/store/user'
|
|
41
|
|
-import { ElMessage } from 'element-plus';
|
|
42
|
|
-import { T } from '@/utils/i18n';
|
|
43
|
|
-import { useRoute, useRouter } from 'vue-router';
|
|
44
|
|
-import { loginOptions } from '@/api/login';
|
|
45
|
|
-import { getCode, removeCode } from '@/utils/auth'
|
|
46
|
|
-
|
|
47
|
|
-const oauthInfo = ref({})
|
|
48
|
|
-const userStore = useUserStore()
|
|
49
|
|
-const route = useRoute()
|
|
50
|
|
-const router = useRouter()
|
|
51
|
|
-const options = reactive([]); // 存储 OIDC 登录选项
|
|
52
|
|
-
|
|
53
|
|
-let platform = window.navigator.platform
|
|
54
|
|
-if (navigator.platform.indexOf('Mac') === 0) {
|
|
55
|
|
- platform = 'mac'
|
|
56
|
|
-} else if (navigator.platform.indexOf('Win') === 0) {
|
|
57
|
|
- platform = 'windows'
|
|
58
|
|
-} else if (navigator.platform.indexOf('Linux armv') === 0) {
|
|
59
|
|
- platform = 'android'
|
|
60
|
|
-} else if (navigator.platform.indexOf('Linux') === 0) {
|
|
61
|
|
- platform = 'linux'
|
|
62
|
|
-}
|
|
63
|
|
-const userAgent = navigator.userAgent;
|
|
64
|
|
-let browser = 'Unknown Browser';
|
|
65
|
|
-if (/chrome|crios/i.test(userAgent)) browser = 'Chrome';
|
|
66
|
|
-else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox';
|
|
67
|
|
-else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari';
|
|
68
|
|
-else if (/edg/i.test(userAgent)) browser = 'Edge';
|
|
69
|
|
-
|
|
70
|
|
-const form = reactive({
|
|
71
|
|
- username: '',
|
|
72
|
|
- password: '',
|
|
73
|
|
- platform: platform,
|
|
74
|
|
-})
|
|
75
|
|
-
|
|
76
|
|
-const redirect = route.query?.redirect
|
|
77
|
|
-const login = async () => {
|
|
78
|
|
- const res = await userStore.login(form)
|
|
79
|
|
- if (res) {
|
|
80
|
|
- ElMessage.success(T('LoginSuccess'))
|
|
81
|
|
- router.push({ path: redirect || '/', replace: true })
|
|
|
45
|
+ import { reactive, onMounted, ref } from 'vue'
|
|
|
46
|
+ import { useUserStore } from '@/store/user'
|
|
|
47
|
+ import { ElMessage } from 'element-plus'
|
|
|
48
|
+ import { T } from '@/utils/i18n'
|
|
|
49
|
+ import { useRoute, useRouter } from 'vue-router'
|
|
|
50
|
+ import { loginOptions, captcha } from '@/api/login'
|
|
|
51
|
+ import { getCode, removeCode } from '@/utils/auth'
|
|
|
52
|
+
|
|
|
53
|
+ const oauthInfo = ref({})
|
|
|
54
|
+ const userStore = useUserStore()
|
|
|
55
|
+ const route = useRoute()
|
|
|
56
|
+ const router = useRouter()
|
|
|
57
|
+ const options = reactive([]) // 存储 OIDC 登录选项
|
|
|
58
|
+
|
|
|
59
|
+ let platform = window.navigator.platform
|
|
|
60
|
+ if (navigator.platform.indexOf('Mac') === 0) {
|
|
|
61
|
+ platform = 'mac'
|
|
|
62
|
+ } else if (navigator.platform.indexOf('Win') === 0) {
|
|
|
63
|
+ platform = 'windows'
|
|
|
64
|
+ } else if (navigator.platform.indexOf('Linux armv') === 0) {
|
|
|
65
|
+ platform = 'android'
|
|
|
66
|
+ } else if (navigator.platform.indexOf('Linux') === 0) {
|
|
|
67
|
+ platform = 'linux'
|
|
82
|
68
|
}
|
|
83
|
|
-}
|
|
84
|
|
-
|
|
85
|
|
-const handleOIDCLogin = (provider) => {
|
|
86
|
|
- userStore.oidc(provider, platform, browser)
|
|
87
|
|
-};
|
|
88
|
|
-
|
|
89
|
|
-import googleImage from '@/assets/google.png';
|
|
90
|
|
-import githubImage from '@/assets/github.png';
|
|
91
|
|
-import oidcImage from '@/assets/oidc.png';
|
|
92
|
|
-import webauthImage from '@/assets/webauth.png';
|
|
93
|
|
-import defaultImage from '@/assets/oidc.png';
|
|
94
|
|
-
|
|
95
|
|
-const providerImageMap = {
|
|
96
|
|
- google: googleImage,
|
|
97
|
|
- github: githubImage,
|
|
98
|
|
- oidc: oidcImage,
|
|
99
|
|
- // WebAuth: webauthImage,
|
|
100
|
|
- default: defaultImage,
|
|
101
|
|
-};
|
|
102
|
|
-
|
|
103
|
|
-const getProviderImage = (provider) => {
|
|
104
|
|
- return providerImageMap[provider.toLowerCase()] || providerImageMap.default;
|
|
105
|
|
-};
|
|
106
|
|
-
|
|
107
|
|
-const allowRegister = ref(false)
|
|
108
|
|
-const loadLoginOptions = async () => {
|
|
109
|
|
- try {
|
|
110
|
|
- const res = await loginOptions().catch(_ => false);
|
|
111
|
|
- if(!res || !res.data) return console.error('No valid response received');
|
|
112
|
|
- res.data.ops.map(option => (options.push({ name: option }))); // 创建新的对象数组
|
|
113
|
|
- allowRegister.value = res.data.register
|
|
114
|
|
- } catch (error) {
|
|
115
|
|
- console.error('Error loading login options:', error.message);
|
|
116
|
|
- }
|
|
117
|
|
-};
|
|
118
|
|
-
|
|
119
|
|
-onMounted(async () => {
|
|
120
|
|
- const code = getCode();
|
|
121
|
|
- if (code) {
|
|
122
|
|
- // 如果code存在,进行query获取user info
|
|
123
|
|
- const res = await userStore.query(code)
|
|
124
|
|
- if (res) {
|
|
125
|
|
- // 删除code,确保跳转之前对code进行清楚
|
|
126
|
|
- removeCode()
|
|
|
69
|
+ const userAgent = navigator.userAgent
|
|
|
70
|
+ let browser = 'Unknown Browser'
|
|
|
71
|
+ if (/chrome|crios/i.test(userAgent)) browser = 'Chrome'
|
|
|
72
|
+ else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox'
|
|
|
73
|
+ else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari'
|
|
|
74
|
+ else if (/edg/i.test(userAgent)) browser = 'Edge'
|
|
|
75
|
+
|
|
|
76
|
+ const form = reactive({
|
|
|
77
|
+ username: '',
|
|
|
78
|
+ password: '',
|
|
|
79
|
+ platform: platform,
|
|
|
80
|
+ captcha: '',
|
|
|
81
|
+ })
|
|
|
82
|
+
|
|
|
83
|
+ const captchaCode = ref('')
|
|
|
84
|
+ const redirect = route.query?.redirect
|
|
|
85
|
+ const login = async () => {
|
|
|
86
|
+ const res = await userStore.login(form).catch(e => e)
|
|
|
87
|
+ if (!res.code) {
|
|
127
|
88
|
ElMessage.success(T('LoginSuccess'))
|
|
128
|
89
|
router.push({ path: redirect || '/', replace: true })
|
|
129
|
90
|
}
|
|
130
|
|
- } else {
|
|
131
|
|
- // 如果code不存在, 现实登陆页面
|
|
132
|
|
- loadLoginOptions(); // 组件挂载后调用登录选项加载函数
|
|
|
91
|
+ if (res.code === 110 && captchaCode.value === '') {
|
|
|
92
|
+ // need captcha
|
|
|
93
|
+ loadCaptcha()
|
|
|
94
|
+ }
|
|
133
|
95
|
}
|
|
134
|
|
-});
|
|
135
|
96
|
|
|
136
|
|
-const register = () => {
|
|
137
|
|
- router.push('/register')
|
|
138
|
|
-}
|
|
|
97
|
+ const loadCaptcha = async () => {
|
|
|
98
|
+ const captchaRes = await captcha().catch(_ => false)
|
|
|
99
|
+ console.log(captchaRes)
|
|
|
100
|
+ captchaCode.value = captchaRes.data.captcha
|
|
|
101
|
+ }
|
|
|
102
|
+
|
|
|
103
|
+ const handleOIDCLogin = (provider) => {
|
|
|
104
|
+ userStore.oidc(provider, platform, browser)
|
|
|
105
|
+ }
|
|
|
106
|
+
|
|
|
107
|
+ import googleImage from '@/assets/google.png'
|
|
|
108
|
+ import githubImage from '@/assets/github.png'
|
|
|
109
|
+ import oidcImage from '@/assets/oidc.png'
|
|
|
110
|
+ import webauthImage from '@/assets/webauth.png'
|
|
|
111
|
+ import defaultImage from '@/assets/oidc.png'
|
|
|
112
|
+
|
|
|
113
|
+ const providerImageMap = {
|
|
|
114
|
+ google: googleImage,
|
|
|
115
|
+ github: githubImage,
|
|
|
116
|
+ oidc: oidcImage,
|
|
|
117
|
+ // WebAuth: webauthImage,
|
|
|
118
|
+ default: defaultImage,
|
|
|
119
|
+ }
|
|
|
120
|
+
|
|
|
121
|
+ const getProviderImage = (provider) => {
|
|
|
122
|
+ return providerImageMap[provider.toLowerCase()] || providerImageMap.default
|
|
|
123
|
+ }
|
|
|
124
|
+
|
|
|
125
|
+ const allowRegister = ref(false)
|
|
|
126
|
+ const loadLoginOptions = async () => {
|
|
|
127
|
+ try {
|
|
|
128
|
+ const res = await loginOptions().catch(_ => false)
|
|
|
129
|
+ if (!res || !res.data) return console.error('No valid response received')
|
|
|
130
|
+ res.data.ops.map(option => (options.push({ name: option }))) // 创建新的对象数组
|
|
|
131
|
+ allowRegister.value = res.data.register
|
|
|
132
|
+ if (res.data.need_captcha) {
|
|
|
133
|
+ loadCaptcha()
|
|
|
134
|
+ }
|
|
|
135
|
+ } catch (error) {
|
|
|
136
|
+ console.error('Error loading login options:', error.message)
|
|
|
137
|
+ }
|
|
|
138
|
+ }
|
|
|
139
|
+
|
|
|
140
|
+ onMounted(async () => {
|
|
|
141
|
+ const code = getCode()
|
|
|
142
|
+ if (code) {
|
|
|
143
|
+ // 如果code存在,进行query获取user info
|
|
|
144
|
+ const res = await userStore.query(code)
|
|
|
145
|
+ if (res) {
|
|
|
146
|
+ // 删除code,确保跳转之前对code进行清楚
|
|
|
147
|
+ removeCode()
|
|
|
148
|
+ ElMessage.success(T('LoginSuccess'))
|
|
|
149
|
+ router.push({ path: redirect || '/', replace: true })
|
|
|
150
|
+ }
|
|
|
151
|
+ } else {
|
|
|
152
|
+ // 如果code不存在, 现实登陆页面
|
|
|
153
|
+ loadLoginOptions() // 组件挂载后调用登录选项加载函数
|
|
|
154
|
+ }
|
|
|
155
|
+ })
|
|
|
156
|
+
|
|
|
157
|
+ const register = () => {
|
|
|
158
|
+ router.push('/register')
|
|
|
159
|
+ }
|
|
139
|
160
|
</script>
|
|
140
|
161
|
|
|
141
|
162
|
<style scoped lang="scss">
|
|
|
@@ -170,6 +191,17 @@ h1 {
|
|
170
|
191
|
|
|
171
|
192
|
.login-input {
|
|
172
|
193
|
width: 100%;
|
|
|
194
|
+ .captcha{
|
|
|
195
|
+ cursor: pointer;
|
|
|
196
|
+ width: 150px;
|
|
|
197
|
+ }
|
|
|
198
|
+}
|
|
|
199
|
+.captcha-input{
|
|
|
200
|
+ :deep(.el-input-group__append) {
|
|
|
201
|
+ border-radius: 5px;
|
|
|
202
|
+ padding: 0;
|
|
|
203
|
+ overflow: hidden;
|
|
|
204
|
+ }
|
|
173
|
205
|
}
|
|
174
|
206
|
|
|
175
|
207
|
.login-button {
|