feat: 重构用户认证逻辑,添加导航重定向功能,更新相关组件和路由
This commit is contained in:
4
auto-imports.d.ts
vendored
4
auto-imports.d.ts
vendored
@@ -234,6 +234,7 @@ declare global {
|
||||
const useMouseInElement: typeof import('@vueuse/core').useMouseInElement
|
||||
const useMousePressed: typeof import('@vueuse/core').useMousePressed
|
||||
const useMutationObserver: typeof import('@vueuse/core').useMutationObserver
|
||||
const useNavigateToRedirect: typeof import('./src/composables/useNavigateToRedirect').useNavigateToRedirect
|
||||
const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage
|
||||
const useNetwork: typeof import('@vueuse/core').useNetwork
|
||||
const useNow: typeof import('@vueuse/core').useNow
|
||||
@@ -372,7 +373,6 @@ declare module 'vue' {
|
||||
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
||||
readonly beforeApp: UnwrapRef<typeof import('./src/composables/beforeApp')['beforeApp']>
|
||||
readonly clearExpiredCaches: UnwrapRef<typeof import('./src/composables/useStorageCache')['clearExpiredCaches']>
|
||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
||||
@@ -516,7 +516,6 @@ declare module 'vue' {
|
||||
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
|
||||
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
|
||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||
readonly useAuth: UnwrapRef<typeof import('./src/composables/useAuth')['useAuth']>
|
||||
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
|
||||
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
|
||||
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
|
||||
@@ -595,6 +594,7 @@ declare module 'vue' {
|
||||
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
||||
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
|
||||
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
|
||||
readonly useNavigateToRedirect: UnwrapRef<typeof import('./src/composables/useNavigateToRedirect')['useNavigateToRedirect']>
|
||||
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
|
||||
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
|
||||
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { App as CapacitorApp } from "@capacitor/app";
|
||||
|
||||
const { isAuthenticated } = useAuth();
|
||||
const userStore = useUserStore();
|
||||
const { isAuthenticated } = storeToRefs(userStore);
|
||||
const { locale, loadSavedLanguage } = useLanguage();
|
||||
const { initializeWallet } = useWalletStore();
|
||||
const { updateProfile } = useUserStore();
|
||||
|
||||
onMounted(() => {
|
||||
updateProfile();
|
||||
initializeWallet();
|
||||
CapacitorApp.addListener("appStateChange", async ({ isActive }) => {
|
||||
if (isActive && isAuthenticated.value) {
|
||||
await userStore.updateProfile();
|
||||
|
||||
@@ -8,6 +8,13 @@ const client = treaty<App>(window.location.origin, {
|
||||
fetch: {
|
||||
credentials: "include",
|
||||
},
|
||||
headers() {
|
||||
const token = localStorage.getItem("user-token") || "";
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": token ? `Bearer ${token}` : "",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export interface SafeClientOptions {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export function beforeApp() {
|
||||
const { updateProfile } = useUserStore();
|
||||
const { initializeWallet } = useWalletStore();
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
useAuth().then((session) => {
|
||||
if (session) {
|
||||
updateProfile();
|
||||
initializeWallet();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { UnwrapRef } from "vue";
|
||||
import { safeClient } from "@/api";
|
||||
import { authClient } from "@/auth";
|
||||
|
||||
type User = UnwrapRef<ReturnType<typeof authClient.useSession> extends Promise<infer R extends { data: any }> ? R["data"] : never>;
|
||||
|
||||
export function useAuth() {
|
||||
const session = ref<User | null>(null);
|
||||
|
||||
if (session.value === undefined) {
|
||||
safeClient(authClient.getSession()).then((res) => {
|
||||
session.value = res.data.value;
|
||||
});
|
||||
}
|
||||
|
||||
const user = computed(() => session.value?.user);
|
||||
const isAuthenticated = computed(() => !!session.value);
|
||||
|
||||
return {
|
||||
user,
|
||||
session,
|
||||
isAuthenticated,
|
||||
then(onfulfilled: (value: User | null) => void) {
|
||||
return safeClient(authClient.getSession()).then((res) => {
|
||||
session.value = res.data.value;
|
||||
onfulfilled(session.value);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
12
src/composables/useNavigateToRedirect.ts
Normal file
12
src/composables/useNavigateToRedirect.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { LocationQueryValue } from "vue-router";
|
||||
import { router } from "@/router";
|
||||
|
||||
export function useNavigateToRedirect(redirect: LocationQueryValue): void;
|
||||
|
||||
export function useNavigateToRedirect(redirect: LocationQueryValue[], index: number): void;
|
||||
|
||||
export function useNavigateToRedirect(redirect: LocationQueryValue | LocationQueryValue[], index?: number) {
|
||||
const _redirect = Array.isArray(redirect) ? redirect[index || 0] as string : redirect as string;
|
||||
const path = decodeURIComponent(_redirect || "/");
|
||||
router.replace(path);
|
||||
}
|
||||
@@ -47,8 +47,6 @@ const app = createApp(App)
|
||||
.use(router)
|
||||
.use(i18n);
|
||||
|
||||
beforeApp().then(() => {
|
||||
router.isReady().then(() => {
|
||||
app.mount("#app");
|
||||
});
|
||||
router.isReady().then(() => {
|
||||
app.mount("#app");
|
||||
});
|
||||
|
||||
@@ -2,6 +2,11 @@ import type { Router } from "vue-router";
|
||||
|
||||
export function createRouterGuard(router: Router) {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const userStore = useUserStore();
|
||||
if (to.meta.requiresAuth && !userStore.isAuthenticated) {
|
||||
const redirect = encodeURIComponent(to.fullPath);
|
||||
return next({ path: "/auth/login", query: { redirect } });
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,40 +36,49 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "user",
|
||||
component: () => import("@/views/user/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/onchain-address",
|
||||
component: () => import("@/views/onchain-address/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/deposit/fiat",
|
||||
component: () => import("@/views/deposit/fiat.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/withdraw/index",
|
||||
component: () => import("@/views/withdraw/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/user/settings",
|
||||
component: () => import("@/views/user-settings/outlet.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/views/user-settings/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "username",
|
||||
component: () => import("@/views/user-settings/username.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "nickname",
|
||||
component: () => import("@/views/user-settings/nickname.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "email",
|
||||
component: () => import("@/views/user-settings/email.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -95,23 +104,28 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: "/issue/issuing-apply",
|
||||
props: ({ query, params }) => ({ query, params }),
|
||||
component: () => import("@/views/issue/issuing-apply/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/trade-settings/bank-management",
|
||||
component: () => import("@/views/trade-settings/bank-management/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/trade-settings/my-subscribe",
|
||||
component: () => import("@/views/trade-settings/my-subscribe/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/trade-settings/bank-management/add",
|
||||
component: () => import("@/views/trade-settings/bank-management/add.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/trade-rwa/:id",
|
||||
props: true,
|
||||
component: () => import("@/views/trade-rwa/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -2,24 +2,41 @@ import type { UserData, UserProfileData } from "@/api/types";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
interface State {
|
||||
user: UserData | null;
|
||||
userProfile: UserProfileData | null;
|
||||
session: UserData | null;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore("user", () => {
|
||||
const token = useStorage<string | null>("user-token", null);
|
||||
const state = reactive<State>({
|
||||
user: null,
|
||||
userProfile: null,
|
||||
session: null,
|
||||
});
|
||||
|
||||
const isAuthenticated = computed(() => token.value !== null);
|
||||
|
||||
async function updateProfile() {
|
||||
const { data } = await safeClient(client.api.user.profile.get(), { silent: true });
|
||||
state.userProfile = data.value?.userProfile || null;
|
||||
state.session = data.value?.user || null;
|
||||
state.user = data.value?.user || null;
|
||||
}
|
||||
|
||||
function setToken(value: string) {
|
||||
token.value = value;
|
||||
}
|
||||
|
||||
function signOut() {
|
||||
token.value = null;
|
||||
state.userProfile = null;
|
||||
state.user = null;
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
...toRefs(state),
|
||||
token,
|
||||
isAuthenticated,
|
||||
signOut,
|
||||
setToken,
|
||||
updateProfile,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang='ts' setup>
|
||||
import type { EmailVerifyClient, PhoneNumberVerifyClient } from "@/api/types";
|
||||
import { closeOutline } from "ionicons/icons";
|
||||
import { authClient } from "@/auth";
|
||||
import EmailLogin from "./components/email.vue";
|
||||
import PhoneNumberLogin from "./components/phone-number.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const userStore = useUserStore();
|
||||
|
||||
async function handleSignInEmail(value: EmailVerifyClient) {
|
||||
const { data } = await authClient.signIn.emailOtp({
|
||||
@@ -13,7 +16,8 @@ async function handleSignInEmail(value: EmailVerifyClient) {
|
||||
otp: value.otp,
|
||||
});
|
||||
if (data?.token) {
|
||||
router.back();
|
||||
userStore.setToken(data.token);
|
||||
useNavigateToRedirect(route.query.redirect as string);
|
||||
}
|
||||
}
|
||||
async function handleSignInPhoneNumber(value: PhoneNumberVerifyClient) {
|
||||
@@ -24,7 +28,8 @@ async function handleSignInPhoneNumber(value: PhoneNumberVerifyClient) {
|
||||
updatePhoneNumber: false,
|
||||
});
|
||||
if (data?.token) {
|
||||
router.back();
|
||||
userStore.setToken(data.token);
|
||||
useNavigateToRedirect(route.query.redirect as string);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -33,7 +38,11 @@ async function handleSignInPhoneNumber(value: PhoneNumberVerifyClient) {
|
||||
<IonPage>
|
||||
<IonHeader class="ion-no-border">
|
||||
<IonToolbar class="ui-toolbar">
|
||||
<ion-back-button slot="start" />
|
||||
<ion-buttons slot="start">
|
||||
<ion-button @click="$router.back()">
|
||||
<ion-icon slot="icon-only" :icon="closeOutline" />
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-button slot="end" fill="clear" @click="router.push('/auth/signup')">
|
||||
{{ t('auth.login.signupButton') }}
|
||||
</ion-button>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang='ts' setup>
|
||||
import { copyOutline, qrCodeOutline, shareOutline } from "ionicons/icons";
|
||||
|
||||
const { user } = useAuth();
|
||||
const userStore = useUserStore();
|
||||
const { user } = storeToRefs(userStore);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -7,7 +7,8 @@ import * as yup from "yup";
|
||||
import { safeClient } from "@/api";
|
||||
import { authClient, emailSchema } from "@/auth";
|
||||
|
||||
const { user } = useAuth();
|
||||
const userStore = useUserStore();
|
||||
const { user } = storeToRefs(userStore);
|
||||
const email = ref(user.value?.email || "");
|
||||
const { updateProfile } = useUserStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { authClient } from "@/auth";
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const { user, userProfile } = storeToRefs(userStore);
|
||||
|
||||
async function handleSignOut() {
|
||||
const alert = await alertController.create({
|
||||
@@ -20,6 +21,7 @@ async function handleSignOut() {
|
||||
text: "Sign Out",
|
||||
role: "destructive",
|
||||
handler: async () => {
|
||||
userStore.signOut();
|
||||
authClient.signOut();
|
||||
router.replace("/layout/riwa");
|
||||
},
|
||||
@@ -88,7 +90,7 @@ async function handleSignOut() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="end">
|
||||
{{ userStore.state.userProfile?.nickname }}
|
||||
{{ userProfile?.nickname }}
|
||||
</div>
|
||||
</div>
|
||||
</ion-item>
|
||||
@@ -101,7 +103,7 @@ async function handleSignOut() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="end">
|
||||
{{ userStore.state.session?.email }}
|
||||
{{ user?.email }}
|
||||
</div>
|
||||
</div>
|
||||
</ion-item>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { arrowBackOutline } from "ionicons/icons";
|
||||
import { client, safeClient } from "@/api";
|
||||
import { authClient } from "@/auth";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const nickname = ref(userStore.state.userProfile?.nickname || "");
|
||||
const { userProfile } = storeToRefs(userStore);
|
||||
const nickname = ref(userProfile.value?.nickname || "");
|
||||
const { updateProfile } = useUserStore();
|
||||
|
||||
async function handleSave() {
|
||||
|
||||
@@ -4,7 +4,8 @@ import { arrowBackOutline } from "ionicons/icons";
|
||||
import { safeClient } from "@/api";
|
||||
import { authClient } from "@/auth";
|
||||
|
||||
const { user } = useAuth();
|
||||
const userStore = useUserStore();
|
||||
const { user } = storeToRefs(userStore);
|
||||
const username = ref(user.value?.username || "");
|
||||
const { updateProfile } = useUserStore();
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang='ts' setup>
|
||||
import { chevronForwardOutline, copyOutline, qrCodeOutline } from "ionicons/icons";
|
||||
|
||||
const { user } = useAuth();
|
||||
const userStore = useUserStore();
|
||||
const { user } = storeToRefs(userStore);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
9
src/vite-env.d.ts
vendored
9
src/vite-env.d.ts
vendored
@@ -7,6 +7,9 @@ import {
|
||||
DefineLocaleMessage,
|
||||
DefineNumberFormat,
|
||||
} from "vue-i18n";
|
||||
import "vue-router";
|
||||
|
||||
export {};
|
||||
|
||||
declare module "vue-i18n" {
|
||||
// define the locale messages schema
|
||||
@@ -21,3 +24,9 @@ declare module "vue-i18n" {
|
||||
export interface DefineNumberFormat {
|
||||
}
|
||||
}
|
||||
|
||||
declare module "vue-router" {
|
||||
interface RouteMeta {
|
||||
requiresAuth: ? boolean;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user