liujunjie 1 неделя назад
Родитель
Сommit
c38aecb9af
3 измененных файлов с 85 добавлено и 27 удалено
  1. 8 2
      src/AppleCallbackPage.vue
  2. 56 17
      src/SimubusLanding.vue
  3. 21 8
      src/style.css

+ 8 - 2
src/AppleCallbackPage.vue

@@ -103,11 +103,17 @@ onMounted(() => {
     return
     return
   }
   }
 
 
+  if (!payload.id_token) {
+    clearStoredAppleAuthState()
+    statusText.value = messages.value.error
+    return
+  }
+
   void socialLogin(
   void socialLogin(
     {
     {
       provider: 'apple',
       provider: 'apple',
-      token: payload.id_token || payload.code,
-      uuid: payload.code || '',
+      token: payload.id_token,
+      uuid: '',
       nickname: '',
       nickname: '',
       avatarUrl: '',
       avatarUrl: '',
       email: payload.user?.email || '',
       email: payload.user?.email || '',

+ 56 - 17
src/SimubusLanding.vue

@@ -3,6 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
 import lottie from 'lottie-web'
 import lottie from 'lottie-web'
 import { clearSession, loadSession, logout, saveSession, socialLogin } from './services/auth'
 import { clearSession, loadSession, logout, saveSession, socialLogin } from './services/auth'
 import { disableGoogleAutoSelect, parseGoogleCredential, renderGoogleSignInButton } from './google-auth.js'
 import { disableGoogleAutoSelect, parseGoogleCredential, renderGoogleSignInButton } from './google-auth.js'
+import { loadHomeScenes } from './services/home-scenes.js'
 import {
 import {
   clearStoredAppleAuthState,
   clearStoredAppleAuthState,
   getStoredAppleAuthState,
   getStoredAppleAuthState,
@@ -18,6 +19,8 @@ const APPLE_CLIENT_ID = import.meta.env.VITE_APPLE_CLIENT_ID || ''
 const APPLE_REDIRECT_URI = import.meta.env.VITE_APPLE_REDIRECT_URI || `${window.location.origin}/auth/apple/callback`
 const APPLE_REDIRECT_URI = import.meta.env.VITE_APPLE_REDIRECT_URI || `${window.location.origin}/auth/apple/callback`
 const APPLE_SCOPE = import.meta.env.VITE_APPLE_SCOPE || 'name email'
 const APPLE_SCOPE = import.meta.env.VITE_APPLE_SCOPE || 'name email'
 const PRO_DESTINATION_URL = '#/pro'
 const PRO_DESTINATION_URL = '#/pro'
+const IOS_DOWNLOAD_URL = 'https://apps.apple.com/app/id6757686667'
+const ANDROID_DOWNLOAD_URL = 'https://play.google.com/store/apps/details?id=com.snkj.simubus.simulation.wiring'
 
 
 const activeQr = ref(null)
 const activeQr = ref(null)
 const previewRefs = ref({})
 const previewRefs = ref({})
@@ -36,21 +39,50 @@ const googleButtonReady = ref(false)
 const authLoading = ref(false)
 const authLoading = ref(false)
 const authError = ref('')
 const authError = ref('')
 const authStatus = ref('')
 const authStatus = ref('')
+const HERO_SCENE_ID = 'Self-locking-belt-with-slight-movement'
+const INTERACTIVE_SCENE_ID = 'Family-dual-control'
+const SIMULATION_SCENE_ID = 'Industrial-self-locking'
 
 
 const languageOptions = [
 const languageOptions = [
   { value: 'en', label: 'English' },
   { value: 'en', label: 'English' },
   { value: 'zh', label: '中文' },
   { value: 'zh', label: '中文' },
 ]
 ]
 
 
-const heroScene = computed(() => scenes.value.find((scene) => scene.id === '自锁带点动') ?? null)
-const interactiveScenes = computed(() => scenes.value.filter((scene) => scene.id === '家庭双控'))
-const simulationScenes = computed(() => scenes.value.filter((scene) => scene.id === '工业自锁'))
+const heroScene = computed(() => scenes.value.find((scene) => scene.id === HERO_SCENE_ID) ?? null)
+const interactiveScenes = computed(() => scenes.value.filter((scene) => scene.id === INTERACTIVE_SCENE_ID))
+const simulationScenes = computed(() => scenes.value.filter((scene) => scene.id === SIMULATION_SCENE_ID))
 const isAuthenticated = computed(() => Boolean(session.value?.token))
 const isAuthenticated = computed(() => Boolean(session.value?.token))
 
 
 function openProDestination() {
 function openProDestination() {
   window.location.hash = PRO_DESTINATION_URL
   window.location.hash = PRO_DESTINATION_URL
 }
 }
 
 
+function isMobileViewport() {
+  return typeof window !== 'undefined' && window.matchMedia('(max-width: 768px)').matches
+}
+
+function setActiveQr(platform) {
+  if (isMobileViewport()) {
+    return
+  }
+
+  activeQr.value = platform
+}
+
+function clearActiveQr() {
+  activeQr.value = null
+}
+
+function handleDownloadClick(platform) {
+  if (!isMobileViewport()) {
+    activeQr.value = platform
+    return
+  }
+
+  const targetUrl = platform === 'ios' ? IOS_DOWNLOAD_URL : ANDROID_DOWNLOAD_URL
+  window.location.href = targetUrl
+}
+
 function setPreviewRef(id, el) {
 function setPreviewRef(id, el) {
   if (el) {
   if (el) {
     previewRefs.value[id] = el
     previewRefs.value[id] = el
@@ -222,6 +254,8 @@ async function mountGoogleButton() {
     await renderGoogleSignInButton(googleButtonRef.value, {
     await renderGoogleSignInButton(googleButtonRef.value, {
       clientId: GOOGLE_CLIENT_ID,
       clientId: GOOGLE_CLIENT_ID,
       locale: currentLocale.value,
       locale: currentLocale.value,
+      width: 320,
+      height: 66,
       callback: handleGoogleCredentialResponse,
       callback: handleGoogleCredentialResponse,
     })
     })
     googleButtonReady.value = true
     googleButtonReady.value = true
@@ -242,9 +276,8 @@ async function finalizeAppleSignIn(response) {
   const authorization = response?.authorization
   const authorization = response?.authorization
   const user = response?.user
   const user = response?.user
   const identityToken = authorization?.id_token || ''
   const identityToken = authorization?.id_token || ''
-  const authorizationCode = authorization?.code || ''
 
 
-  if (!identityToken && !authorizationCode) {
+  if (!identityToken) {
     throw new Error(messages.value.appleMissingCredential)
     throw new Error(messages.value.appleMissingCredential)
   }
   }
 
 
@@ -256,8 +289,8 @@ async function finalizeAppleSignIn(response) {
   const nextSession = await socialLogin(
   const nextSession = await socialLogin(
     {
     {
       provider: 'apple',
       provider: 'apple',
-      token: identityToken || authorizationCode,
-      uuid: profile.sub || authorizationCode || '',
+      token: identityToken,
+      uuid: profile.sub || '',
       nickname: fullName || profile.email || '',
       nickname: fullName || profile.email || '',
       avatarUrl: '',
       avatarUrl: '',
       email: user?.email || profile.email || '',
       email: user?.email || profile.email || '',
@@ -335,7 +368,6 @@ function handleAppleCallbackMessage(event) {
 
 
   void finalizeAppleSignIn({
   void finalizeAppleSignIn({
     authorization: {
     authorization: {
-      code: payload.code || '',
       id_token: payload.id_token || '',
       id_token: payload.id_token || '',
     },
     },
     user: payload.user || null,
     user: payload.user || null,
@@ -366,6 +398,13 @@ async function confirmLogout() {
 }
 }
 
 
 onMounted(async () => {
 onMounted(async () => {
+  try {
+    scenes.value = await loadHomeScenes()
+  } catch (error) {
+    console.error('Failed to load home scenes', error)
+    scenes.value = []
+  }
+
   await mountAllAnimations()
   await mountAllAnimations()
   document.addEventListener('click', handleDocumentClick)
   document.addEventListener('click', handleDocumentClick)
   window.addEventListener('message', handleAppleCallbackMessage)
   window.addEventListener('message', handleAppleCallbackMessage)
@@ -398,8 +437,8 @@ watch(currentLocale, async () => {
 const figma = {
 const figma = {
   map: '/figma-assets/asset-01.svg',
   map: '/figma-assets/asset-01.svg',
   heroImage: '/figma-assets/asset-02.png',
   heroImage: '/figma-assets/asset-02.png',
-  iosQr: '/figma-assets/asset-03.png',
-  androidQr: '/figma-assets/asset-03.png',
+  iosQr: '/ios.png',
+  androidQr: '/google.png',
   logoIcon: '/figma-assets/asset-04.png',
   logoIcon: '/figma-assets/asset-04.png',
   logoText: '/figma-assets/asset-05.svg',
   logoText: '/figma-assets/asset-05.svg',
   footerLogo: '/figma-assets/asset-06.png',
   footerLogo: '/figma-assets/asset-06.png',
@@ -667,15 +706,15 @@ const logoutBodyText = computed(() => {
         </div>
         </div>
 
 
         <div class="download-buttons">
         <div class="download-buttons">
-          <div class="download-item ios" @mouseenter="activeQr = 'ios'" @mouseleave="activeQr = null">
-            <button class="download-button ios" type="button" @focus="activeQr = 'ios'" @blur="activeQr = null">{{ messages.iosDownload }}</button>
+          <div class="download-item ios" @mouseenter="setActiveQr('ios')" @mouseleave="clearActiveQr">
+            <button class="download-button ios" type="button" @focus="setActiveQr('ios')" @blur="clearActiveQr" @click="handleDownloadClick('ios')">{{ messages.iosDownload }}</button>
             <div class="qr-popup" :class="{ visible: activeQr === 'ios' }" data-node-id="2911:2309">
             <div class="qr-popup" :class="{ visible: activeQr === 'ios' }" data-node-id="2911:2309">
               <img :src="figma.iosQr" :alt="messages.iosQrAlt" />
               <img :src="figma.iosQr" :alt="messages.iosQrAlt" />
             </div>
             </div>
           </div>
           </div>
 
 
-          <div class="download-item android" @mouseenter="activeQr = 'android'" @mouseleave="activeQr = null">
-            <button class="download-button android" type="button" @focus="activeQr = 'android'" @blur="activeQr = null">{{ messages.androidDownload }}</button>
+          <div class="download-item android" @mouseenter="setActiveQr('android')" @mouseleave="clearActiveQr">
+            <button class="download-button android" type="button" @focus="setActiveQr('android')" @blur="clearActiveQr" @click="handleDownloadClick('android')">{{ messages.androidDownload }}</button>
             <div class="qr-popup" :class="{ visible: activeQr === 'android' }" data-node-id="2911:2310">
             <div class="qr-popup" :class="{ visible: activeQr === 'android' }" data-node-id="2911:2310">
               <img :src="figma.androidQr" :alt="messages.androidQrAlt" />
               <img :src="figma.androidQr" :alt="messages.androidQrAlt" />
             </div>
             </div>
@@ -803,12 +842,12 @@ const logoutBodyText = computed(() => {
           </button>
           </button>
         </template>
         </template>
         <template v-else>
         <template v-else>
-          <div class="google-signin-slot" :style="{ display: 'flex', justifyContent: 'center', marginTop: '20px' }">
-            <div ref="googleButtonRef" class="google-signin-slot__button" :style="{ width: currentLocale === 'zh' ? '280px' : '300px' }"></div>
+          <div class="google-signin-slot">
+            <div ref="googleButtonRef" class="google-signin-slot__button"></div>
           </div>
           </div>
         </template>
         </template>
 
 
-        <button class="login-modal__signin-option" type="button" :disabled="authLoading" @click="handleAppleClick">
+        <button class="login-modal__signin-option login-modal__signin-option--apple" type="button" :disabled="authLoading" @click="handleAppleClick">
           <img class="login-modal__signin-icon" src="/auth-icons/apple-icon.svg" alt="Apple" />
           <img class="login-modal__signin-icon" src="/auth-icons/apple-icon.svg" alt="Apple" />
           <span>{{ messages.modalApple }}</span>
           <span>{{ messages.modalApple }}</span>
         </button>
         </button>

+ 21 - 8
src/style.css

@@ -717,18 +717,14 @@ img {
 }
 }
 
 
 .google-signin-slot {
 .google-signin-slot {
-  position: relative;
+  display: flex;
+  justify-content: center;
   margin-top: 20px;
   margin-top: 20px;
 }
 }
 
 
 .google-signin-slot__button {
 .google-signin-slot__button {
-  width: 100%;
-  min-height: 66px;
-}
-
-.google-signin-slot__button :deep(div),
-.google-signin-slot__button :deep(iframe) {
-  width: 100% !important;
+  width: 320px;
+  height: 66px;
 }
 }
 
 
 .google-signin-slot__fallback {
 .google-signin-slot__fallback {
@@ -983,6 +979,23 @@ img {
   object-fit: contain;
   object-fit: contain;
 }
 }
 
 
+.login-modal__signin-option--apple {
+  width: 320px;
+  height: 48px;
+  margin: 20px auto 0;
+  gap: 10px;
+  border-color: #dadce0;
+  border-radius: 8px;
+  color: #3c4043;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.login-modal__signin-option--apple .login-modal__signin-icon {
+  width: 18px;
+  height: 18px;
+}
+
 .login-modal__legal {
 .login-modal__legal {
   margin: 54px 0 0;
   margin: 54px 0 0;
   color: #85908c;
   color: #85908c;