feat: update environment variables for development and production; add user store and authentication client
- Updated VITE_API_URL in .env.development to point to local server. - Retained VITE_API_URL in .env.production for production use. - Added user store for managing user profile and authentication state. - Created authentication client for handling user login and token management. - Introduced safeClient utility for making API requests with error handling. - Updated various components and views to utilize new user store and authentication logic. - Enhanced UI styles for better visual consistency across the application.
This commit is contained in:
@@ -1,3 +1 @@
|
||||
VITE_API_URL=https://riwa-api.riwsan1.com
|
||||
VITE_TRADINGVIEW_LIBRARY_URL=https://dev.riwsan1.com
|
||||
# VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com
|
||||
VITE_API_URL=http://192.168.1.2:9538
|
||||
@@ -1,3 +1 @@
|
||||
VITE_API_URL=https://riwa-api.riwsan1.com
|
||||
VITE_TRADINGVIEW_LIBRARY_URL=https://dev.riwsan1.com
|
||||
# VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com
|
||||
@@ -1,3 +1 @@
|
||||
VITE_API_URL=http://192.168.1.7:9527
|
||||
VITE_TRADINGVIEW_LIBRARY_URL=https://dev.riwsan1.com
|
||||
# VITE_TRADINGVIEW_DATA_API_URL=https://demo-feed-data.tradingview.com
|
||||
2
auto-imports.d.ts
vendored
2
auto-imports.d.ts
vendored
@@ -281,6 +281,7 @@ declare global {
|
||||
const useTransition: typeof import('@vueuse/core').useTransition
|
||||
const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams
|
||||
const useUserMedia: typeof import('@vueuse/core').useUserMedia
|
||||
const useUserStore: typeof import('./src/store/user').useUserStore
|
||||
const useVModel: typeof import('@vueuse/core').useVModel
|
||||
const useVModels: typeof import('@vueuse/core').useVModels
|
||||
const useVibrate: typeof import('@vueuse/core').useVibrate
|
||||
@@ -597,6 +598,7 @@ declare module 'vue' {
|
||||
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
|
||||
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
|
||||
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
|
||||
readonly useUserStore: UnwrapRef<typeof import('./src/store/user')['useUserStore']>
|
||||
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
|
||||
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
|
||||
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"@capacitor/keyboard": "catalog:",
|
||||
"@capacitor/share": "catalog:",
|
||||
"@capacitor/status-bar": "catalog:",
|
||||
"@capp/eden": "catalog:",
|
||||
"@elysiajs/eden": "catalog:",
|
||||
"@ionic/vue": "catalog:",
|
||||
"@ionic/vue-router": "catalog:",
|
||||
|
||||
69
pnpm-lock.yaml
generated
69
pnpm-lock.yaml
generated
@@ -51,11 +51,14 @@ catalogs:
|
||||
'@capacitor/status-bar':
|
||||
specifier: 8.0.0
|
||||
version: 8.0.0
|
||||
'@capp/eden':
|
||||
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.2.tgz
|
||||
version: 0.0.2
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20260113.0
|
||||
version: 4.20260116.0
|
||||
'@elysiajs/eden':
|
||||
specifier: ^1.4.5
|
||||
specifier: ^1.4.6
|
||||
version: 1.4.6
|
||||
'@iconify-json/bx':
|
||||
specifier: ^1.2.2
|
||||
@@ -136,8 +139,8 @@ catalogs:
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0
|
||||
better-auth:
|
||||
specifier: ^1.4.6
|
||||
version: 1.4.13
|
||||
specifier: ^1.4.14
|
||||
version: 1.4.14
|
||||
dayjs:
|
||||
specifier: ^1.11.19
|
||||
version: 1.11.19
|
||||
@@ -290,6 +293,9 @@ importers:
|
||||
'@capacitor/status-bar':
|
||||
specifier: 'catalog:'
|
||||
version: 8.0.0(@capacitor/core@8.0.0)
|
||||
'@capp/eden':
|
||||
specifier: 'catalog:'
|
||||
version: http://192.168.1.2:9538/api/capp-eden-0.0.2.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
||||
'@elysiajs/eden':
|
||||
specifier: 'catalog:'
|
||||
version: 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
@@ -316,7 +322,7 @@ importers:
|
||||
version: 14.1.0(vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3))
|
||||
better-auth:
|
||||
specifier: 'catalog:'
|
||||
version: 1.4.13(vitest@4.0.17(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
|
||||
version: 1.4.14(vitest@4.0.17(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))
|
||||
dayjs:
|
||||
specifier: 'catalog:'
|
||||
version: 1.11.19
|
||||
@@ -1075,20 +1081,20 @@ packages:
|
||||
resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@better-auth/core@1.4.13':
|
||||
resolution: {integrity: sha512-+8OrU/9T9mkNNKCfTv9UMlNhl9qBKsXIS8d1JNrtuCkud8Ps0+jYvbBlwa90nFmDy8X96c9UIsq+eMhPs1SDXA==}
|
||||
'@better-auth/core@1.4.14':
|
||||
resolution: {integrity: sha512-2zskCRAd+oaMwgf4joC/tJVuKl+BUB9OhH6nhDHJsoe69Xkxaf/XyRpKpU7Cjeth8SVAUNKaCInF21zBBrj6Ow==}
|
||||
peerDependencies:
|
||||
'@better-auth/utils': 0.3.0
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
better-call: 1.1.7
|
||||
better-call: 1.1.8
|
||||
jose: ^6.1.0
|
||||
kysely: ^0.28.5
|
||||
nanostores: ^1.0.1
|
||||
|
||||
'@better-auth/telemetry@1.4.13':
|
||||
resolution: {integrity: sha512-bH77Scx0K0lRIuRuHUUBLLMJ/rd9T2Tties1RniDLU8kclVwjboJQbfUY5FIamZwdayf2o0psNgS4rkaZUq+Qg==}
|
||||
'@better-auth/telemetry@1.4.14':
|
||||
resolution: {integrity: sha512-tIOvLT9vwPhbxT4rJIsHjxqcjlTX/eHHs0gXrKM90Dot71DqM9Cwse9C5IHQxJ1WLfPhc62laby4fbgcu5XSIA==}
|
||||
peerDependencies:
|
||||
'@better-auth/core': 1.4.13
|
||||
'@better-auth/core': 1.4.14
|
||||
|
||||
'@better-auth/utils@0.3.0':
|
||||
resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==}
|
||||
@@ -1170,6 +1176,12 @@ packages:
|
||||
'@capacitor/synapse@1.0.4':
|
||||
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
||||
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.2.tgz':
|
||||
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.2.tgz}
|
||||
version: 0.0.2
|
||||
peerDependencies:
|
||||
'@elysiajs/eden': ^1.4.6
|
||||
|
||||
'@clack/core@0.5.0':
|
||||
resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==}
|
||||
|
||||
@@ -2821,13 +2833,14 @@ packages:
|
||||
resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
better-auth@1.4.13:
|
||||
resolution: {integrity: sha512-frGQmYT0rglidLpx91SP9n4ztaNBFGBb0JrWSdMTAHvhBkmQlUT/43e0IboMK2mPrAZFlvhdcMV8jCnqpYVE9A==}
|
||||
better-auth@1.4.14:
|
||||
resolution: {integrity: sha512-gDpOmWOilhnLIBuv/7/6HoaXLtCDR7iUEjkvYCunJhUWZng53kZ2jivxtekhYC0j4+VSG6AZTihPIaoKrlztlA==}
|
||||
peerDependencies:
|
||||
'@lynx-js/react': '*'
|
||||
'@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
'@sveltejs/kit': ^2.0.0
|
||||
'@tanstack/start-server-core': ^1.0.0
|
||||
'@tanstack/react-start-server': ^1.0.0
|
||||
'@tanstack/solid-start-server': ^1.0.0
|
||||
better-sqlite3: ^12.0.0
|
||||
drizzle-kit: '>=0.31.4'
|
||||
drizzle-orm: '>=0.41.0'
|
||||
@@ -2849,7 +2862,9 @@ packages:
|
||||
optional: true
|
||||
'@sveltejs/kit':
|
||||
optional: true
|
||||
'@tanstack/start-server-core':
|
||||
'@tanstack/react-start-server':
|
||||
optional: true
|
||||
'@tanstack/solid-start-server':
|
||||
optional: true
|
||||
better-sqlite3:
|
||||
optional: true
|
||||
@@ -2880,8 +2895,8 @@ packages:
|
||||
vue:
|
||||
optional: true
|
||||
|
||||
better-call@1.1.7:
|
||||
resolution: {integrity: sha512-6gaJe1bBIEgVebQu/7q9saahVzvBsGaByEnE8aDVncZEDiJO7sdNB28ot9I6iXSbR25egGmmZ6aIURXyQHRraQ==}
|
||||
better-call@1.1.8:
|
||||
resolution: {integrity: sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==}
|
||||
peerDependencies:
|
||||
zod: ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
@@ -6774,20 +6789,20 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@better-auth/core@1.4.13(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)':
|
||||
'@better-auth/core@1.4.14(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)':
|
||||
dependencies:
|
||||
'@better-auth/utils': 0.3.0
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
'@standard-schema/spec': 1.1.0
|
||||
better-call: 1.1.7(zod@4.3.5)
|
||||
better-call: 1.1.8(zod@4.3.5)
|
||||
jose: 6.1.3
|
||||
kysely: 0.28.9
|
||||
nanostores: 1.1.0
|
||||
zod: 4.3.5
|
||||
|
||||
'@better-auth/telemetry@1.4.13(@better-auth/core@1.4.13(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0))':
|
||||
'@better-auth/telemetry@1.4.14(@better-auth/core@1.4.14(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.4.13(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)
|
||||
'@better-auth/core': 1.4.14(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)
|
||||
'@better-auth/utils': 0.3.0
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
|
||||
@@ -6878,6 +6893,10 @@ snapshots:
|
||||
|
||||
'@capacitor/synapse@1.0.4': {}
|
||||
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.2.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||
dependencies:
|
||||
'@elysiajs/eden': 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
|
||||
'@clack/core@0.5.0':
|
||||
dependencies:
|
||||
picocolors: 1.1.1
|
||||
@@ -8435,15 +8454,15 @@ snapshots:
|
||||
|
||||
basic-ftp@5.1.0: {}
|
||||
|
||||
better-auth@1.4.13(vitest@4.0.17(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)):
|
||||
better-auth@1.4.14(vitest@4.0.17(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@better-auth/core': 1.4.13(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)
|
||||
'@better-auth/telemetry': 1.4.13(@better-auth/core@1.4.13(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.7(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0))
|
||||
'@better-auth/core': 1.4.14(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0)
|
||||
'@better-auth/telemetry': 1.4.14(@better-auth/core@1.4.14(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0))
|
||||
'@better-auth/utils': 0.3.0
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
'@noble/ciphers': 2.1.1
|
||||
'@noble/hashes': 2.0.1
|
||||
better-call: 1.1.7(zod@4.3.5)
|
||||
better-call: 1.1.8(zod@4.3.5)
|
||||
defu: 6.1.4
|
||||
jose: 6.1.3
|
||||
kysely: 0.28.9
|
||||
@@ -8453,7 +8472,7 @@ snapshots:
|
||||
vitest: 4.0.17(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2)
|
||||
vue: 3.5.26(typescript@5.9.3)
|
||||
|
||||
better-call@1.1.7(zod@4.3.5):
|
||||
better-call@1.1.8(zod@4.3.5):
|
||||
dependencies:
|
||||
'@better-auth/utils': 0.3.0
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
|
||||
@@ -18,8 +18,9 @@ catalog:
|
||||
'@capacitor/keyboard': 8.0.0
|
||||
'@capacitor/share': ^8.0.0
|
||||
'@capacitor/status-bar': 8.0.0
|
||||
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.2.tgz
|
||||
'@cloudflare/workers-types': ^4.20260113.0
|
||||
'@elysiajs/eden': ^1.4.5
|
||||
'@elysiajs/eden': ^1.4.6
|
||||
'@iconify-json/bx': ^1.2.2
|
||||
'@iconify-json/circle-flags': ^1.2.10
|
||||
'@iconify-json/cryptocurrency-color': ^1.2.4
|
||||
@@ -48,7 +49,7 @@ catalog:
|
||||
'@vueuse/core': ^14.1.0
|
||||
'@vueuse/integrations': ^14.1.0
|
||||
'@vueuse/router': ^14.1.0
|
||||
better-auth: ^1.4.6
|
||||
better-auth: ^1.4.14
|
||||
cypress: ^15.7.1
|
||||
dayjs: ^1.11.19
|
||||
dotenv: ^17.2.3
|
||||
|
||||
124
src/api/index.ts
Normal file
124
src/api/index.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { App } from "@capp/eden";
|
||||
import type { WatchSource } from "vue";
|
||||
import { treaty } from "@elysiajs/eden";
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { i18n } from "@/locales";
|
||||
|
||||
const baseURL = import.meta.env.DEV ? window.location.origin : import.meta.env.VITE_API_URL;
|
||||
|
||||
export const client = treaty<App>(baseURL, {
|
||||
fetch: {
|
||||
credentials: "include",
|
||||
},
|
||||
headers() {
|
||||
const token = localStorage.getItem("user-token") || "";
|
||||
const headers = new Headers();
|
||||
headers.append("Content-Type", "application/json");
|
||||
headers.append("Authorization", token ? `Bearer ${token}` : "");
|
||||
return headers;
|
||||
},
|
||||
});
|
||||
|
||||
export interface SafeClientOptions {
|
||||
silent?: boolean;
|
||||
immediate?: boolean;
|
||||
watchSource?: WatchSource; // 用于监听的响应式数据源
|
||||
}
|
||||
|
||||
export interface SafeClientReturn<T, E> {
|
||||
data: Ref<T | null>;
|
||||
error: Ref<E | null>;
|
||||
isPending: Ref<boolean>;
|
||||
execute: () => Promise<void>;
|
||||
onFetchResponse: (callback: (data: T, error: E) => void) => void;
|
||||
stopWatching?: () => void;
|
||||
}
|
||||
|
||||
export type RequestPromise<T, E> = Promise<{ data: T; error: E; status?: number; response?: Response }>;
|
||||
|
||||
export function safeClient<T, E>(
|
||||
requestPromise: (() => RequestPromise<T, E>) | RequestPromise<T, E>,
|
||||
options: SafeClientOptions = {},
|
||||
): SafeClientReturn<T, E> & Promise<SafeClientReturn<T, E>> {
|
||||
const { immediate = true, watchSource } = options;
|
||||
const data = ref<T | null>(null);
|
||||
const error = ref<E | null>(null);
|
||||
const isPending = ref(false);
|
||||
let responseCallback: ((data: T, error: E) => void) | null = null;
|
||||
let stopWatcher: (() => void) | undefined;
|
||||
|
||||
const execute = async () => {
|
||||
isPending.value = true;
|
||||
let request: () => RequestPromise<T, E>;
|
||||
if (typeof requestPromise !== "function") {
|
||||
request = () => Promise.resolve(requestPromise);
|
||||
}
|
||||
else {
|
||||
request = requestPromise;
|
||||
}
|
||||
|
||||
const res = await request().finally(() => {
|
||||
isPending.value = false;
|
||||
});
|
||||
|
||||
if (res.error && res.status === 418) {
|
||||
if (!options.silent) {
|
||||
const toast = await toastController.create({
|
||||
message: i18n.global.t((res.error as any).value.code, {
|
||||
...(res.error as any).value.context,
|
||||
}),
|
||||
duration: 3000,
|
||||
position: "bottom",
|
||||
color: "danger",
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
throw res.error;
|
||||
}
|
||||
data.value = res.data;
|
||||
error.value = res.error;
|
||||
|
||||
// 调用注册的回调函数
|
||||
if (responseCallback) {
|
||||
responseCallback(res.data, res.error);
|
||||
}
|
||||
};
|
||||
|
||||
function onFetchResponse(callback: (data: T, error: E) => void) {
|
||||
responseCallback = callback;
|
||||
}
|
||||
|
||||
function stopWatching() {
|
||||
if (stopWatcher) {
|
||||
stopWatcher();
|
||||
stopWatcher = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果提供了 watchSource,则监听其变化
|
||||
if (watchSource) {
|
||||
stopWatcher = watch(
|
||||
watchSource,
|
||||
() => {
|
||||
execute();
|
||||
},
|
||||
{ immediate: false }, // 不立即执行,避免与 immediate 选项冲突
|
||||
);
|
||||
}
|
||||
|
||||
const result: SafeClientReturn<T, E> = {
|
||||
data: data as Ref<T | null>,
|
||||
error: error as Ref<E | null>,
|
||||
isPending,
|
||||
execute,
|
||||
onFetchResponse,
|
||||
stopWatching,
|
||||
};
|
||||
|
||||
const promise = immediate ? execute().then(() => result) : Promise.resolve(result);
|
||||
|
||||
Object.assign(promise, result);
|
||||
|
||||
return promise as SafeClientReturn<T, E> & Promise<SafeClientReturn<T, E>>;
|
||||
}
|
||||
12
src/api/types.ts
Normal file
12
src/api/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Treaty } from "@elysiajs/eden";
|
||||
import type { client } from ".";
|
||||
|
||||
export type TreatyQuery<T> = T extends (...args: any[]) => any
|
||||
? NonNullable<NonNullable<Parameters<T>[0]>["query"]>
|
||||
: never;
|
||||
|
||||
export type TreatyBody<T> = T extends (...args: any[]) => any
|
||||
? NonNullable<Parameters<T>[0]>
|
||||
: never;
|
||||
|
||||
export type UserProfileData = Treaty.Data<typeof client.api.user.profile.get>;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 193 KiB |
16
src/auth/index.ts
Normal file
16
src/auth/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { usernameClient } from "better-auth/client/plugins";
|
||||
import { createAuthClient } from "better-auth/vue";
|
||||
|
||||
const baseURL = import.meta.env.DEV ? window.location.origin : import.meta.env.VITE_API_URL;
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL,
|
||||
fetchOptions: {
|
||||
credentials: "include",
|
||||
auth: {
|
||||
type: "Bearer",
|
||||
token: () => localStorage.getItem("user-token") || "",
|
||||
},
|
||||
},
|
||||
plugins: [usernameClient()],
|
||||
});
|
||||
@@ -45,7 +45,7 @@ const { t } = useI18n();
|
||||
ion-tab-bar {
|
||||
height: 70px;
|
||||
--background: white;
|
||||
box-shadow: 0px 0px 12px var(--ion-color-tertiary);
|
||||
box-shadow: 0px 0px 12px #cfe3ff;
|
||||
padding-bottom: var(--ion-safe-area-bottom);
|
||||
}
|
||||
.icon {
|
||||
|
||||
@@ -2,6 +2,14 @@ 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) {
|
||||
// if (from.path === "/auth/login") {
|
||||
// return next("/");
|
||||
// }
|
||||
// const redirect = encodeURIComponent(to.fullPath);
|
||||
// return next({ path: "/auth/login", query: { redirect } });
|
||||
// }
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,18 +20,22 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "home",
|
||||
component: () => import("@/views/home/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "service",
|
||||
component: () => import("@/views/service/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "product",
|
||||
component: () => import("@/views/product/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "profile",
|
||||
component: () => import("@/views/profile/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
1
src/store/index.ts
Normal file
1
src/store/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./user";
|
||||
39
src/store/user.ts
Normal file
39
src/store/user.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { UserProfileData } from "@/api/types";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
interface State {
|
||||
userProfile: UserProfileData | null;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore("user", () => {
|
||||
const token = useStorageAsync<string | null>("user-token", null);
|
||||
const state = reactive<State>({
|
||||
userProfile: null,
|
||||
});
|
||||
|
||||
const isAuthenticated = computed(() => !!token.value);
|
||||
|
||||
async function updateProfile() {
|
||||
const { data } = await safeClient(client.api.user.profile.get(), { silent: true });
|
||||
state.userProfile = data.value || null;
|
||||
}
|
||||
|
||||
function setToken(value: string) {
|
||||
localStorage.setItem("user-token", value);
|
||||
token.value = value;
|
||||
}
|
||||
|
||||
function signOut() {
|
||||
token.value = null;
|
||||
state.userProfile = null;
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
token,
|
||||
isAuthenticated,
|
||||
signOut,
|
||||
setToken,
|
||||
updateProfile,
|
||||
};
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
:root {
|
||||
/* :root {
|
||||
--ion-color-primary: #c31d39;
|
||||
--ion-color-primary-rgb: 195,29,57;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
@@ -62,6 +62,72 @@
|
||||
--ion-color-dark-shade: #292929;
|
||||
--ion-color-dark-tint: #444444;
|
||||
|
||||
} */
|
||||
|
||||
:root {
|
||||
--ion-color-primary: #2065c3;
|
||||
--ion-color-primary-rgb: 32,101,195;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255,255,255;
|
||||
--ion-color-primary-shade: #1c59ac;
|
||||
--ion-color-primary-tint: #3674c9;
|
||||
|
||||
--ion-color-secondary: #0163aa;
|
||||
--ion-color-secondary-rgb: 1,99,170;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255,255,255;
|
||||
--ion-color-secondary-shade: #015796;
|
||||
--ion-color-secondary-tint: #1a73b3;
|
||||
|
||||
--ion-color-tertiary: #6030ff;
|
||||
--ion-color-tertiary-rgb: 96,48,255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255,255,255;
|
||||
--ion-color-tertiary-shade: #542ae0;
|
||||
--ion-color-tertiary-tint: #7045ff;
|
||||
|
||||
--ion-color-success: #2dd55b;
|
||||
--ion-color-success-rgb: 45,213,91;
|
||||
--ion-color-success-contrast: #000000;
|
||||
--ion-color-success-contrast-rgb: 0,0,0;
|
||||
--ion-color-success-shade: #28bb50;
|
||||
--ion-color-success-tint: #42d96b;
|
||||
|
||||
--ion-color-warning: #ffc409;
|
||||
--ion-color-warning-rgb: 255,196,9;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0,0,0;
|
||||
--ion-color-warning-shade: #e0ac08;
|
||||
--ion-color-warning-tint: #ffca22;
|
||||
|
||||
--ion-color-danger: #c5000f;
|
||||
--ion-color-danger-rgb: 197,0,15;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255,255,255;
|
||||
--ion-color-danger-shade: #ad000d;
|
||||
--ion-color-danger-tint: #cb1a27;
|
||||
|
||||
--ion-color-light: #f6f8fc;
|
||||
--ion-color-light-rgb: 246,248,252;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0,0,0;
|
||||
--ion-color-light-shade: #d8dade;
|
||||
--ion-color-light-tint: #f7f9fc;
|
||||
|
||||
--ion-color-medium: #5f5f5f;
|
||||
--ion-color-medium-rgb: 95,95,95;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
--ion-color-medium-contrast-rgb: 255,255,255;
|
||||
--ion-color-medium-shade: #545454;
|
||||
--ion-color-medium-tint: #6f6f6f;
|
||||
|
||||
--ion-color-dark: #2f2f2f;
|
||||
--ion-color-dark-rgb: 47,47,47;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
--ion-color-dark-contrast-rgb: 255,255,255;
|
||||
--ion-color-dark-shade: #292929;
|
||||
--ion-color-dark-tint: #444444;
|
||||
|
||||
}
|
||||
|
||||
.ion-toolbar {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang='ts' setup>
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const phoneNumber = ref("");
|
||||
@@ -23,7 +21,7 @@ function handleLogin() {
|
||||
}
|
||||
|
||||
function handleSignup() {
|
||||
router.push("/auth/signup");
|
||||
router.push(`/auth/signup?redirect=${encodeURIComponent(route.query.redirect as string || "/")}`);
|
||||
}
|
||||
|
||||
function goToTerms(type: "service" | "privacy") {
|
||||
|
||||
@@ -66,10 +66,10 @@ const newsList = ref([
|
||||
const router = useRouter();
|
||||
// 快捷入口
|
||||
const quickActions = ref([
|
||||
{ id: 1, name: "签到", icon: calendarOutline, color: "#c32120" },
|
||||
{ id: 2, name: "团队中心", icon: peopleOutline, color: "#c32120" },
|
||||
{ id: 3, name: "邀请好友", icon: rocketOutline, color: "#c32120" },
|
||||
{ id: 4, name: "在线客服", icon: chatbubblesOutline, color: "#c32120" },
|
||||
{ id: 1, name: "签到", icon: calendarOutline, color: "#2373c3" },
|
||||
{ id: 2, name: "团队中心", icon: peopleOutline, color: "#2373c3" },
|
||||
{ id: 3, name: "邀请好友", icon: rocketOutline, color: "#2373c3" },
|
||||
{ id: 4, name: "在线客服", icon: chatbubblesOutline, color: "#2373c3" },
|
||||
]);
|
||||
|
||||
function handleQuickAction(action: any) {
|
||||
@@ -149,7 +149,7 @@ onUnmounted(() => {
|
||||
@click="handleQuickAction(action)"
|
||||
>
|
||||
<div
|
||||
class="w-12 h-12 rounded-full flex-center shadow-lg"
|
||||
class="w-12 h-12 rounded-xl flex-center shadow-lg"
|
||||
:style="{ background: action.color }"
|
||||
>
|
||||
<ion-icon :icon="action.icon" class="text-xl text-white" />
|
||||
@@ -182,7 +182,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<div class="relative w-28 h-28 flex-shrink-0 overflow-hidden">
|
||||
<img :src="news.image" :alt="news.title" class="w-full h-full object-cover">
|
||||
<div class="news-badge absolute top-2 left-2 bg-gradient-to-br from-[#c41e3a] to-[#8b1a2e] text-white px-2 py-0.5 rounded-lg text-xs font-semibold shadow-lg">
|
||||
<div class="news-badge absolute top-2 left-2 bg-linear-to-br from-[#78d0ff] to-[#1879aa] text-white px-2 py-0.5 rounded-lg text-xs font-semibold shadow-lg">
|
||||
热点
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -174,12 +174,12 @@ function handleSubscribe(product: any, event: Event) {
|
||||
}
|
||||
|
||||
.subscribe-btn {
|
||||
--background: linear-gradient(135deg, #c41e3a 0%, #8b1a2e 100%);
|
||||
--background-activated: linear-gradient(135deg, #8b1a2e 0%, #c41e3a 100%);
|
||||
--background: linear-gradient(135deg, #1778ac 0%, #265166 100%);
|
||||
--background-activated: linear-gradient(135deg, #1778ac 0%, #265166 100%);
|
||||
--border-radius: 12px;
|
||||
--padding-start: 16px;
|
||||
--padding-end: 16px;
|
||||
--box-shadow: 0 2px 8px rgba(196, 30, 58, 0.3);
|
||||
--box-shadow: 0 2px 8px rgba(30, 124, 196, 0.3);
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
height: 32px;
|
||||
|
||||
@@ -118,13 +118,13 @@ function handleLogout() {
|
||||
<img alt="用户头像" src="@/assets/images/avatar.jpg">
|
||||
</ion-avatar>
|
||||
<div>
|
||||
<div class="text-primary text-xl font-bold">
|
||||
<div class="text-danger text-xl font-bold">
|
||||
{{ userInfo.name }}
|
||||
</div>
|
||||
<div class="text-primary text-sm font-semibold opacity-90">
|
||||
<div class="text-danger text-sm font-semibold opacity-90">
|
||||
手机号:{{ userInfo.phone }}
|
||||
</div>
|
||||
<div class="text-primary text-sm font-semibold opacity-90">
|
||||
<div class="text-danger text-sm font-semibold opacity-90">
|
||||
邀请码:{{ userInfo.inviteCode }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,7 +136,7 @@ function handleLogout() {
|
||||
<section class="mb-4 -mt-12">
|
||||
<div class="card rounded-2xl shadow-lg p-5">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<ion-icon :icon="walletOutline" class="text-2xl text-[#c41e3a]" />
|
||||
<ion-icon :icon="walletOutline" class="text-2xl text-[#1e71c4]" />
|
||||
<div class="text-lg font-bold text-[#1a1a1a] m-0">
|
||||
我的钱包
|
||||
</div>
|
||||
@@ -144,19 +144,19 @@ function handleLogout() {
|
||||
|
||||
<!-- 余额展示 -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div class="bg-gradient-to-br from-[#fff7f0] to-[#ffe8e8] rounded-xl p-4">
|
||||
<div class="bg-gradient-to-br from-[#ffffff] to-[#e8f5ff] rounded-xl p-4">
|
||||
<div class="text-xs mb-1">
|
||||
收益钱包
|
||||
</div>
|
||||
<div class="text-2xl font-bold text-[#c41e3a] mb-1">
|
||||
<div class="text-2xl font-bold text-[#1e71c4] mb-1">
|
||||
¥{{ wallet.profitBalance.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gradient-to-br from-[#fff7f0] to-[#ffe8e8] rounded-xl p-4">
|
||||
<div class="bg-gradient-to-br from-[#ffffff] to-[#e8f5ff] rounded-xl p-4">
|
||||
<div class="text-xs mb-1">
|
||||
账户余额
|
||||
</div>
|
||||
<div class="text-2xl font-bold text-[#c41e3a] mb-1">
|
||||
<div class="text-2xl font-bold text-[#1e71c4] mb-1">
|
||||
¥{{ wallet.accountBalance.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,7 +189,7 @@ function handleLogout() {
|
||||
<section class="my-5">
|
||||
<div class="card rounded-2xl shadow-lg p-5">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<ion-icon :icon="homeOutline" class="text-2xl text-[#c41e3a]" />
|
||||
<ion-icon :icon="homeOutline" class="text-2xl text-[#1e71c4]" />
|
||||
<div class="text-lg font-bold text-[#1a1a1a] m-0">
|
||||
我的应用
|
||||
</div>
|
||||
@@ -227,13 +227,13 @@ function handleLogout() {
|
||||
|
||||
<style lang='css' scoped>
|
||||
.card {
|
||||
background: linear-gradient(180deg, #ffeef1, #ffffff 15%);
|
||||
background: linear-gradient(180deg, #eef9ff, #ffffff 15%);
|
||||
}
|
||||
.recharge-btn {
|
||||
--background: linear-gradient(135deg, #c41e3a 0%, #8b1a2e 100%);
|
||||
--background-activated: linear-gradient(135deg, #8b1a2e 0%, #c41e3a 100%);
|
||||
--background: linear-gradient(135deg, #1778ac 0%, #265166 100%);
|
||||
--background-activated: linear-gradient(135deg, #1778ac 0%, #265166 100%);
|
||||
--border-radius: 12px;
|
||||
--box-shadow: 0 2px 8px rgba(196, 30, 58, 0.3);
|
||||
--box-shadow: 0 2px 8px rgba(30, 124, 196, 0.3);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
height: 44px;
|
||||
@@ -241,8 +241,8 @@ function handleLogout() {
|
||||
}
|
||||
|
||||
.withdraw-btn {
|
||||
--border-color: #c41e3a;
|
||||
--color: #c41e3a;
|
||||
--border-color: #1972a2;
|
||||
--color: #1e6ac4;
|
||||
--border-radius: 12px;
|
||||
--border-width: 2px;
|
||||
font-weight: 600;
|
||||
@@ -255,6 +255,6 @@ function handleLogout() {
|
||||
font-weight: 600;
|
||||
}
|
||||
.app-item {
|
||||
background: linear-gradient(135deg, rgb(249 103 102 / 93%), rgb(195, 33, 32));
|
||||
background: #3f8bba;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -63,7 +63,7 @@ function handleNewsClick(news: any) {
|
||||
>
|
||||
<div class="relative w-full h-45 overflow-hidden">
|
||||
<img :src="news.image" :alt="news.title" class="w-full h-full object-cover">
|
||||
<div class="news-badge absolute top-3 left-3 bg-gradient-to-br from-[#c41e3a] to-[#8b1a2e] text-white px-3 py-1 rounded-xl text-xs font-semibold shadow-lg">
|
||||
<div class="news-badge absolute top-3 left-3 bg-linear-to-br from-[#78d0ff] to-[#1879aa] text-white px-3 py-1 rounded-xl text-xs font-semibold shadow-lg">
|
||||
热点
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user