|
|
@@ -1,98 +1,252 @@
|
|
1
|
1
|
<template>
|
|
2
|
|
- <div class="login">
|
|
3
|
|
- <el-card class="login-card">
|
|
4
|
|
- <h1>{{ T('Login') }}</h1>
|
|
5
|
|
- <el-form label-width="100px">
|
|
6
|
|
- <el-form-item :label=" T('Username') ">
|
|
7
|
|
- <el-input v-model="form.username"></el-input>
|
|
|
2
|
+ <div class="login-container">
|
|
|
3
|
+ <div class="login-card">
|
|
|
4
|
+ <img src="@/assets/logo.png" alt="logo" class="login-logo" />
|
|
|
5
|
+
|
|
|
6
|
+ <el-form label-position="top" class="login-form">
|
|
|
7
|
+ <el-form-item :label="T('Username')">
|
|
|
8
|
+ <el-input v-model="form.username" class="login-input"></el-input>
|
|
8
|
9
|
</el-form-item>
|
|
9
|
|
- <el-form-item :label=" T('Password') ">
|
|
10
|
|
- <el-input v-model="form.password" type="password" @keyup.enter.native="login" show-password></el-input>
|
|
|
10
|
+
|
|
|
11
|
+ <el-form-item :label="T('Password')">
|
|
|
12
|
+ <el-input v-model="form.password" type="password" @keyup.enter.native="login" show-password
|
|
|
13
|
+ class="login-input"></el-input>
|
|
11
|
14
|
</el-form-item>
|
|
|
15
|
+
|
|
12
|
16
|
<el-form-item>
|
|
13
|
|
- <el-button @click="login" type="primary">{{ T('Login') }}</el-button>
|
|
|
17
|
+ <el-button @click="login" type="primary" class="login-button">{{ T('Login') }}</el-button>
|
|
14
|
18
|
</el-form-item>
|
|
15
|
19
|
</el-form>
|
|
16
|
|
- </el-card>
|
|
|
20
|
+
|
|
|
21
|
+ <div class="divider" v-if="options.length > 0">
|
|
|
22
|
+ <span>{{ T('or login in with') }}</span>
|
|
|
23
|
+ </div>
|
|
|
24
|
+
|
|
|
25
|
+ <div class="oidc-options">
|
|
|
26
|
+ <div v-for="(option, index) in options" :key="index" class="oidc-option">
|
|
|
27
|
+ <el-button @click="handleOIDCLogin(option.name)" class="oidc-btn">
|
|
|
28
|
+ <img :src="getProviderImage(option.name)" alt="provider" class="oidc-icon" />
|
|
|
29
|
+ {{ T(option.name) }}
|
|
|
30
|
+ </el-button>
|
|
|
31
|
+ </div>
|
|
|
32
|
+ </div>
|
|
|
33
|
+ </div>
|
|
17
|
34
|
</div>
|
|
18
|
35
|
</template>
|
|
19
|
36
|
|
|
20
|
37
|
<script setup>
|
|
21
|
|
- import { defineComponent, reactive } from 'vue'
|
|
22
|
|
- import { useUserStore } from '@/store/user'
|
|
23
|
|
- import { ElMessage } from 'element-plus'
|
|
24
|
|
- import { useRoute, useRouter } from 'vue-router'
|
|
25
|
|
- import { T } from '@/utils/i18n'
|
|
26
|
|
-
|
|
27
|
|
- const userStore = useUserStore()
|
|
28
|
|
- const route = useRoute()
|
|
29
|
|
- const router = useRouter()
|
|
30
|
|
-
|
|
31
|
|
- let platform = window.navigator.platform
|
|
32
|
|
- if (navigator.platform.indexOf('Mac') === 0) {
|
|
33
|
|
- platform = 'mac'
|
|
34
|
|
- } else if (navigator.platform.indexOf('Win') === 0) {
|
|
35
|
|
- platform = 'windows'
|
|
36
|
|
- } else if (navigator.platform.indexOf('Linux armv') === 0) {
|
|
37
|
|
- platform = 'android'
|
|
38
|
|
- } else if (navigator.platform.indexOf('Linux') === 0) {
|
|
39
|
|
- platform = 'linux'
|
|
|
38
|
+import { reactive, onMounted, ref } from 'vue';
|
|
|
39
|
+import { useUserStore } from '@/store/user'
|
|
|
40
|
+import { ElMessage } from 'element-plus';
|
|
|
41
|
+import { T } from '@/utils/i18n';
|
|
|
42
|
+import { useRoute, useRouter } from 'vue-router';
|
|
|
43
|
+import { loginOptions, oidcAuth, oidcQuery } from '@/api/login';
|
|
|
44
|
+import { getCode, removeCode } from '@/utils/auth'
|
|
|
45
|
+
|
|
|
46
|
+const oauthInfo = ref({})
|
|
|
47
|
+const userStore = useUserStore()
|
|
|
48
|
+const route = useRoute()
|
|
|
49
|
+const router = useRouter()
|
|
|
50
|
+const options = reactive([]); // 存储 OIDC 登录选项
|
|
|
51
|
+
|
|
|
52
|
+let platform = window.navigator.platform
|
|
|
53
|
+if (navigator.platform.indexOf('Mac') === 0) {
|
|
|
54
|
+ platform = 'mac'
|
|
|
55
|
+} else if (navigator.platform.indexOf('Win') === 0) {
|
|
|
56
|
+ platform = 'windows'
|
|
|
57
|
+} else if (navigator.platform.indexOf('Linux armv') === 0) {
|
|
|
58
|
+ platform = 'android'
|
|
|
59
|
+} else if (navigator.platform.indexOf('Linux') === 0) {
|
|
|
60
|
+ platform = 'linux'
|
|
|
61
|
+}
|
|
|
62
|
+const userAgent = navigator.userAgent;
|
|
|
63
|
+let browser = 'Unknown Browser';
|
|
|
64
|
+if (/chrome|crios/i.test(userAgent)) browser = 'Chrome';
|
|
|
65
|
+else if (/firefox|fxios/i.test(userAgent)) browser = 'Firefox';
|
|
|
66
|
+else if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) browser = 'Safari';
|
|
|
67
|
+else if (/edg/i.test(userAgent)) browser = 'Edge';
|
|
|
68
|
+
|
|
|
69
|
+const form = reactive({
|
|
|
70
|
+ username: '',
|
|
|
71
|
+ password: '',
|
|
|
72
|
+ platform: platform,
|
|
|
73
|
+})
|
|
|
74
|
+
|
|
|
75
|
+const redirect = route.query?.redirect
|
|
|
76
|
+const login = async () => {
|
|
|
77
|
+ const res = await userStore.login(form)
|
|
|
78
|
+ if (res) {
|
|
|
79
|
+ ElMessage.success(T('LoginSuccess'))
|
|
|
80
|
+ router.push({ path: redirect || '/', replace: true })
|
|
40
|
81
|
}
|
|
|
82
|
+}
|
|
|
83
|
+
|
|
|
84
|
+const handleOIDCLogin = (provider) => {
|
|
|
85
|
+ userStore.oidc(provider, platform, browser)
|
|
|
86
|
+};
|
|
41
|
87
|
|
|
42
|
|
- const form = reactive({
|
|
43
|
|
- username: '',
|
|
44
|
|
- password: '',
|
|
45
|
|
- platform: platform,
|
|
46
|
|
- })
|
|
47
|
|
- const redirect = route.query?.redirect
|
|
48
|
|
- const login = async () => {
|
|
49
|
|
- const res = await userStore.login(form)
|
|
|
88
|
+const providerImageMap = {
|
|
|
89
|
+ google: '/google.png',
|
|
|
90
|
+ github: '/github.png',
|
|
|
91
|
+ oidc: '/oidc.png',
|
|
|
92
|
+ webauth: '/webauth.png',
|
|
|
93
|
+ default: '/default.png',
|
|
|
94
|
+};
|
|
|
95
|
+
|
|
|
96
|
+const getProviderImage = (provider) => {
|
|
|
97
|
+ return providerImageMap[provider] || providerImageMap.default;
|
|
|
98
|
+};
|
|
|
99
|
+
|
|
|
100
|
+const loadLoginOptions = async () => {
|
|
|
101
|
+ try {
|
|
|
102
|
+ const res = await loginOptions().catch(() => []);
|
|
|
103
|
+ if (!Array.isArray(res) || !res.length) return console.warn('No valid response received');
|
|
|
104
|
+
|
|
|
105
|
+ const jsonPart = res[0].split('/')[1];
|
|
|
106
|
+ if (!jsonPart) return console.error('Invalid input string:', res[0]);
|
|
|
107
|
+
|
|
|
108
|
+ // const ops = JSON.parse(jsonPart).map(option => ({ name: option.name }));
|
|
|
109
|
+ // 不确定怎么处理webauth,不显示
|
|
|
110
|
+ // 解析 JSON,并过滤掉 "webauth" 类型的选项
|
|
|
111
|
+ const ops = JSON.parse(jsonPart)
|
|
|
112
|
+ .filter(option => option.name !== "webauth") // 排除 "webauth" 类型的选项
|
|
|
113
|
+ .map(option => ({ name: option.name })); // 创建新的对象数组
|
|
|
114
|
+ if (!ops.length) return;
|
|
|
115
|
+
|
|
|
116
|
+ options.push(...ops);
|
|
|
117
|
+ } catch (error) {
|
|
|
118
|
+ console.error('Error loading login options:', error.message);
|
|
|
119
|
+ }
|
|
|
120
|
+};
|
|
|
121
|
+
|
|
|
122
|
+onMounted(async () => {
|
|
|
123
|
+ const code = getCode();
|
|
|
124
|
+ if (code) {
|
|
|
125
|
+ // 如果code存在,进行query获取user info
|
|
|
126
|
+ const res = await userStore.query(code)
|
|
50
|
127
|
if (res) {
|
|
|
128
|
+ // 删除code,确保跳转之前对code进行清楚
|
|
|
129
|
+ removeCode()
|
|
51
|
130
|
ElMessage.success(T('LoginSuccess'))
|
|
52
|
131
|
router.push({ path: redirect || '/', replace: true })
|
|
53
|
132
|
}
|
|
|
133
|
+ } else {
|
|
|
134
|
+ // 如果code不存在, 现实登陆页面
|
|
|
135
|
+ loadLoginOptions(); // 组件挂载后调用登录选项加载函数
|
|
54
|
136
|
}
|
|
|
137
|
+});
|
|
55
|
138
|
</script>
|
|
56
|
139
|
|
|
57
|
140
|
<style scoped lang="scss">
|
|
58
|
|
-.login {
|
|
59
|
|
- width: 100vw;
|
|
|
141
|
+.login-container {
|
|
|
142
|
+ display: flex;
|
|
|
143
|
+ justify-content: center;
|
|
|
144
|
+ align-items: center;
|
|
60
|
145
|
height: 100vh;
|
|
61
|
146
|
background-color: #2d3a4b;
|
|
62
|
|
- padding-top: 25vh;
|
|
63
|
|
- box-sizing: border-box;
|
|
|
147
|
+ padding: 20px;
|
|
|
148
|
+}
|
|
64
|
149
|
|
|
65
|
|
- .tips {
|
|
66
|
|
- font-size: 12px;
|
|
67
|
|
- color: #fff;
|
|
68
|
|
- margin-left: 60px;
|
|
|
150
|
+.login-card {
|
|
|
151
|
+ width: 360px;
|
|
|
152
|
+ background-color: #283342;
|
|
|
153
|
+ padding: 40px;
|
|
|
154
|
+ border-radius: 8px;
|
|
|
155
|
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
|
156
|
+ text-align: center;
|
|
|
157
|
+}
|
|
|
158
|
+
|
|
|
159
|
+h1 {
|
|
|
160
|
+ margin-bottom: 20px;
|
|
|
161
|
+ font-size: 24px;
|
|
|
162
|
+ font-weight: bold;
|
|
|
163
|
+}
|
|
|
164
|
+
|
|
|
165
|
+.login-form {
|
|
|
166
|
+ margin-bottom: 20px;
|
|
|
167
|
+}
|
|
|
168
|
+
|
|
|
169
|
+.login-input {
|
|
|
170
|
+ width: 100%;
|
|
|
171
|
+}
|
|
|
172
|
+
|
|
|
173
|
+.login-button {
|
|
|
174
|
+ width: 100%;
|
|
|
175
|
+ height: 40px;
|
|
|
176
|
+ margin-bottom: 20px;
|
|
|
177
|
+}
|
|
|
178
|
+
|
|
|
179
|
+.divider {
|
|
|
180
|
+ display: flex;
|
|
|
181
|
+ align-items: center;
|
|
|
182
|
+ margin: 20px 0;
|
|
|
183
|
+ font-size: 14px;
|
|
|
184
|
+ color: #888;
|
|
|
185
|
+
|
|
|
186
|
+ &::before,
|
|
|
187
|
+ &::after {
|
|
|
188
|
+ content: '';
|
|
|
189
|
+ flex: 1;
|
|
|
190
|
+ height: 1px;
|
|
|
191
|
+ background-color: #ddd;
|
|
69
|
192
|
}
|
|
70
|
193
|
|
|
71
|
|
- .login-card {
|
|
72
|
|
- max-width: 500px;
|
|
73
|
|
- background-color: #283342;
|
|
74
|
|
- color: #fff;
|
|
75
|
|
- border: none;
|
|
76
|
|
- margin: 0 auto;
|
|
|
194
|
+ &::before {
|
|
|
195
|
+ margin-right: 10px;
|
|
|
196
|
+ }
|
|
77
|
197
|
|
|
78
|
|
- .el-form-item {
|
|
|
198
|
+ &::after {
|
|
|
199
|
+ margin-left: 10px;
|
|
|
200
|
+ }
|
|
|
201
|
+}
|
|
|
202
|
+
|
|
|
203
|
+.oidc-options {
|
|
|
204
|
+ display: flex;
|
|
|
205
|
+ flex-direction: column;
|
|
|
206
|
+ gap: 10px;
|
|
|
207
|
+}
|
|
79
|
208
|
|
|
80
|
|
- ::v-deep(.el-form-item__label) {
|
|
81
|
|
- color: #fff;
|
|
82
|
|
- }
|
|
|
209
|
+.oidc-btn {
|
|
|
210
|
+ display: flex;
|
|
|
211
|
+ align-items: center;
|
|
|
212
|
+ justify-content: center;
|
|
|
213
|
+ gap: 10px;
|
|
|
214
|
+ width: 100%;
|
|
|
215
|
+ height: 50px;
|
|
|
216
|
+ background-color: white;
|
|
|
217
|
+ border: 1px solid #ddd;
|
|
|
218
|
+ border-radius: 4px;
|
|
|
219
|
+ color: black;
|
|
|
220
|
+ font-size: 14px;
|
|
|
221
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
222
|
+}
|
|
83
|
223
|
|
|
84
|
|
- .el-input {
|
|
|
224
|
+.oidc-icon {
|
|
|
225
|
+ width: 24px;
|
|
|
226
|
+ height: 24px;
|
|
|
227
|
+}
|
|
85
|
228
|
|
|
86
|
|
- ::v-deep(.el-input__wrapper) {
|
|
87
|
|
- border: 1px solid rgba(255, 255, 255, 0.1);
|
|
88
|
|
- background: transparent;
|
|
89
|
|
- }
|
|
|
229
|
+.login-logo {
|
|
|
230
|
+ width: 80px;
|
|
|
231
|
+ height: 80px;
|
|
|
232
|
+ margin: 0 auto 20px;
|
|
|
233
|
+ display: block;
|
|
|
234
|
+}
|
|
|
235
|
+
|
|
|
236
|
+.el-form-item {
|
|
|
237
|
+ ::v-deep(.el-form-item__label) {
|
|
|
238
|
+ color: #fff;
|
|
|
239
|
+ }
|
|
|
240
|
+
|
|
|
241
|
+ .el-input {
|
|
|
242
|
+ ::v-deep(.el-input__wrapper) {
|
|
|
243
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
244
|
+ background: transparent;
|
|
|
245
|
+ }
|
|
90
|
246
|
|
|
91
|
|
- ::v-deep(input) {
|
|
92
|
|
- color: #fff;
|
|
93
|
|
- }
|
|
94
|
|
- }
|
|
|
247
|
+ ::v-deep(input) {
|
|
|
248
|
+ color: #fff;
|
|
95
|
249
|
}
|
|
96
|
250
|
}
|
|
97
|
251
|
}
|
|
98
|
|
-</style>
|
|
|
252
|
+</style>
|