From 08aafeffcb5825b44f728f803d966638117cfff1 Mon Sep 17 00:00:00 2001
From: Mplan
Date: Sun, 31 May 2026 15:21:22 +0800
Subject: [PATCH 1/9] fix: fix password reset recovery flow
---
src/views/auth/ResetPasswordView.vue | 114 +++++++++++++++++++--------
1 file changed, 81 insertions(+), 33 deletions(-)
diff --git a/src/views/auth/ResetPasswordView.vue b/src/views/auth/ResetPasswordView.vue
index 5b47139..0c4bfe6 100644
--- a/src/views/auth/ResetPasswordView.vue
+++ b/src/views/auth/ResetPasswordView.vue
@@ -1,27 +1,50 @@
@@ -74,21 +105,38 @@ async function handleResetPassword() {
- 返回登录
+
+
{{ countdown }} 秒后自动跳转登录页面...
+
返回登录
+
+
+
+
+
Password Recovery
设置新密码
-
请输入新的登录密码,提交后当前重置链接会失效。
+
+ {{
+ state === 'invalid'
+ ? '当前重置链接不可用,请重新发送密码重置邮件。'
+ : '请输入新的登录密码,提交后当前重置链接会失效。'
+ }}
+
--
2.50.1 (Apple Git-155)
From 0e2f24288f678192278a61cd32c90f668c7d08b0 Mon Sep 17 00:00:00 2001
From: Mplan
Date: Sun, 31 May 2026 15:38:38 +0800
Subject: [PATCH 2/9] fix: handle auth email callbacks explicitly
---
src/lib/supabase.ts | 7 ++-
src/views/auth/AuthConfirmView.vue | 33 ++++++++++----
src/views/auth/ResetPasswordView.vue | 65 ++++++++++++++++++++++++----
3 files changed, 87 insertions(+), 18 deletions(-)
diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts
index 11bd985..95820f9 100644
--- a/src/lib/supabase.ts
+++ b/src/lib/supabase.ts
@@ -7,4 +7,9 @@ if (!supabaseUrl || !supabaseKey) {
throw new Error('Missing Supabase environment variables')
}
-export const supabase = createClient(supabaseUrl, supabaseKey)
+export const supabase = createClient(supabaseUrl, supabaseKey, {
+ auth: {
+ // Email confirmation and password recovery are handled by route components.
+ detectSessionInUrl: false,
+ },
+})
diff --git a/src/views/auth/AuthConfirmView.vue b/src/views/auth/AuthConfirmView.vue
index 6be76a2..f6fac75 100644
--- a/src/views/auth/AuthConfirmView.vue
+++ b/src/views/auth/AuthConfirmView.vue
@@ -22,32 +22,47 @@ function startCountdown() {
onMounted(async () => {
try {
+ const search = new URLSearchParams(window.location.search)
const hash = window.location.hash.slice(1)
const params = new URLSearchParams(hash)
+ const code = search.get('code')
const accessToken = params.get('access_token')
const refreshToken = params.get('refresh_token')
const type = params.get('type')
- const error = params.get('error')
+ const error = search.get('error') ?? params.get('error')
if (error) {
state.value = 'failed'
return
}
- if (type === 'signup' && accessToken && refreshToken) {
+ if (code || (type === 'signup' && accessToken && refreshToken)) {
const confirmClient = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
+ {
+ auth: {
+ detectSessionInUrl: false,
+ },
+ },
)
- const { error: setSessionError } = await confirmClient.auth.setSession({
- access_token: accessToken,
- refresh_token: refreshToken,
- })
+ if (code) {
+ const { error: exchangeError } = await confirmClient.auth.exchangeCodeForSession(code)
+ if (exchangeError) {
+ state.value = 'failed'
+ return
+ }
+ } else {
+ const { error: setSessionError } = await confirmClient.auth.setSession({
+ access_token: accessToken!,
+ refresh_token: refreshToken!,
+ })
- if (setSessionError) {
- state.value = 'failed'
- return
+ if (setSessionError) {
+ state.value = 'failed'
+ return
+ }
}
await confirmClient.auth.signOut()
diff --git a/src/views/auth/ResetPasswordView.vue b/src/views/auth/ResetPasswordView.vue
index 0c4bfe6..ba2669e 100644
--- a/src/views/auth/ResetPasswordView.vue
+++ b/src/views/auth/ResetPasswordView.vue
@@ -23,6 +23,20 @@ function showInvalidRecoveryLink() {
state.value = 'invalid'
}
+function getRecoveryParams() {
+ const search = new URLSearchParams(window.location.search)
+ const hash = new URLSearchParams(window.location.hash.slice(1))
+
+ return {
+ code: search.get('code'),
+ accessToken: hash.get('access_token'),
+ refreshToken: hash.get('refresh_token'),
+ type: hash.get('type'),
+ error: search.get('error') ?? hash.get('error'),
+ errorDescription: search.get('error_description') ?? hash.get('error_description'),
+ }
+}
+
function getResetErrorMessage(value: unknown) {
const message = value instanceof Error ? value.message : ''
if (
@@ -47,6 +61,47 @@ function startCountdown() {
}, 1000)
}
+async function initializeRecoverySession() {
+ const {
+ code,
+ accessToken,
+ refreshToken,
+ type,
+ error: recoveryError,
+ errorDescription,
+ } = getRecoveryParams()
+
+ if (recoveryError || errorDescription) {
+ showInvalidRecoveryLink()
+ return
+ }
+
+ try {
+ if (code) {
+ const { error } = await supabase.auth.exchangeCodeForSession(code)
+ if (error) throw error
+ } else if (type === 'recovery' && accessToken && refreshToken) {
+ const { error } = await supabase.auth.setSession({
+ access_token: accessToken,
+ refresh_token: refreshToken,
+ })
+ if (error) throw error
+ }
+
+ const { data: { session }, error } = await supabase.auth.getSession()
+ if (error || !session?.user) {
+ showInvalidRecoveryLink()
+ return
+ }
+
+ window.history.replaceState(null, '', window.location.pathname)
+ state.value = 'ready'
+ } catch (e) {
+ error.value = getResetErrorMessage(e)
+ state.value = 'invalid'
+ }
+}
+
async function handleResetPassword() {
error.value = ''
@@ -85,14 +140,8 @@ async function handleResetPassword() {
}
}
-onMounted(async () => {
- const { data: { session }, error: sessionError } = await supabase.auth.getSession()
- if (sessionError || !session?.user) {
- showInvalidRecoveryLink()
- return
- }
-
- state.value = 'ready'
+onMounted(() => {
+ void initializeRecoverySession()
})
onUnmounted(() => {
--
2.50.1 (Apple Git-155)
From b6c3653b64fee3ed47cb30ed9e2e2851a717e335 Mon Sep 17 00:00:00 2001
From: Mplan
Date: Sun, 31 May 2026 15:51:11 +0800
Subject: [PATCH 3/9] fix: redesign password recovery flow
---
src/lib/authEmail.ts | 152 ++++++++++++++++++++++
src/router/index.ts | 6 +
src/stores/auth.ts | 7 +-
src/views/auth/AuthConfirmView.vue | 58 ++-------
src/views/auth/ForgotPasswordView.vue | 174 ++++++++++++++++++++++++++
src/views/auth/LoginView.vue | 55 ++------
src/views/auth/ResetPasswordView.vue | 71 ++++-------
7 files changed, 380 insertions(+), 143 deletions(-)
create mode 100644 src/lib/authEmail.ts
create mode 100644 src/views/auth/ForgotPasswordView.vue
diff --git a/src/lib/authEmail.ts b/src/lib/authEmail.ts
new file mode 100644
index 0000000..bbf3d6d
--- /dev/null
+++ b/src/lib/authEmail.ts
@@ -0,0 +1,152 @@
+import {
+ createClient,
+ type EmailOtpType,
+ type Session,
+ type SupabaseClient,
+} from '@supabase/supabase-js'
+
+const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
+const supabaseKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
+
+const knownEmailOtpTypes = new Set([
+ 'signup',
+ 'invite',
+ 'magiclink',
+ 'recovery',
+ 'email_change',
+ 'email',
+])
+
+type AuthCallbackParams = {
+ accessToken: string | null
+ code: string | null
+ error: string | null
+ errorCode: string | null
+ errorDescription: string | null
+ refreshToken: string | null
+ tokenHash: string | null
+ type: EmailOtpType | null
+}
+
+type ResolveEmailAuthCallbackOptions = {
+ allowedTypes: EmailOtpType[]
+ client: SupabaseClient
+}
+
+type ResolveEmailAuthCallbackResult =
+ | {
+ ok: true
+ session: Session | null
+ type: EmailOtpType | null
+ }
+ | {
+ ok: false
+ error: string
+ }
+
+function parseEmailOtpType(value: string | null): EmailOtpType | null {
+ if (!value || !knownEmailOtpTypes.has(value as EmailOtpType)) {
+ return null
+ }
+
+ return value as EmailOtpType
+}
+
+export function readAuthCallbackParams(): AuthCallbackParams {
+ const search = new URLSearchParams(window.location.search)
+ const hash = new URLSearchParams(window.location.hash.slice(1))
+
+ return {
+ code: search.get('code'),
+ tokenHash: search.get('token_hash') ?? hash.get('token_hash'),
+ accessToken: hash.get('access_token'),
+ refreshToken: hash.get('refresh_token'),
+ type: parseEmailOtpType(search.get('type') ?? hash.get('type')),
+ error: search.get('error') ?? hash.get('error'),
+ errorCode: search.get('error_code') ?? hash.get('error_code'),
+ errorDescription: search.get('error_description') ?? hash.get('error_description'),
+ }
+}
+
+export function clearAuthCallbackUrl() {
+ window.history.replaceState(null, '', window.location.pathname)
+}
+
+export function createDetachedAuthClient() {
+ return createClient(supabaseUrl, supabaseKey, {
+ auth: {
+ autoRefreshToken: false,
+ detectSessionInUrl: false,
+ persistSession: false,
+ },
+ })
+}
+
+export async function resolveEmailAuthCallback(
+ options: ResolveEmailAuthCallbackOptions,
+): Promise {
+ const { client, allowedTypes } = options
+ const params = readAuthCallbackParams()
+ const allowedTypeSet = new Set(allowedTypes)
+
+ if (params.error || params.errorCode || params.errorDescription) {
+ return {
+ ok: false,
+ error: params.errorDescription || '链接无效或已过期。',
+ }
+ }
+
+ try {
+ let session: Session | null = null
+
+ if (params.tokenHash && params.type && allowedTypeSet.has(params.type)) {
+ const { data, error } = await client.auth.verifyOtp({
+ token_hash: params.tokenHash,
+ type: params.type,
+ })
+ if (error) throw error
+ session = data.session
+ } else if (params.code) {
+ const { data, error } = await client.auth.exchangeCodeForSession(params.code)
+ if (error) throw error
+ session = data.session
+ } else if (
+ params.accessToken
+ && params.refreshToken
+ && params.type
+ && allowedTypeSet.has(params.type)
+ ) {
+ const { data, error } = await client.auth.setSession({
+ access_token: params.accessToken,
+ refresh_token: params.refreshToken,
+ })
+ if (error) throw error
+ session = data.session
+ } else {
+ return {
+ ok: false,
+ error: '链接无效或已过期。',
+ }
+ }
+
+ if (!session) {
+ const { data, error } = await client.auth.getSession()
+ if (error) throw error
+ session = data.session
+ }
+
+ clearAuthCallbackUrl()
+
+ return {
+ ok: true,
+ session,
+ type: params.type,
+ }
+ } catch (error) {
+ const message = error instanceof Error ? error.message : '链接无效或已过期。'
+ return {
+ ok: false,
+ error: message,
+ }
+ }
+}
diff --git a/src/router/index.ts b/src/router/index.ts
index c5d3601..1fc6435 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -27,6 +27,12 @@ const router = createRouter({
component: () => import('@/views/auth/RegisterView.vue'),
meta: { title: '注册', noindex: true },
},
+ {
+ path: '/forgot-password',
+ name: 'forgot-password',
+ component: () => import('@/views/auth/ForgotPasswordView.vue'),
+ meta: { title: '忘记密码', noindex: true },
+ },
{
path: '/auth/confirm',
name: 'auth-confirm',
diff --git a/src/stores/auth.ts b/src/stores/auth.ts
index cc0fcab..fa6b96f 100644
--- a/src/stores/auth.ts
+++ b/src/stores/auth.ts
@@ -118,7 +118,12 @@ export const useAuthStore = defineStore('auth', () => {
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`,
})
- if (error) throw error
+ if (error) {
+ if (error.message.includes('rate limit')) {
+ throw new Error('发送过于频繁,请稍后再试。')
+ }
+ throw error
+ }
}
async function initialize() {
diff --git a/src/views/auth/AuthConfirmView.vue b/src/views/auth/AuthConfirmView.vue
index f6fac75..911b892 100644
--- a/src/views/auth/AuthConfirmView.vue
+++ b/src/views/auth/AuthConfirmView.vue
@@ -2,7 +2,7 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { NButton, NCard, NResult, NSpin } from 'naive-ui'
-import { createClient } from '@supabase/supabase-js'
+import { createDetachedAuthClient, resolveEmailAuthCallback } from '@/lib/authEmail'
const router = useRouter()
@@ -22,58 +22,20 @@ function startCountdown() {
onMounted(async () => {
try {
- const search = new URLSearchParams(window.location.search)
- const hash = window.location.hash.slice(1)
- const params = new URLSearchParams(hash)
- const code = search.get('code')
- const accessToken = params.get('access_token')
- const refreshToken = params.get('refresh_token')
- const type = params.get('type')
- const error = search.get('error') ?? params.get('error')
+ const confirmClient = createDetachedAuthClient()
+ const result = await resolveEmailAuthCallback({
+ client: confirmClient,
+ allowedTypes: ['signup', 'email'],
+ })
- if (error) {
+ if (!result.ok) {
state.value = 'failed'
return
}
- if (code || (type === 'signup' && accessToken && refreshToken)) {
- const confirmClient = createClient(
- import.meta.env.VITE_SUPABASE_URL,
- import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
- {
- auth: {
- detectSessionInUrl: false,
- },
- },
- )
-
- if (code) {
- const { error: exchangeError } = await confirmClient.auth.exchangeCodeForSession(code)
- if (exchangeError) {
- state.value = 'failed'
- return
- }
- } else {
- const { error: setSessionError } = await confirmClient.auth.setSession({
- access_token: accessToken!,
- refresh_token: refreshToken!,
- })
-
- if (setSessionError) {
- state.value = 'failed'
- return
- }
- }
-
- await confirmClient.auth.signOut()
-
- window.history.replaceState(null, '', window.location.pathname)
- state.value = 'success'
- startCountdown()
- return
- }
-
- state.value = 'failed'
+ await confirmClient.auth.signOut()
+ state.value = 'success'
+ startCountdown()
} catch {
state.value = 'failed'
}
diff --git a/src/views/auth/ForgotPasswordView.vue b/src/views/auth/ForgotPasswordView.vue
new file mode 100644
index 0000000..d4ad0ec
--- /dev/null
+++ b/src/views/auth/ForgotPasswordView.vue
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+ Password Recovery
+
+ 重新拿回你的
+ OpenCloud 账号
+
+
+ 输入注册邮箱后,我们会发送一封密码重置邮件。打开邮件中的链接,即可进入新的密码设置页面。
+
+
+
+
Step 1
+
发送邮件
+
使用注册邮箱请求重置链接
+
+
+
Step 2
+
设置新密码
+
在专用页面完成新密码更新
+
+
+
+
+
+
+
+
+
+ 目标邮箱:
+ {{ email }}
+
+
+
+ {{ resendButtonLabel }}
+
+
+ 返回登录
+
+
+
+
+
+
+
+
+
Reset Request
+
忘记密码
+
输入你的注册邮箱,我们会发送一封密码重置邮件。
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+ {{ loading ? '发送中...' : '发送重置邮件' }}
+
+
+
+
+
如果你已经记起密码,可以直接返回登录。
+
+ 返回登录
+
+
+
+
+
+
+
diff --git a/src/views/auth/LoginView.vue b/src/views/auth/LoginView.vue
index 9d0ca8c..5c34dca 100644
--- a/src/views/auth/LoginView.vue
+++ b/src/views/auth/LoginView.vue
@@ -8,13 +8,10 @@ const authStore = useAuthStore()
const router = useRouter()
const route = useRoute()
-const email = ref('')
+const email = ref(typeof route.query.email === 'string' ? route.query.email : '')
const password = ref('')
const error = ref('')
-const resetMessage = ref('')
-const resetMode = ref(false)
const loading = ref(false)
-const resetLoading = ref(false)
async function handleLogin() {
error.value = ''
@@ -29,26 +26,6 @@ async function handleLogin() {
loading.value = false
}
}
-
-async function handleSendResetEmail() {
- error.value = ''
- resetMessage.value = ''
-
- if (!email.value) {
- error.value = '请输入需要重置密码的邮箱。'
- return
- }
-
- resetLoading.value = true
- try {
- await authStore.sendPasswordReset(email.value)
- resetMessage.value = '重置密码邮件已发送,请查收邮箱。'
- } catch (e: unknown) {
- error.value = e instanceof Error ? e.message : '重置邮件发送失败,请稍后重试'
- } finally {
- resetLoading.value = false
- }
-}
@@ -84,7 +61,7 @@ async function handleSendResetEmail() {
输入邮箱和密码,继续你的天空档案。
-
+
-
+
-
- 输入注册邮箱,我们会发送一封密码重置邮件。点击邮件链接后即可设置新密码。
-
-
{{ error }}
-
- {{ resetMessage }}
-
-
-
- {{ resetLoading ? '发送中...' : '发送重置邮件' }}
-
-
- {{ loading ? '登录中...' : '登录' }}
-
+ {{ loading ? '登录中...' : '登录' }}
@@ -139,13 +103,12 @@ async function handleSendResetEmail() {
没有账号?
去注册
-
+ 忘记密码?
+
diff --git a/src/views/auth/ResetPasswordView.vue b/src/views/auth/ResetPasswordView.vue
index ba2669e..82c6303 100644
--- a/src/views/auth/ResetPasswordView.vue
+++ b/src/views/auth/ResetPasswordView.vue
@@ -2,6 +2,7 @@
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { NAlert, NButton, NCard, NForm, NFormItem, NInput, NResult, NSpin } from 'naive-ui'
+import { resolveEmailAuthCallback } from '@/lib/authEmail'
import { supabase } from '@/lib/supabase'
const router = useRouter()
@@ -23,20 +24,6 @@ function showInvalidRecoveryLink() {
state.value = 'invalid'
}
-function getRecoveryParams() {
- const search = new URLSearchParams(window.location.search)
- const hash = new URLSearchParams(window.location.hash.slice(1))
-
- return {
- code: search.get('code'),
- accessToken: hash.get('access_token'),
- refreshToken: hash.get('refresh_token'),
- type: hash.get('type'),
- error: search.get('error') ?? hash.get('error'),
- errorDescription: search.get('error_description') ?? hash.get('error_description'),
- }
-}
-
function getResetErrorMessage(value: unknown) {
const message = value instanceof Error ? value.message : ''
if (
@@ -44,10 +31,16 @@ function getResetErrorMessage(value: unknown) {
|| message.includes('session_not_found')
|| message.includes('refresh_token_not_found')
|| message.includes('Invalid Refresh Token')
+ || message.includes('otp_expired')
+ || message.includes('expired')
) {
return '密码重置链接无效或已过期,请重新发送邮件。'
}
+ if (message.includes('Password should be at least')) {
+ return '新密码至少需要 6 位。'
+ }
+
return message || '密码重置失败,请稍后重试。'
}
@@ -62,44 +55,18 @@ function startCountdown() {
}
async function initializeRecoverySession() {
- const {
- code,
- accessToken,
- refreshToken,
- type,
- error: recoveryError,
- errorDescription,
- } = getRecoveryParams()
+ const result = await resolveEmailAuthCallback({
+ client: supabase,
+ allowedTypes: ['recovery'],
+ })
- if (recoveryError || errorDescription) {
- showInvalidRecoveryLink()
+ if (!result.ok || !result.session?.user) {
+ error.value = getResetErrorMessage(result.ok ? '链接无效或已过期。' : result.error)
+ state.value = 'invalid'
return
}
- try {
- if (code) {
- const { error } = await supabase.auth.exchangeCodeForSession(code)
- if (error) throw error
- } else if (type === 'recovery' && accessToken && refreshToken) {
- const { error } = await supabase.auth.setSession({
- access_token: accessToken,
- refresh_token: refreshToken,
- })
- if (error) throw error
- }
-
- const { data: { session }, error } = await supabase.auth.getSession()
- if (error || !session?.user) {
- showInvalidRecoveryLink()
- return
- }
-
- window.history.replaceState(null, '', window.location.pathname)
- state.value = 'ready'
- } catch (e) {
- error.value = getResetErrorMessage(e)
- state.value = 'invalid'
- }
+ state.value = 'ready'
}
async function handleResetPassword() {
@@ -215,6 +182,14 @@ onUnmounted(() => {
{{ error }}
+
+ 返回重新发送重置邮件
+
+
Date: Sun, 31 May 2026 16:12:52 +0800
Subject: [PATCH 4/9] fix: keep users signed in after password reset
---
src/views/auth/ResetPasswordView.vue | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/views/auth/ResetPasswordView.vue b/src/views/auth/ResetPasswordView.vue
index 82c6303..4580f6c 100644
--- a/src/views/auth/ResetPasswordView.vue
+++ b/src/views/auth/ResetPasswordView.vue
@@ -49,7 +49,7 @@ function startCountdown() {
countdown.value--
if (countdown.value <= 0) {
if (countdownTimer) clearInterval(countdownTimer)
- router.push('/login')
+ router.push('/')
}
}, 1000)
}
@@ -92,7 +92,6 @@ async function handleResetPassword() {
const { error: updateError } = await supabase.auth.updateUser({ password: password.value })
if (updateError) throw updateError
- await supabase.auth.signOut()
window.history.replaceState(null, '', window.location.pathname)
countdown.value = 5
state.value = 'success'
@@ -124,12 +123,12 @@ onUnmounted(() => {
v-if="state === 'success'"
status="success"
title="密码已重置"
- description="现在可以使用新密码登录。"
+ description="你已使用新密码完成更新,正在返回地图页。"
>
-
{{ countdown }} 秒后自动跳转登录页面...
-
返回登录
+
{{ countdown }} 秒后自动跳转地图页面...
+
进入地图
--
2.50.1 (Apple Git-155)
From eaa475a5e267e3822faf7664cc25d9cf9c9bd7f1 Mon Sep 17 00:00:00 2001
From: Mplan
Date: Sun, 31 May 2026 16:22:13 +0800
Subject: [PATCH 5/9] feat: gate upload access behind auth notice
---
src/router/index.ts | 13 ++++++-
src/views/map/MapView.vue | 10 ++++--
src/views/system/AuthRequiredView.vue | 50 +++++++++++++++++++++++++++
3 files changed, 69 insertions(+), 4 deletions(-)
create mode 100644 src/views/system/AuthRequiredView.vue
diff --git a/src/router/index.ts b/src/router/index.ts
index 1fc6435..da44c1c 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -120,6 +120,12 @@ const router = createRouter({
component: () => import('@/views/system/ForbiddenView.vue'),
meta: { title: '无权访问', noindex: true },
},
+ {
+ path: '/401',
+ name: 'auth-required',
+ component: () => import('@/views/system/AuthRequiredView.vue'),
+ meta: { title: '需要登录', noindex: true },
+ },
{
path: '/:pathMatch(.*)*',
name: 'not-found',
@@ -136,7 +142,12 @@ router.beforeEach((to, _from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
- next({ name: 'login', query: { redirect: to.fullPath } })
+ next({
+ name: 'auth-required',
+ query: {
+ redirect: to.fullPath,
+ },
+ })
} else if (to.meta.requiresAdmin && !authStore.isAdmin) {
next({ name: 'forbidden' })
} else if ((to.name === 'login' || to.name === 'register') && authStore.isLoggedIn) {
diff --git a/src/views/map/MapView.vue b/src/views/map/MapView.vue
index 5ed6484..2461b70 100644
--- a/src/views/map/MapView.vue
+++ b/src/views/map/MapView.vue
@@ -5,6 +5,7 @@ import MiniLocationMap from '@/components/cloud/MiniLocationMap.vue'
import QuickUploadModal from '@/components/cloud/QuickUploadModal.vue'
import { supabase } from '@/lib/supabase'
import { loadAMap } from '@/lib/amap'
+import { useAuthStore } from '@/stores/auth'
import { NIcon } from 'naive-ui'
import { Adjustments, Calendar, CloudUpload, Refresh, Map, Satellite, X } from '@vicons/tabler'
@@ -24,6 +25,7 @@ interface CloudMarkerData {
}
const mapEl = ref()
+const authStore = useAuthStore()
const previewCloud = ref(null)
const satelliteOn = ref(false)
const statusText = ref('加载中...')
@@ -70,7 +72,6 @@ const archiveTitle = computed(() => {
if (mapMode.value === 'realtime') return '实时'
return archiveKind.value === 'day' ? archiveDay.value : archiveMonth.value
})
-
function getMinuteOfDay(date: Date) {
return date.getHours() * 60 + date.getMinutes()
}
@@ -344,6 +345,7 @@ function toggleSat() {
}
function openQuickUpload() {
+ if (authStore.loading || !authStore.isLoggedIn) return
quickUploadOpen.value = true
}
@@ -446,9 +448,11 @@ onUnmounted(() => {