diff --git a/.env b/.env deleted file mode 100644 index c5d1ca7..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -VITE_API_URL=http://192.168.1.36:9527 \ No newline at end of file diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..72038a4 --- /dev/null +++ b/.env.development @@ -0,0 +1 @@ +VITE_API_URL=http://192.168.1.33:9528 \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..240ca57 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VITE_API_URL=http://192.168.1.33:9527 \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1ff89e8..5367796 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -69,6 +69,7 @@ API 集成: 使用 TypeScript 严格模式,避免 any 类型 组件命名使用 PascalCase,文件名使用 PascalCase 或 kebab-case 使用 @/ 作为 src 目录的别名 +颜色主题使用ion-color 变量,支持深色模式和浅色模式 2. 项目结构 ``` src/ diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 1d66dfe..e54fa92 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -199,6 +199,7 @@ declare global { const useFullscreen: typeof import('@vueuse/core').useFullscreen const useGamepad: typeof import('@vueuse/core').useGamepad const useGeolocation: typeof import('@vueuse/core').useGeolocation + const useHaptics: typeof import('./src/composables/useVibrate').useHaptics const useI18n: typeof import('vue-i18n').useI18n const useId: typeof import('vue').useId const useIdle: typeof import('@vueuse/core').useIdle @@ -333,6 +334,9 @@ declare global { export type { Series, TData, WeightChartOptions, TradingViewData, TradingViewOptions } from './src/composables/useTradingView' import('./src/composables/useTradingView') // @ts-ignore + export type { HapticsOptions } from './src/composables/useVibrate' + import('./src/composables/useVibrate') + // @ts-ignore export type { PageInstance, InputInstance, ModalInstance, FormInstance } from './src/utils/ionic-helper' import('./src/utils/ionic-helper') } @@ -535,6 +539,7 @@ declare module 'vue' { readonly useFullscreen: UnwrapRef readonly useGamepad: UnwrapRef readonly useGeolocation: UnwrapRef + readonly useHaptics: UnwrapRef readonly useI18n: UnwrapRef readonly useId: UnwrapRef readonly useIdle: UnwrapRef diff --git a/components.d.ts b/components.d.ts index 7a40da9..c85f90c 100644 --- a/components.d.ts +++ b/components.d.ts @@ -20,11 +20,15 @@ declare module 'vue' { IonBackButton: typeof import('@ionic/vue')['IonBackButton'] IonButton: typeof import('@ionic/vue')['IonButton'] IonButtons: typeof import('@ionic/vue')['IonButtons'] + IonCol: typeof import('@ionic/vue')['IonCol'] IonContent: typeof import('@ionic/vue')['IonContent'] IonDatetime: typeof import('@ionic/vue')['IonDatetime'] IonDatetimeButton: typeof import('@ionic/vue')['IonDatetimeButton'] + IonGrid: typeof import('@ionic/vue')['IonGrid'] IonHeader: typeof import('@ionic/vue')['IonHeader'] IonIcon: typeof import('@ionic/vue')['IonIcon'] + IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll'] + IonInfiniteScrollContent: typeof import('@ionic/vue')['IonInfiniteScrollContent'] IonInputOtp: typeof import('@ionic/vue')['IonInputOtp'] IonItem: typeof import('@ionic/vue')['IonItem'] IonLabel: typeof import('@ionic/vue')['IonLabel'] @@ -34,7 +38,10 @@ declare module 'vue' { IonPage: typeof import('@ionic/vue')['IonPage'] IonRadio: typeof import('@ionic/vue')['IonRadio'] IonRadioGroup: typeof import('@ionic/vue')['IonRadioGroup'] + IonRefresher: typeof import('@ionic/vue')['IonRefresher'] + IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent'] IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet'] + IonRow: typeof import('@ionic/vue')['IonRow'] IonSearchbar: typeof import('@ionic/vue')['IonSearchbar'] IonSelect: typeof import('@ionic/vue')['IonSelect'] IonSelectOption: typeof import('@ionic/vue')['IonSelectOption'] @@ -70,11 +77,15 @@ declare global { const IonBackButton: typeof import('@ionic/vue')['IonBackButton'] const IonButton: typeof import('@ionic/vue')['IonButton'] const IonButtons: typeof import('@ionic/vue')['IonButtons'] + const IonCol: typeof import('@ionic/vue')['IonCol'] const IonContent: typeof import('@ionic/vue')['IonContent'] const IonDatetime: typeof import('@ionic/vue')['IonDatetime'] const IonDatetimeButton: typeof import('@ionic/vue')['IonDatetimeButton'] + const IonGrid: typeof import('@ionic/vue')['IonGrid'] const IonHeader: typeof import('@ionic/vue')['IonHeader'] const IonIcon: typeof import('@ionic/vue')['IonIcon'] + const IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll'] + const IonInfiniteScrollContent: typeof import('@ionic/vue')['IonInfiniteScrollContent'] const IonInputOtp: typeof import('@ionic/vue')['IonInputOtp'] const IonItem: typeof import('@ionic/vue')['IonItem'] const IonLabel: typeof import('@ionic/vue')['IonLabel'] @@ -84,7 +95,10 @@ declare global { const IonPage: typeof import('@ionic/vue')['IonPage'] const IonRadio: typeof import('@ionic/vue')['IonRadio'] const IonRadioGroup: typeof import('@ionic/vue')['IonRadioGroup'] + const IonRefresher: typeof import('@ionic/vue')['IonRefresher'] + const IonRefresherContent: typeof import('@ionic/vue')['IonRefresherContent'] const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet'] + const IonRow: typeof import('@ionic/vue')['IonRow'] const IonSearchbar: typeof import('@ionic/vue')['IonSearchbar'] const IonSelect: typeof import('@ionic/vue')['IonSelect'] const IonSelectOption: typeof import('@ionic/vue')['IonSelectOption'] diff --git a/package.json b/package.json index 1046f31..5160cf3 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview", "run:ios": "ionic capacitor run ios -l --external", "run:android": "ionic capacitor run android -l --external", + "proxy": "ionic config set -g proxy http://192.168.1.36:9527", "test:e2e": "cypress run", "test:unit": "vitest", "lint": "eslint", @@ -28,7 +29,7 @@ "@elysiajs/eden": "^1.4.5", "@ionic/vue": "^8.7.11", "@ionic/vue-router": "^8.7.11", - "@riwa/api-types": "http://192.168.1.36:9527/api/riwa-api-types-0.0.29.tgz", + "@riwa/api-types": "http://192.168.1.33:9527/api/riwa-api-types-0.0.30.tgz", "@tailwindcss/vite": "^4.1.18", "@vee-validate/yup": "^4.15.1", "@vueuse/core": "^14.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a551d68..1ce2856 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^8.7.11 version: 8.7.11(@stencil/core@4.39.0)(vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)))(vue@3.5.25(typescript@5.9.3)) '@riwa/api-types': - specifier: http://192.168.1.36:9527/api/riwa-api-types-0.0.29.tgz - version: http://192.168.1.36:9527/api/riwa-api-types-0.0.29.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3))) + specifier: http://192.168.1.33:9527/api/riwa-api-types-0.0.30.tgz + version: http://192.168.1.33:9527/api/riwa-api-types-0.0.30.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3))) '@tailwindcss/vite': specifier: ^4.1.18 version: 4.1.18(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) @@ -1302,9 +1302,9 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@riwa/api-types@http://192.168.1.36:9527/api/riwa-api-types-0.0.29.tgz': - resolution: {tarball: http://192.168.1.36:9527/api/riwa-api-types-0.0.29.tgz} - version: 0.0.29 + '@riwa/api-types@http://192.168.1.33:9527/api/riwa-api-types-0.0.30.tgz': + resolution: {tarball: http://192.168.1.33:9527/api/riwa-api-types-0.0.30.tgz} + version: 0.0.30 peerDependencies: '@elysiajs/eden': ^1.4.5 @@ -6341,7 +6341,7 @@ snapshots: '@pkgr/core@0.2.9': {} - '@riwa/api-types@http://192.168.1.36:9527/api/riwa-api-types-0.0.29.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))': + '@riwa/api-types@http://192.168.1.33:9527/api/riwa-api-types-0.0.30.tgz(@elysiajs/eden@1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)))': dependencies: '@elysiajs/eden': 1.4.5(elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.5(@sinclair/typebox@0.34.41))(file-type@21.1.1)(openapi-types@12.1.3)(typescript@5.9.3)) diff --git a/src/api/index.ts b/src/api/index.ts index d5b3e8a..a10bdeb 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,5 @@ import type { App } from "@riwa/api-types"; -import type { Awaitable } from "@vueuse/core"; +import type { WatchSource } from "vue"; import { treaty } from "@elysiajs/eden"; import { toastController } from "@ionic/vue"; @@ -12,26 +12,42 @@ const client = treaty(window.location.origin, { export interface SafeClientOptions { silent?: boolean; immediate?: boolean; + watchSource?: WatchSource; // 用于监听的响应式数据源 } export interface SafeClientReturn { data: Ref; error: Ref; - refresh: () => Promise; + isPending: Ref; + execute: () => Promise; onFetchResponse: (callback: (data: T, error: E) => void) => void; + stopWatching?: () => void; } export function safeClient( - requestPromise: () => Promise<{ data: T; error: E }>, + requestPromise: (() => Promise<{ data: T; error: E }>) | Promise<{ data: T; error: E }>, options: SafeClientOptions = {}, ): SafeClientReturn & Promise> { - const { immediate = true } = options; + const { immediate = true, watchSource } = options; const data = ref(null); const error = ref(null); + const isPending = ref(false); let responseCallback: ((data: T, error: E) => void) | null = null; + let stopWatcher: (() => void) | undefined; const execute = async () => { - const res = await requestPromise(); + isPending.value = true; + let request: () => Promise<{ data: T; error: E }>; + if (typeof requestPromise !== "function") { + request = () => Promise.resolve(requestPromise); + } + else { + request = requestPromise; + } + + const res = await request().finally(() => { + isPending.value = false; + }); if (res.error) { let errMsg = ""; @@ -44,6 +60,7 @@ export function safeClient( else if (res.error && "value" in (res.error as unknown as object)) { errMsg = String((res.error as unknown as { value: string }).value); } + // if(res.error && typeof res.error === 'object' && 'err' in res.error) { if (!options.silent) { const toast = await toastController.create({ message: errMsg, @@ -69,17 +86,35 @@ export function safeClient( responseCallback = callback; } + function stopWatching() { + if (stopWatcher) { + stopWatcher(); + stopWatcher = undefined; + } + } + + // 如果提供了 watchSource,则监听其变化 + if (watchSource) { + stopWatcher = watch( + watchSource, + () => { + execute(); + }, + { immediate: false }, // 不立即执行,避免与 immediate 选项冲突 + ); + } + const result: SafeClientReturn = { data: data as Ref, error: error as Ref, - refresh: execute, + isPending, + execute, onFetchResponse, + stopWatching, }; - // 创建一个 Promise 并在其上添加属性 const promise = immediate ? execute().then(() => result) : Promise.resolve(result); - // 将 result 的属性添加到 promise 上 Object.assign(promise, result); return promise as SafeClientReturn & Promise>; diff --git a/src/api/types.ts b/src/api/types.ts index 0d085ae..bb8d9a3 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -45,3 +45,5 @@ export type SupportBanksData = Treaty.Data; export type AvailableSubscriptionBody = TreatyQuery; + +export type RwaData = Treaty.Data; diff --git a/src/components/ui/datetime/index.vue b/src/components/ui/datetime/index.vue index ff0d861..b1cc0e2 100644 --- a/src/components/ui/datetime/index.vue +++ b/src/components/ui/datetime/index.vue @@ -3,14 +3,20 @@ import type { FieldBindingObject } from "vee-validate"; interface Props extends FieldBindingObject { label?: string; + formatterValue?: (value: string) => string; + format?: (value: string) => string; } const props = defineProps(); function handleChange(value: string) { - const formattedValue = useDateFormat(value, "YYYY/MM/DD").value; + const formattedValue = props.formatterValue ? props.formatterValue(value) : new Date(value).toISOString(); props.onChange(formattedValue); } + +function formatDisplay(value: string) { + return props.format ? props.format(value) : useDateFormat(value, "YYYY/MM/DD").value; +} diff --git a/src/views/market/components/category.vue b/src/views/market/components/category.vue index c8ae4eb..3b155e4 100644 --- a/src/views/market/components/category.vue +++ b/src/views/market/components/category.vue @@ -7,7 +7,7 @@ const { data: categories } = await safeClient(() => client.api.rwa.issuance.cate