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:
2026-01-17 17:23:38 +07:00
parent 1239935b57
commit 7ec2522fa0
22 changed files with 353 additions and 68 deletions

View File

@@ -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

View File

@@ -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=https://riwa-api.riwsan1.com

View File

@@ -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
VITE_API_URL=http://192.168.1.7:9527

2
auto-imports.d.ts vendored
View File

@@ -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']>

View File

@@ -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
View File

@@ -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

View File

@@ -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
View 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
View 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
View 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()],
});

View File

@@ -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 {

View File

@@ -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();
});
}

View File

@@ -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
View File

@@ -0,0 +1 @@
export * from "./user";

39
src/store/user.ts Normal file
View 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,
};
});

View File

@@ -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 {

View File

@@ -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") {

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>