diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1e893d8..26ce51f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -161,3 +161,5 @@ Capacitor 配置本地开发服务器地址为 http://localhost:5173 国际化支持中文和英文,配置文件在 src/locales/ 目前多语言只需要支持中英文,其他语言先不要更改 样式使用 TailwindCSS 4.x + Ionic CSS Variables 混合模式 函数风格使用function关键字定义,一般不要使用箭头函数 +如果有任何不明确的地方,随时提问以获取更多信息 +如果代码有错误,请先执行 pnpm run lint:fix 来自动修复代码问题 diff --git a/components.d.ts b/components.d.ts index 070df19..889808e 100644 --- a/components.d.ts +++ b/components.d.ts @@ -18,6 +18,7 @@ declare module 'vue' { IonApp: typeof import('@ionic/vue')['IonApp'] IonAvatar: typeof import('@ionic/vue')['IonAvatar'] IonBackButton: typeof import('@ionic/vue')['IonBackButton'] + IonBadge: typeof import('@ionic/vue')['IonBadge'] IonButton: typeof import('@ionic/vue')['IonButton'] IonButtons: typeof import('@ionic/vue')['IonButtons'] IonCol: typeof import('@ionic/vue')['IonCol'] @@ -32,6 +33,9 @@ declare module 'vue' { IonInfiniteScrollContent: typeof import('@ionic/vue')['IonInfiniteScrollContent'] IonInput: typeof import('@ionic/vue')['IonInput'] IonItem: typeof import('@ionic/vue')['IonItem'] + IonItemOption: typeof import('@ionic/vue')['IonItemOption'] + IonItemOptions: typeof import('@ionic/vue')['IonItemOptions'] + IonItemSliding: typeof import('@ionic/vue')['IonItemSliding'] IonLabel: typeof import('@ionic/vue')['IonLabel'] IonList: typeof import('@ionic/vue')['IonList'] IonListHeader: typeof import('@ionic/vue')['IonListHeader'] @@ -49,6 +53,7 @@ declare module 'vue' { IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton'] IonSelect: typeof import('@ionic/vue')['IonSelect'] IonSelectOption: typeof import('@ionic/vue')['IonSelectOption'] + IonSpinner: typeof import('@ionic/vue')['IonSpinner'] IonTabBar: typeof import('@ionic/vue')['IonTabBar'] IonTabButton: typeof import('@ionic/vue')['IonTabButton'] IonTabs: typeof import('@ionic/vue')['IonTabs'] @@ -71,6 +76,7 @@ declare global { const IonApp: typeof import('@ionic/vue')['IonApp'] const IonAvatar: typeof import('@ionic/vue')['IonAvatar'] const IonBackButton: typeof import('@ionic/vue')['IonBackButton'] + const IonBadge: typeof import('@ionic/vue')['IonBadge'] const IonButton: typeof import('@ionic/vue')['IonButton'] const IonButtons: typeof import('@ionic/vue')['IonButtons'] const IonCol: typeof import('@ionic/vue')['IonCol'] @@ -85,6 +91,9 @@ declare global { const IonInfiniteScrollContent: typeof import('@ionic/vue')['IonInfiniteScrollContent'] const IonInput: typeof import('@ionic/vue')['IonInput'] const IonItem: typeof import('@ionic/vue')['IonItem'] + const IonItemOption: typeof import('@ionic/vue')['IonItemOption'] + const IonItemOptions: typeof import('@ionic/vue')['IonItemOptions'] + const IonItemSliding: typeof import('@ionic/vue')['IonItemSliding'] const IonLabel: typeof import('@ionic/vue')['IonLabel'] const IonList: typeof import('@ionic/vue')['IonList'] const IonListHeader: typeof import('@ionic/vue')['IonListHeader'] @@ -102,6 +111,7 @@ declare global { const IonSegmentButton: typeof import('@ionic/vue')['IonSegmentButton'] const IonSelect: typeof import('@ionic/vue')['IonSelect'] const IonSelectOption: typeof import('@ionic/vue')['IonSelectOption'] + const IonSpinner: typeof import('@ionic/vue')['IonSpinner'] const IonTabBar: typeof import('@ionic/vue')['IonTabBar'] const IonTabButton: typeof import('@ionic/vue')['IonTabButton'] const IonTabs: typeof import('@ionic/vue')['IonTabs'] diff --git a/src/api/index.ts b/src/api/index.ts index 3330126..d2a15ab 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,6 +3,7 @@ import type { WatchSource } from "vue"; import { treaty } from "@elysiajs/eden"; import { toastController } from "@ionic/vue"; import { i18n } from "@/locales"; +import { useMocks } from "@/mocks"; const client = treaty(window.location.origin, { fetch: { @@ -121,4 +122,17 @@ export function safeClient( return promise as SafeClientReturn & Promise>; } +export function mockClient(key: string): SafeClientReturn & Promise> { + const requestPromise = async () => { + const mocks = useMocks(); + const mockFn = mocks.get(key); + if (!mockFn) { + throw new Error(`Mock with key "${key}" not found.`); + } + const data = await mockFn(); + return { data: data as T, error: null as E }; + }; + return safeClient(requestPromise, { immediate: true }); +} + export { client }; diff --git a/src/main.ts b/src/main.ts index 077d771..1efe6b8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,7 @@ import uiComponents from "@/ui"; import App from "./App.vue"; import { authClient } from "./auth"; import { i18n } from "./locales"; - +import { setupMocks } from "./mocks"; import { router } from "./router"; /* Core CSS required for Ionic components to work properly */ @@ -41,26 +41,32 @@ import "./theme/ionic.css"; useTheme(); -authClient.getSession().then((session) => { - const pinia = createPinia(); - const userStore = useUserStore(pinia); - userStore.setToken(session.data?.session.token || ""); +function initApp() { + authClient.getSession().then((session) => { + const pinia = createPinia(); + const userStore = useUserStore(pinia); + userStore.setToken(session.data?.session.token || ""); - const app = createApp(App) - .use(IonicVue, { - backButtonText: "返回", - mode: "ios", - statusTap: true, - swipeBackEnabled: true, + const app = createApp(App) + .use(IonicVue, { + backButtonText: "返回", + mode: "ios", + statusTap: true, + swipeBackEnabled: true, // rippleEffect: true, // animated: false, - }) - .use(uiComponents) - .use(pinia) - .use(router) - .use(i18n); + }) + .use(uiComponents) + .use(pinia) + .use(router) + .use(i18n); - router.isReady().then(() => { - app.mount("#app"); + router.isReady().then(() => { + app.mount("#app"); + }); }); +} + +setupMocks().then(() => { + initApp(); }); diff --git a/src/mocks/data/index.ts b/src/mocks/data/index.ts new file mode 100644 index 0000000..1acb4fa --- /dev/null +++ b/src/mocks/data/index.ts @@ -0,0 +1 @@ +import "./notify"; diff --git a/src/mocks/data/notify.ts b/src/mocks/data/notify.ts new file mode 100644 index 0000000..6a79a29 --- /dev/null +++ b/src/mocks/data/notify.ts @@ -0,0 +1,80 @@ +import { useMocks } from ".."; + +const mocks = useMocks(); + +mocks.register("notify.list", () => { + const now = new Date(); + + return [ + { + id: 1, + title: "系统通知", + content: "您的账户安全等级已提升至 LV2,现在可以享受更高的交易限额。", + date: new Date(now.getTime() - 1000 * 60 * 30).toISOString(), // 30分钟前 + read: false, + }, + { + id: 2, + title: "交易通知", + content: "您的买入订单已成功成交,交易对: BTC/USDT,数量: 0.005 BTC", + date: new Date(now.getTime() - 1000 * 60 * 60 * 2).toISOString(), // 2小时前 + read: false, + }, + { + id: 3, + title: "活动通知", + content: "新春特惠活动火热进行中!邀请好友注册交易即可获得最高 100 USDT 奖励,活动截止时间:2025-02-28", + date: new Date(now.getTime() - 1000 * 60 * 60 * 5).toISOString(), // 5小时前 + read: true, + }, + { + id: 4, + title: "安全提醒", + content: "检测到您的账户在新设备登录,登录地点:北京,如非本人操作,请立即修改密码并联系客服。", + date: new Date(now.getTime() - 1000 * 60 * 60 * 24).toISOString(), // 1天前 + read: false, + }, + { + id: 5, + title: "交易通知", + content: "您的卖出订单已成功成交,交易对: ETH/USDT,数量: 0.1 ETH,成交价格: 2850 USDT", + date: new Date(now.getTime() - 1000 * 60 * 60 * 24 * 2).toISOString(), // 2天前 + read: true, + }, + { + id: 6, + title: "系统通知", + content: "平台将于北京时间 2025-01-05 02:00 至 04:00 进行系统升级维护,期间部分功能可能暂时无法使用。", + date: new Date(now.getTime() - 1000 * 60 * 60 * 24 * 3).toISOString(), // 3天前 + read: true, + }, + { + id: 7, + title: "活动通知", + content: "恭喜您获得新手礼包!包含 10 USDT 体验金和 7 天 VIP 会员权益,请及时领取。", + date: new Date(now.getTime() - 1000 * 60 * 60 * 24 * 5).toISOString(), // 5天前 + read: true, + }, + { + id: 8, + title: "安全提醒", + content: "您的提现申请已通过审核,预计将在 24 小时内到账,请注意查收。", + date: new Date(now.getTime() - 1000 * 60 * 60 * 24 * 7).toISOString(), // 7天前 + read: true, + }, + { + id: 9, + title: "交易通知", + content: "市场波动较大,您持仓的 BTC 当前盈利已达 15%,是否考虑止盈?", + date: new Date(now.getTime() - 1000 * 60 * 60 * 24 * 10).toISOString(), // 10天前 + read: true, + }, + { + id: 10, + title: "系统通知", + content: "为了更好地保护您的资产安全,建议您开启双重验证(2FA)功能。", + date: new Date(now.getTime() - 1000 * 60 * 60 * 24 * 15).toISOString(), // 15天前 + read: true, + }, + ]; +}); diff --git a/src/mocks/index.ts b/src/mocks/index.ts new file mode 100644 index 0000000..67aa44b --- /dev/null +++ b/src/mocks/index.ts @@ -0,0 +1,41 @@ +import type { Awaitable } from "@vueuse/core"; + +export class Mock { + private mocks: Map Awaitable>; + constructor() { + this.mocks = new Map(); + } + + get keys(): string[] { + return Array.from(this.mocks.keys()); + } + + register(key: string, data: () => Awaitable) { + this.mocks.set(key, data); + } + + unregister(key: string) { + this.mocks.delete(key); + } + + get(key: string): (() => Awaitable) | undefined { + return this.mocks.get(key); + } +} + +export function useMocks() { + const singletonKey = "__mocks_singleton__"; + if (!(globalThis as any)[singletonKey]) { + (globalThis as any)[singletonKey] = new Mock(); + } + return (globalThis as any)[singletonKey] as Mock; +} + +export function setupMocks() { + return new Promise((resolve) => { + if (import.meta.env.DEV) { + import("./data"); + } + resolve(); + }); +} diff --git a/src/views/notify/index.vue b/src/views/notify/index.vue index 4ad5e9f..91e5a26 100644 --- a/src/views/notify/index.vue +++ b/src/views/notify/index.vue @@ -1,6 +1,10 @@