|
@@ -160,19 +160,18 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 区号选择弹窗 -->
|
|
<!-- 区号选择弹窗 -->
|
|
|
- <van-popup v-model:show="showAreaCode" position="bottom" round>
|
|
|
|
|
- <van-picker
|
|
|
|
|
- :columns="areaCodes"
|
|
|
|
|
- @confirm="onAreaCodeConfirm"
|
|
|
|
|
- @cancel="showAreaCode = false"
|
|
|
|
|
- show-toolbar
|
|
|
|
|
- />
|
|
|
|
|
- </van-popup>
|
|
|
|
|
|
|
+ <van-action-sheet
|
|
|
|
|
+ v-model:show="showAreaCode"
|
|
|
|
|
+ :actions="areaCodeActions"
|
|
|
|
|
+ @select="onAreaCodeSelect"
|
|
|
|
|
+ cancel-text="Cancel"
|
|
|
|
|
+ close-on-click-action
|
|
|
|
|
+ />
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive, onMounted, nextTick } from 'vue';
|
|
|
|
|
|
|
+import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue';
|
|
|
import { useRouter } from 'vue-router';
|
|
import { useRouter } from 'vue-router';
|
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
import { toast } from 'vue-sonner';
|
|
import { toast } from 'vue-sonner';
|
|
@@ -212,8 +211,13 @@ const areaCodes = [
|
|
|
{ text: '+1 USA', value: '1' }
|
|
{ text: '+1 USA', value: '1' }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
-const onAreaCodeConfirm = ({ selectedValues }) => {
|
|
|
|
|
- areaCode.value = selectedValues[0];
|
|
|
|
|
|
|
+const areaCodeActions = areaCodes.map(item => ({
|
|
|
|
|
+ name: item.text,
|
|
|
|
|
+ value: item.value
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+const onAreaCodeSelect = (action: { name: string; value: string }) => {
|
|
|
|
|
+ areaCode.value = action.value;
|
|
|
showAreaCode.value = false;
|
|
showAreaCode.value = false;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -455,35 +459,71 @@ const handleGoogleLogin = () => {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (window.google) {
|
|
|
|
|
- // 使用 Google One Tap 或弹窗登录
|
|
|
|
|
- window.google.accounts.id.prompt((notification: any) => {
|
|
|
|
|
- if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
|
|
|
|
|
- // 如果 One Tap 不显示,使用传统弹窗方式
|
|
|
|
|
- window.google.accounts.oauth2.initCodeClient({
|
|
|
|
|
- client_id: googleClientId.value,
|
|
|
|
|
- scope: 'email profile',
|
|
|
|
|
- callback: (response: any) => {
|
|
|
|
|
- if (response.code) {
|
|
|
|
|
- // 这里可以用 code 换取 token,但我们使用 ID Token 方式更简单
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 使用 OAuth 2.0 隐式授权流程,通过弹窗方式
|
|
|
|
|
+ const redirectUri = window.location.origin + '/google-callback.html';
|
|
|
|
|
+ const scope = 'openid email profile';
|
|
|
|
|
+ const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${googleClientId.value}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=id_token&scope=${encodeURIComponent(scope)}&nonce=${Date.now()}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 打开弹窗
|
|
|
|
|
+ const width = 500;
|
|
|
|
|
+ const height = 600;
|
|
|
|
|
+ const left = (screen.width - width) / 2;
|
|
|
|
|
+ const top = (screen.height - height) / 2;
|
|
|
|
|
+ const popup = window.open(authUrl, 'google-login', `width=${width},height=${height},left=${left},top=${top}`);
|
|
|
|
|
+
|
|
|
|
|
+ // 监听弹窗返回
|
|
|
|
|
+ const timer = setInterval(() => {
|
|
|
|
|
+ if (popup?.closed) {
|
|
|
|
|
+ clearInterval(timer);
|
|
|
|
|
+ googleLoading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 500);
|
|
|
|
|
+
|
|
|
|
|
+ googleLoading.value = true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 监听 Google 弹窗回传的消息
|
|
|
|
|
+const onGoogleMessage = async (event: MessageEvent) => {
|
|
|
|
|
+ if (event.origin !== window.location.origin) return;
|
|
|
|
|
+ if (event.data?.type !== 'google-login' || !event.data?.idToken) return;
|
|
|
|
|
+
|
|
|
|
|
+ googleLoading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const payload = JSON.parse(atob(event.data.idToken.split('.')[1]));
|
|
|
|
|
+ const res = await requestOAuthLogin({
|
|
|
|
|
+ provider: 'google',
|
|
|
|
|
+ openId: payload.sub,
|
|
|
|
|
+ nickname: payload.name,
|
|
|
|
|
+ avatar: payload.picture,
|
|
|
|
|
+ email: payload.email
|
|
|
});
|
|
});
|
|
|
- } else {
|
|
|
|
|
- toast.error('Google SDK 加载失败');
|
|
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ localStorage.setItem('token', res.data.token);
|
|
|
|
|
+ userStore.setUserInfo(res.data.user);
|
|
|
|
|
+ toast.success(t('auth.loginSuccess'));
|
|
|
|
|
+ router.replace('/home');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Google login error:', error);
|
|
|
|
|
+ toast.error('Google 登录失败');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ googleLoading.value = false;
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 组件挂载时初始化 OAuth
|
|
// 组件挂载时初始化 OAuth
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
- // 先检查 Zalo 回调(需要在 OAuth 配置加载前检查,因为 codeVerifier 在 sessionStorage 中)
|
|
|
|
|
|
|
+ window.addEventListener('message', onGoogleMessage);
|
|
|
|
|
+ // 先检查 Zalo 回调
|
|
|
initZaloLogin();
|
|
initZaloLogin();
|
|
|
// 再初始化 OAuth 配置
|
|
// 再初始化 OAuth 配置
|
|
|
initOAuth();
|
|
initOAuth();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ window.removeEventListener('message', onGoogleMessage);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
// 生成随机字符串
|
|
// 生成随机字符串
|
|
|
const generateRandomString = (length: number): string => {
|
|
const generateRandomString = (length: number): string => {
|
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
@@ -607,62 +647,64 @@ const goRegister = () => {
|
|
|
.login-page {
|
|
.login-page {
|
|
|
min-height: 100vh;
|
|
min-height: 100vh;
|
|
|
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
|
|
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
|
|
|
- padding: 60px 24px 40px;
|
|
|
|
|
|
|
+ padding: min(6vw, 30px) min(5.333vw, 25.6px) min(10vw, 48px);
|
|
|
|
|
+ max-width: 500px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.logo-section {
|
|
.logo-section {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
- margin-bottom: 40px;
|
|
|
|
|
|
|
+ margin-bottom: min(5.333vw, 25.6px);
|
|
|
|
|
|
|
|
.logo {
|
|
.logo {
|
|
|
- width: 80px;
|
|
|
|
|
- height: 80px;
|
|
|
|
|
- margin: 0 auto 16px;
|
|
|
|
|
|
|
+ width: min(17.067vw, 80px);
|
|
|
|
|
+ height: min(17.067vw, 80px);
|
|
|
|
|
+ margin: 0 auto min(2.667vw, 12.8px);
|
|
|
background: linear-gradient(135deg, #ffc300 0%, #ff9500 100%);
|
|
background: linear-gradient(135deg, #ffc300 0%, #ff9500 100%);
|
|
|
- border-radius: 20px;
|
|
|
|
|
|
|
+ border-radius: min(4.267vw, 20.48px);
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
|
|
|
|
|
.logo-img {
|
|
.logo-img {
|
|
|
- width: 60px;
|
|
|
|
|
- height: 60px;
|
|
|
|
|
|
|
+ width: min(12.8vw, 60px);
|
|
|
|
|
+ height: min(12.8vw, 60px);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.title {
|
|
.title {
|
|
|
- font-size: 28px;
|
|
|
|
|
|
|
+ font-size: min(5.867vw, 28px);
|
|
|
font-weight: bold;
|
|
font-weight: bold;
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
- margin-bottom: 8px;
|
|
|
|
|
|
|
+ margin-bottom: min(1.067vw, 5.12px);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.subtitle {
|
|
.subtitle {
|
|
|
- font-size: 14px;
|
|
|
|
|
|
|
+ font-size: min(3.733vw, 17.92px);
|
|
|
color: rgba(255, 255, 255, 0.6);
|
|
color: rgba(255, 255, 255, 0.6);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.form-section {
|
|
.form-section {
|
|
|
background: rgba(255, 255, 255, 0.05);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
|
- border-radius: 16px;
|
|
|
|
|
- padding: 24px;
|
|
|
|
|
|
|
+ border-radius: min(4.267vw, 20.48px);
|
|
|
|
|
+ padding: min(5.333vw, 25.6px);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.login-tabs {
|
|
.login-tabs {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- margin-bottom: 24px;
|
|
|
|
|
|
|
+ margin-bottom: min(5.333vw, 25.6px);
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
- border-radius: 8px;
|
|
|
|
|
- padding: 4px;
|
|
|
|
|
|
|
+ border-radius: min(2.133vw, 10.24px);
|
|
|
|
|
+ padding: min(1.067vw, 5.12px);
|
|
|
|
|
|
|
|
.tab-item {
|
|
.tab-item {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
- padding: 10px 0;
|
|
|
|
|
|
|
+ padding: min(2.667vw, 12.8px) 0;
|
|
|
color: rgba(255, 255, 255, 0.6);
|
|
color: rgba(255, 255, 255, 0.6);
|
|
|
- font-size: 14px;
|
|
|
|
|
- border-radius: 6px;
|
|
|
|
|
|
|
+ font-size: min(3.733vw, 17.92px);
|
|
|
|
|
+ border-radius: min(1.6vw, 7.68px);
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
@@ -676,15 +718,16 @@ const goRegister = () => {
|
|
|
|
|
|
|
|
.form-content {
|
|
.form-content {
|
|
|
.input-group {
|
|
.input-group {
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
|
|
+ margin-bottom: min(3.2vw, 15.36px);
|
|
|
|
|
|
|
|
:deep(.van-field) {
|
|
:deep(.van-field) {
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
- border-radius: 8px;
|
|
|
|
|
- padding: 12px 16px;
|
|
|
|
|
|
|
+ border-radius: min(2.133vw, 10.24px);
|
|
|
|
|
+ padding: min(3.2vw, 15.36px) min(4.267vw, 20.48px);
|
|
|
|
|
|
|
|
.van-field__control {
|
|
.van-field__control {
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
|
|
+ font-size: min(3.733vw, 17.92px);
|
|
|
|
|
|
|
|
&::placeholder {
|
|
&::placeholder {
|
|
|
color: rgba(255, 255, 255, 0.4);
|
|
color: rgba(255, 255, 255, 0.4);
|
|
@@ -693,17 +736,19 @@ const goRegister = () => {
|
|
|
|
|
|
|
|
.van-icon {
|
|
.van-icon {
|
|
|
color: rgba(255, 255, 255, 0.6);
|
|
color: rgba(255, 255, 255, 0.6);
|
|
|
|
|
+ font-size: min(4.267vw, 20.48px);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.area-code {
|
|
.area-code {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 4px;
|
|
|
|
|
|
|
+ gap: min(1.067vw, 5.12px);
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
- padding-right: 12px;
|
|
|
|
|
|
|
+ font-size: min(3.733vw, 17.92px);
|
|
|
|
|
+ padding-right: min(3.2vw, 15.36px);
|
|
|
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
- margin-right: 12px;
|
|
|
|
|
|
|
+ margin-right: min(3.2vw, 15.36px);
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -712,27 +757,27 @@ const goRegister = () => {
|
|
|
.forgot-password {
|
|
.forgot-password {
|
|
|
text-align: right;
|
|
text-align: right;
|
|
|
color: #ffc300;
|
|
color: #ffc300;
|
|
|
- font-size: 13px;
|
|
|
|
|
- margin-bottom: 24px;
|
|
|
|
|
|
|
+ font-size: min(3.467vw, 16.64px);
|
|
|
|
|
+ margin-bottom: min(5.333vw, 25.6px);
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.login-btn {
|
|
.login-btn {
|
|
|
- height: 48px;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
|
|
+ height: min(12.8vw, 61.44px);
|
|
|
|
|
+ border-radius: min(2.133vw, 10.24px);
|
|
|
|
|
+ font-size: min(4.267vw, 20.48px);
|
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
|
background: linear-gradient(135deg, #ffc300 0%, #ff9500 100%);
|
|
background: linear-gradient(135deg, #ffc300 0%, #ff9500 100%);
|
|
|
border: none;
|
|
border: none;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.third-party-section {
|
|
.third-party-section {
|
|
|
- margin-top: 32px;
|
|
|
|
|
|
|
+ margin-top: min(6.4vw, 30.72px);
|
|
|
|
|
|
|
|
.divider {
|
|
.divider {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
|
|
+ margin-bottom: min(4.267vw, 20.48px);
|
|
|
|
|
|
|
|
&::before,
|
|
&::before,
|
|
|
&::after {
|
|
&::after {
|
|
@@ -743,9 +788,9 @@ const goRegister = () => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
span {
|
|
span {
|
|
|
- padding: 0 16px;
|
|
|
|
|
|
|
+ padding: 0 min(4.267vw, 20.48px);
|
|
|
color: rgba(255, 255, 255, 0.4);
|
|
color: rgba(255, 255, 255, 0.4);
|
|
|
- font-size: 12px;
|
|
|
|
|
|
|
+ font-size: min(3.2vw, 15.36px);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -753,14 +798,14 @@ const goRegister = () => {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
- gap: 10px;
|
|
|
|
|
- height: 48px;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- font-size: 15px;
|
|
|
|
|
|
|
+ gap: min(2.667vw, 12.8px);
|
|
|
|
|
+ height: min(12.8vw, 61.44px);
|
|
|
|
|
+ border-radius: min(2.133vw, 10.24px);
|
|
|
|
|
+ font-size: min(4vw, 19.2px);
|
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
transition: all 0.3s;
|
|
|
- margin-bottom: 12px;
|
|
|
|
|
|
|
+ margin-bottom: min(3.2vw, 15.36px);
|
|
|
|
|
|
|
|
&.loading {
|
|
&.loading {
|
|
|
pointer-events: none;
|
|
pointer-events: none;
|
|
@@ -803,13 +848,13 @@ const goRegister = () => {
|
|
|
|
|
|
|
|
.register-entry {
|
|
.register-entry {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
- margin-top: 24px;
|
|
|
|
|
|
|
+ margin-top: min(5.333vw, 25.6px);
|
|
|
color: rgba(255, 255, 255, 0.6);
|
|
color: rgba(255, 255, 255, 0.6);
|
|
|
- font-size: 14px;
|
|
|
|
|
|
|
+ font-size: min(3.733vw, 17.92px);
|
|
|
|
|
|
|
|
.link {
|
|
.link {
|
|
|
color: #ffc300;
|
|
color: #ffc300;
|
|
|
- margin-left: 4px;
|
|
|
|
|
|
|
+ margin-left: min(1.067vw, 5.12px);
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -817,11 +862,11 @@ const goRegister = () => {
|
|
|
.telegram-widget-wrapper {
|
|
.telegram-widget-wrapper {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
- margin-bottom: 12px;
|
|
|
|
|
- min-height: 48px;
|
|
|
|
|
|
|
+ margin-bottom: min(3.2vw, 15.36px);
|
|
|
|
|
+ min-height: min(12.8vw, 61.44px);
|
|
|
|
|
|
|
|
iframe {
|
|
iframe {
|
|
|
- border-radius: 8px !important;
|
|
|
|
|
|
|
+ border-radius: min(2.133vw, 10.24px) !important;
|
|
|
width: 100% !important;
|
|
width: 100% !important;
|
|
|
max-width: 100% !important;
|
|
max-width: 100% !important;
|
|
|
}
|
|
}
|