-
+
@@ -107,8 +107,8 @@ useHead({
@@ -180,30 +180,30 @@ useHead({
class="cursor-pointer backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 duration-500 hover:shadow-2xl hover:shadow-primary-500/20 hover:-translate-y-2 hover:rotate-y-2 active:scale-95 active:shadow-lg active:shadow-primary-500/40 relative overflow-hidden"
@click="openAppDetail(app)"
>
-
-
-
-
+
+
+
+
-
-
![]()
+
+
- {{ app.name }}
-
-
- {{ app.shortDescription[locale as 'zh-CN' | 'en-US'] }}
-
-
- v{{ app.version }}
- •
- {{ app.stats.total.toLocaleString() }} {{ locale === 'zh-CN' ? '次下载' : 'downloads' }}
-
+ {{ app.name }}
+
+
+ {{ app.shortDescription[locale as 'zh-CN' | 'en-US'] }}
+
+
+ v{{ app.version }}
+ •
+ {{ app.stats.total.toLocaleString() }} {{ locale === 'zh-CN' ? '次下载' : 'downloads' }}
+
@@ -217,8 +217,7 @@ useHead({
block
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-xl active:shadow-blue-500/60 relative overflow-hidden group/btn"
@click.stop="(e) => handleDownload(app, 'ios', e)"
- >
-
+ />
handleDownload(app, 'android', e)"
- >
-
+ />
handleDownload(app, 'h5', e)"
- >
-
+ />
@@ -262,4 +259,4 @@ useHead({
-
\ No newline at end of file
+
diff --git a/packages/distribute/server/api/apps.get.ts b/packages/distribute/server/api/apps.get.ts
index 9db8f3e..a6f23ca 100644
--- a/packages/distribute/server/api/apps.get.ts
+++ b/packages/distribute/server/api/apps.get.ts
@@ -1,8 +1,8 @@
-import { getAllApps, categories } from '~/data/apps'
+import { categories, getAllApps } from "~/data/apps";
export default defineEventHandler(() => {
return {
apps: getAllApps(),
categories,
- }
-})
+ };
+});
diff --git a/packages/distribute/server/api/apps/[id].get.ts b/packages/distribute/server/api/apps/[id].get.ts
index 3517fba..9d96d57 100644
--- a/packages/distribute/server/api/apps/[id].get.ts
+++ b/packages/distribute/server/api/apps/[id].get.ts
@@ -1,23 +1,23 @@
-import { getAppById } from '~/data/apps'
+import { getAppById } from "~/data/apps";
export default defineEventHandler((event) => {
- const id = getRouterParam(event, 'id')
+ const id = getRouterParam(event, "id");
if (!id) {
throw createError({
statusCode: 400,
- message: 'App ID is required',
- })
+ message: "App ID is required",
+ });
}
- const app = getAppById(id)
+ const app = getAppById(id);
if (!app) {
throw createError({
statusCode: 404,
- message: 'App not found',
- })
+ message: "App not found",
+ });
}
- return app
-})
+ return app;
+});
diff --git a/packages/distribute/server/api/stats.get.ts b/packages/distribute/server/api/stats.get.ts
index fcd06ca..88780f6 100644
--- a/packages/distribute/server/api/stats.get.ts
+++ b/packages/distribute/server/api/stats.get.ts
@@ -1,3 +1,3 @@
export default defineEventHandler(async () => {
- return await fetchDownloadStats()
-})
+ return await fetchDownloadStats();
+});
diff --git a/packages/distribute/server/api/track/[platform].post.ts b/packages/distribute/server/api/track/[platform].post.ts
index 4958acd..20361f2 100644
--- a/packages/distribute/server/api/track/[platform].post.ts
+++ b/packages/distribute/server/api/track/[platform].post.ts
@@ -1,16 +1,16 @@
export default defineEventHandler(async (event) => {
- const platform = getRouterParam(event, 'platform') as 'ios' | 'android' | 'h5'
- const body = await readBody(event)
- const appId = body?.appId
+ const platform = getRouterParam(event, "platform") as "ios" | "android" | "h5";
+ const body = await readBody(event);
+ const appId = body?.appId;
- if (!['ios', 'android', 'h5'].includes(platform)) {
+ if (!["ios", "android", "h5"].includes(platform)) {
throw createError({
statusCode: 400,
- message: 'Invalid platform',
- })
+ message: "Invalid platform",
+ });
}
- await trackDownload(platform)
+ await trackDownload(platform);
- return { success: true, appId, platform }
-})
+ return { success: true, appId, platform };
+});
diff --git a/packages/distribute/server/api/version.get.ts b/packages/distribute/server/api/version.get.ts
index d7dc6a0..64aef44 100644
--- a/packages/distribute/server/api/version.get.ts
+++ b/packages/distribute/server/api/version.get.ts
@@ -1,5 +1,5 @@
-import { currentVersion } from '~/data/versions'
+import { currentVersion } from "~/data/versions";
export default defineEventHandler(() => {
- return currentVersion
-})
+ return currentVersion;
+});
diff --git a/packages/distribute/server/utils/stats.ts b/packages/distribute/server/utils/stats.ts
index 419f615..cb5fcdf 100644
--- a/packages/distribute/server/utils/stats.ts
+++ b/packages/distribute/server/utils/stats.ts
@@ -1,16 +1,16 @@
-import type { DownloadStats } from '~/types'
-import { mockDownloadStats } from '~/data/versions'
+import type { DownloadStats } from "~/types";
+import { mockDownloadStats } from "~/data/versions";
// 获取下载统计(可替换为真实 API)
export async function fetchDownloadStats(): Promise
{
// 模拟 API 延迟
- await new Promise(resolve => setTimeout(resolve, 500))
- return mockDownloadStats
+ await new Promise(resolve => setTimeout(resolve, 500));
+ return mockDownloadStats;
}
// 记录下载事件(可替换为真实 API)
-export async function trackDownload(platform: 'ios' | 'android' | 'h5'): Promise {
+export async function trackDownload(platform: "ios" | "android" | "h5"): Promise {
// 模拟 API 延迟
- await new Promise(resolve => setTimeout(resolve, 200))
- console.log(`Download tracked: ${platform}`)
+ await new Promise(resolve => setTimeout(resolve, 200));
+ console.log(`Download tracked: ${platform}`);
}
diff --git a/packages/distribute/types/index.ts b/packages/distribute/types/index.ts
index caced7e..6db9ef3 100644
--- a/packages/distribute/types/index.ts
+++ b/packages/distribute/types/index.ts
@@ -1,65 +1,65 @@
export interface AppInfo {
- id: string
- name: string
- icon: string
+ id: string;
+ name: string;
+ icon: string;
shortDescription: {
- 'zh-CN': string
- 'en-US': string
- }
+ "zh-CN": string;
+ "en-US": string;
+ };
description: {
- 'zh-CN': string
- 'en-US': string
- }
- category: string
- version: string
- buildNumber: string
- releaseDate: string
+ "zh-CN": string;
+ "en-US": string;
+ };
+ category: string;
+ version: string;
+ buildNumber: string;
+ releaseDate: string;
releaseNotes: {
- 'zh-CN': string[]
- 'en-US': string[]
- }
+ "zh-CN": string[];
+ "en-US": string[];
+ };
downloads: {
- ios?: string
- android?: string
- h5?: string
- }
- screenshots?: string[]
+ ios?: string;
+ android?: string;
+ h5?: string;
+ };
+ screenshots?: string[];
size?: {
- ios?: string
- android?: string
- }
- stats: DownloadStats
+ ios?: string;
+ android?: string;
+ };
+ stats: DownloadStats;
}
export interface AppVersion {
- version: string
- buildNumber: string
- releaseDate: string
+ version: string;
+ buildNumber: string;
+ releaseDate: string;
releaseNotes: {
- 'zh-CN': string[]
- 'en-US': string[]
- }
+ "zh-CN": string[];
+ "en-US": string[];
+ };
downloads: {
- ios: string
- android: string
- h5: string
- }
+ ios: string;
+ android: string;
+ h5: string;
+ };
}
export interface DownloadStats {
- total: number
- today: number
- ios: number
- android: number
+ total: number;
+ today: number;
+ ios: number;
+ android: number;
}
-export type Platform = 'ios' | 'android' | 'desktop' | 'unknown'
+export type Platform = "ios" | "android" | "desktop" | "unknown";
export interface AppCategory {
- id: string
+ id: string;
name: {
- 'zh-CN': string
- 'en-US': string
- }
- icon?: string
+ "zh-CN": string;
+ "en-US": string;
+ };
+ icon?: string;
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 93a400c..76a53f7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -62,9 +62,9 @@ importers:
'@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))
- '@vee-validate/yup':
+ '@vee-validate/zod':
specifier: ^4.15.1
- version: 4.15.1(vue@3.5.25(typescript@5.9.3))(yup@1.7.1)
+ version: 4.15.1(vue@3.5.25(typescript@5.9.3))(zod@3.25.76)
'@vueuse/core':
specifier: ^14.1.0
version: 14.1.0(vue@3.5.25(typescript@5.9.3))
@@ -119,9 +119,9 @@ importers:
vue-router:
specifier: ^4.6.3
version: 4.6.3(vue@3.5.25(typescript@5.9.3))
- yup:
- specifier: ^1.7.1
- version: 1.7.1
+ zod:
+ specifier: ^3.24.1
+ version: 3.25.76
devDependencies:
'@antfu/eslint-config':
specifier: ^6.6.1
@@ -3692,10 +3692,10 @@ packages:
cpu: [x64]
os: [win32]
- '@vee-validate/yup@4.15.1':
- resolution: {integrity: sha512-+u6lI1IZftjHphj+mTCPJRruwBBwv1IKKCI1EFm6ipQroAPibkS5M8UNX+yeVYG5++ix6m1rsv4/SJvJJQTWJg==}
+ '@vee-validate/zod@4.15.1':
+ resolution: {integrity: sha512-329Z4TDBE5Vx0FdbA8S4eR9iGCFFUNGbxjpQ20ff5b5wGueScjocUIx9JHPa79LTG06RnlUR4XogQsjN4tecKA==}
peerDependencies:
- yup: ^1.3.2
+ zod: ^3.24.0
'@vercel/nft@0.30.4':
resolution: {integrity: sha512-wE6eAGSXScra60N2l6jWvNtVK0m+sh873CpfZW4KI2v8EHuUQp+mSEi4T+IcdPCSEDgCdAS/7bizbhQlkjzrSA==}
@@ -9264,6 +9264,9 @@ packages:
zod@3.22.3:
resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==}
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
zod@4.1.13:
resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==}
@@ -10036,7 +10039,7 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
- '@better-auth/core@1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0)':
+ '@better-auth/core@1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@3.25.76))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0)':
dependencies:
'@better-auth/utils': 0.3.0
'@better-fetch/fetch': 1.1.18
@@ -10047,9 +10050,9 @@ snapshots:
nanostores: 1.1.0
zod: 4.1.13
- '@better-auth/telemetry@1.4.6(@better-auth/core@1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0))':
+ '@better-auth/telemetry@1.4.6(@better-auth/core@1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@3.25.76))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0))':
dependencies:
- '@better-auth/core': 1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0)
+ '@better-auth/core': 1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@3.25.76))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0)
'@better-auth/utils': 0.3.0
'@better-fetch/fetch': 1.1.18
@@ -12957,11 +12960,11 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
- '@vee-validate/yup@4.15.1(vue@3.5.25(typescript@5.9.3))(yup@1.7.1)':
+ '@vee-validate/zod@4.15.1(vue@3.5.25(typescript@5.9.3))(zod@3.25.76)':
dependencies:
type-fest: 4.41.0
vee-validate: 4.15.1(vue@3.5.25(typescript@5.9.3))
- yup: 1.7.1
+ zod: 3.25.76
transitivePeerDependencies:
- vue
@@ -13584,8 +13587,8 @@ snapshots:
better-auth@1.4.6(vue@3.5.25(typescript@5.9.3)):
dependencies:
- '@better-auth/core': 1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0)
- '@better-auth/telemetry': 1.4.6(@better-auth/core@1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@4.1.13))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0))
+ '@better-auth/core': 1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@3.25.76))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0)
+ '@better-auth/telemetry': 1.4.6(@better-auth/core@1.4.6(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.5(zod@3.25.76))(jose@6.1.3)(kysely@0.28.8)(nanostores@1.1.0))
'@better-auth/utils': 0.3.0
'@better-fetch/fetch': 1.1.18
'@noble/ciphers': 2.1.1
@@ -17476,7 +17479,8 @@ snapshots:
kleur: 3.0.3
sisteransi: 1.0.5
- property-expr@2.0.6: {}
+ property-expr@2.0.6:
+ optional: true
prosemirror-changeset@2.3.1:
dependencies:
@@ -18408,7 +18412,8 @@ snapshots:
through@2.3.8: {}
- tiny-case@1.0.3: {}
+ tiny-case@1.0.3:
+ optional: true
tiny-inflate@1.0.3: {}
@@ -18464,7 +18469,8 @@ snapshots:
dependencies:
eslint-visitor-keys: 3.4.3
- toposort@2.0.2: {}
+ toposort@2.0.2:
+ optional: true
tosource@2.0.0-alpha.3: {}
@@ -18523,7 +18529,8 @@ snapshots:
type-fest@0.8.1: {}
- type-fest@2.19.0: {}
+ type-fest@2.19.0:
+ optional: true
type-fest@4.41.0: {}
@@ -19493,6 +19500,7 @@ snapshots:
tiny-case: 1.0.3
toposort: 2.0.2
type-fest: 2.19.0
+ optional: true
zip-stream@6.0.1:
dependencies:
@@ -19502,6 +19510,8 @@ snapshots:
zod@3.22.3: {}
+ zod@3.25.76: {}
+
zod@4.1.13: {}
zwitch@2.0.4: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index dee51e9..b4e341b 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,2 +1,6 @@
+catalogMode: prefer
+shellEmulator: true
+trustPolicy: no-downgrade
+
packages:
- "packages/*"
diff --git a/src/auth/index.ts b/src/auth/index.ts
index da6a200..a458e2b 100644
--- a/src/auth/index.ts
+++ b/src/auth/index.ts
@@ -1,8 +1,8 @@
import type { PhoneCountry } from "./type";
-import { toTypedSchema } from "@vee-validate/yup";
+import { toTypedSchema } from "@vee-validate/zod";
import { emailOTPClient, phoneNumberClient, usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/vue";
-import * as yup from "yup";
+import { z } from "zod";
import CircleFlagsCnHk from "~icons/circle-flags/cn-hk";
import CircleFlagsEnUs from "~icons/circle-flags/en-us";
import CircleFlagsTw from "~icons/circle-flags/tw";
@@ -23,15 +23,15 @@ export const authClient = createAuthClient({
plugins: [emailOTPClient(), phoneNumberClient(), usernameClient()],
});
-export const emailSchema = toTypedSchema(yup.object({
- email: yup
- .string()
- .required(i18n.global.t("auth.login.validation.emailRequired"))
+export const emailSchema = toTypedSchema(z.object({
+ email: z
+ .string({ message: i18n.global.t("auth.login.validation.emailRequired") })
+ .min(1, i18n.global.t("auth.login.validation.emailRequired"))
.email(i18n.global.t("auth.login.validation.emailInvalid")),
- otp: yup
- .string()
- .required(i18n.global.t("auth.login.validation.otpRequired"))
- .matches(/^\d{6}$/, i18n.global.t("auth.login.validation.otpInvalid")),
+ otp: z
+ .string({ message: i18n.global.t("auth.login.validation.otpRequired") })
+ .min(1, i18n.global.t("auth.login.validation.otpRequired"))
+ .regex(/^\d{6}$/, i18n.global.t("auth.login.validation.otpInvalid")),
}));
export const countries: PhoneCountry[] = [
diff --git a/src/theme/common.css b/src/theme/common.css
index d29c289..bf045a1 100644
--- a/src/theme/common.css
+++ b/src/theme/common.css
@@ -1,5 +1,5 @@
.unselectable {
- user-select: none;
- --moz-user-select: none;
- -webkit-user-select: none;
-}
\ No newline at end of file
+ user-select: none;
+ --moz-user-select: none;
+ -webkit-user-select: none;
+}
diff --git a/src/theme/index.css b/src/theme/index.css
index a326662..8934293 100644
--- a/src/theme/index.css
+++ b/src/theme/index.css
@@ -1,3 +1,3 @@
@import "tailwindcss";
@config "../../tailwind.config.ts";
-@import "./common.css";
\ No newline at end of file
+@import "./common.css";
diff --git a/src/views/auth/login/components/email.vue b/src/views/auth/login/components/email.vue
index bc7f3b6..4531a25 100644
--- a/src/views/auth/login/components/email.vue
+++ b/src/views/auth/login/components/email.vue
@@ -3,7 +3,7 @@ import type { GenericObject } from "vee-validate";
import type { EmailVerifyClient } from "@/api/types";
import { toastController } from "@ionic/vue";
import { Field, Form } from "vee-validate";
-import * as yup from "yup";
+import { z } from "zod";
import { authClient, emailSchema } from "@/auth";
const emit = defineEmits<{
@@ -19,7 +19,7 @@ const canResend = computed(() => countdown.value === 0 && !isSending.value);
const email = ref("");
const emailError = ref("");
-let timer: number | null = null;
+let timer: NodeJS.Timeout | null = null;
function startCountdown() {
countdown.value = 60;
@@ -42,7 +42,7 @@ async function sendOtp() {
}
try {
- await yup.string().email().validate(emailValue);
+ await z.string().email().parseAsync(emailValue);
}
catch {
emailError.value = t("auth.login.validation.emailInvalid");
diff --git a/src/views/auth/login/components/phone-number.vue b/src/views/auth/login/components/phone-number.vue
index 1330e60..3dfdd5f 100644
--- a/src/views/auth/login/components/phone-number.vue
+++ b/src/views/auth/login/components/phone-number.vue
@@ -3,10 +3,10 @@ import type { GenericObject } from "vee-validate";
import type { PhoneNumberVerifyClient } from "@/api/types";
import type { PhoneCountry } from "@/auth/type";
import { toastController } from "@ionic/vue";
-import { toTypedSchema } from "@vee-validate/yup";
+import { toTypedSchema } from "@vee-validate/zod";
import { chevronDown } from "ionicons/icons";
import { Field, Form } from "vee-validate";
-import * as yup from "yup";
+import { z } from "zod";
import { authClient, countries } from "@/auth";
import Country from "./country.vue";
@@ -42,19 +42,18 @@ function validatePhoneNumber(phone: string): boolean {
return currentCountry.value.pattern.test(phone);
}
-const schema = computed(() => toTypedSchema(yup.object({
- phoneNumber: yup
- .string()
- .required(t("auth.login.validation.phoneNumberRequired"))
- .test(
- "phone-format",
+const schema = computed(() => toTypedSchema(z.object({
+ phoneNumber: z
+ .string({ message: t("auth.login.validation.phoneNumberRequired") })
+ .min(1, t("auth.login.validation.phoneNumberRequired"))
+ .refine(
+ value => validatePhoneNumber(value),
t("auth.login.validation.phoneNumberInvalid"),
- value => !value || validatePhoneNumber(value),
),
- code: yup
- .string()
- .required(t("auth.login.validation.codeRequired"))
- .matches(/^\d{6}$/, t("auth.login.validation.codeInvalid")),
+ code: z
+ .string({ message: t("auth.login.validation.codeRequired") })
+ .min(1, t("auth.login.validation.codeRequired"))
+ .regex(/^\d{6}$/, t("auth.login.validation.codeInvalid")),
})));
function startCountdown() {
diff --git a/src/views/issue/issuing-apply/base.vue b/src/views/issue/issuing-apply/base.vue
index 4512af3..ccb5660 100644
--- a/src/views/issue/issuing-apply/base.vue
+++ b/src/views/issue/issuing-apply/base.vue
@@ -1,10 +1,10 @@
- Hello world
+
+
+
+
+
+ {{ form.symbol }}
+
+
+ {{ form.side === 'buy' ? '买入' : '卖出' }}
+
+
+
+
+
+ 委托价格
+
+
+ {{ form.price }} USDT
+
+
+
+
+ 数量
+
+
+ {{ form.quantity }}
+
+
+
+
+ 金额
+
+
+ {{ form.amount }} USDT
+
+
+
+
+ 类型
+
+
+ {{ currentTradeWay?.name }}
+
+
+
+
+
+
+
+ 委托价格
+
+
+ {{ form.price }} USDT
+
+
+
+
+ 数量
+
+
+ {{ form.quantity }}
+
+
+
+
+
+
+
+ 确认下单
+
+
+
-
+
diff --git a/src/views/trade/components/trade-way.vue b/src/views/trade/components/trade-way.vue
index aeb6266..d9ee341 100644
--- a/src/views/trade/components/trade-way.vue
+++ b/src/views/trade/components/trade-way.vue
@@ -7,6 +7,9 @@ import { caretDownOutline } from "ionicons/icons";
import { tradeWayConfig } from "../config";
const model = defineModel({ type: Object as PropType, required: true });
+const currentTradeWay = computed(() => {
+ return tradeWayConfig.find(item => item.value === model.value.orderType);
+});
function onSelectTradeWay(item: TradeWayConfig) {
model.value.orderType = item.value;
@@ -17,7 +20,7 @@ function onSelectTradeWay(item: TradeWayConfig) {
- 市场
+ {{ currentTradeWay?.name }}
diff --git a/src/views/trade/config.ts b/src/views/trade/config.ts
index 42d66fd..8446e7f 100644
--- a/src/views/trade/config.ts
+++ b/src/views/trade/config.ts
@@ -1,4 +1,4 @@
-import * as yup from "yup";
+import { z } from "zod";
export enum TradeWayValueEnum {
LIMIT = "limit",
@@ -21,28 +21,35 @@ export const tradeWayConfig: TradeWayConfig[] = [
description: "以指定价格买入或卖出",
icon: "hugeicons:trade-up",
},
+ {
+ name: "市价委托",
+ value: "market",
+ description: "以市场价格买入或卖出",
+ icon: "hugeicons:trade-down",
+ },
];
-export const confirmOrderSchema = yup.object({
- price: yup.number().when("way", {
- is: TradeWayValueEnum.LIMIT !== undefined,
- then: yup
- .number()
- .typeError("请输入有效的价格")
- .required("价格为必填项")
- .moreThan(0, "价格必须大于0"),
- otherwise: yup.number().notRequired(),
- }),
- amount: yup
- .number()
- .typeError("请输入有效的数量")
- .required("数量为必填项")
- .moreThan(0, "数量必须大于0"),
- way: yup
- .mixed()
- .oneOf(
- Object.values(TradeWayValueEnum),
- "请选择有效的交易方式",
- )
- .required("交易方式为必填项"),
-});
+export const confirmOrderSchema = z.object({
+ quantity: z.coerce.number({ message: "请输入有效的数量" }).gt(0, "数量必须大于0"),
+ price: z.coerce.number({ message: "请输入有效的价格" }).gt(0, "价格必须大于0").optional().or(z.coerce.number().optional()),
+ orderType: z.enum([TradeWayValueEnum.LIMIT, TradeWayValueEnum.MARKET], {
+ message: "请选择有效的交易方式",
+ }) as z.ZodType,
+}).refine(
+ (data) => {
+ if (data.orderType === TradeWayValueEnum.LIMIT) {
+ return data.price !== undefined && data.price > 0;
+ }
+ return true;
+ },
+ {
+ message: "价格为必填项",
+ path: ["price"],
+ },
+);
+
+export const confirmOrderSubmitSchema = confirmOrderSchema.transform(data => ({
+ ...data,
+ quantity: data.quantity.toString(),
+ price: data.price?.toString() ?? "",
+}));
diff --git a/src/views/trade/index.vue b/src/views/trade/index.vue
index 8955bf5..744b5b4 100644
--- a/src/views/trade/index.vue
+++ b/src/views/trade/index.vue
@@ -2,6 +2,7 @@
import type { ChartingLibraryWidgetOptions } from "#/charting_library";
import type { SpotOrderBody } from "@/api/types";
import type { TradingViewInst } from "@/tradingview/index";
+import type { ModalInstance } from "@/utils";
import { modalController } from "@ionic/vue";
import { useRouteQuery } from "@vueuse/router";
import { caretDownOutline, ellipsisHorizontal } from "ionicons/icons";
@@ -9,28 +10,32 @@ import MaterialSymbolsCandlestickChartOutline from "~icons/material-symbols/cand
import { client, safeClient } from "@/api";
import { TradeTypeEnum } from "@/api/enum";
import { TradingViewChart } from "@/tradingview/index";
+import ConfirmOrder from "./components/confirm-order.vue";
import OrdersPanel from "./components/orders-panel.vue";
import TradePairsModal from "./components/trade-pairs-modal.vue";
import TradeSwitch from "./components/trade-switch.vue";
import TradeWay from "./components/trade-way.vue";
-import { confirmOrderSchema, TradeWayValueEnum } from "./config";
+import { confirmOrderSubmitSchema, TradeWayValueEnum } from "./config";
+const { data } = await safeClient(client.api.trading_pairs.get({ query: { limit: 1 } }));
const mode = useRouteQuery("mode", TradeTypeEnum.BUY);
-const symbol = useRouteQuery("symbol", "BTCUSD");
-
+const symbol = useRouteQuery("symbol", data.value?.data[0].symbol);
const tradingviewOptions: Partial = {
disabled_features: [
"create_volume_indicator_by_default",
],
};
const tradingViewInst = useTemplateRef("tradingViewInst");
-const [form] = useResetRef({
+const confirmModalInst = useTemplateRef("confirmModalInst");
+
+const [form] = useResetRef({
orderType: TradeWayValueEnum.LIMIT,
quantity: "",
side: mode.value,
symbol: symbol.value,
memo: "",
price: "",
+ amount: "",
});
async function openTradePairs() {
@@ -40,22 +45,38 @@ async function openTradePairs() {
initialBreakpoint: 0.95,
handle: true,
});
-
await modal.present();
-
const { data: result } = await modal.onWillDismiss();
-
- if (result) {
- symbol.value = result;
+ result && (symbol.value = result);
+}
+function handleChangeQuantity(event) {
+ const val = (event.target as HTMLInputElement).value;
+ if (val && form.value.price) {
+ const amount = Number(val) * Number(form.value.price);
+ form.value.amount = amount.toString();
+ }
+ else {
+ form.value.amount = "";
}
}
-
-function handleSubmit() {
- confirmOrderSchema.validate(form.value).then(() => {
- console.log("submit successfully");
- }).catch((err) => {
- console.log("submit failed:", err);
- });
+function handleChangeAmount(event) {
+ const val = (event.target as HTMLInputElement).value;
+ if (val && form.value.price) {
+ const quantity = Number(val) / Number(form.value.price);
+ form.value.quantity = quantity.toString();
+ }
+ else {
+ form.value.quantity = "";
+ }
+}
+async function handleSubmit() {
+ try {
+ await confirmOrderSubmitSchema.parseAsync(form.value);
+ confirmModalInst.value?.$el.present();
+ }
+ catch (err) {
+ console.error("订单验证失败:", err);
+ }
}
@@ -90,21 +111,37 @@ function handleSubmit() {
form.side = val" />
-
+
+ USDT
+
+
+ {{ symbol }}
+
+
+ USDT
+
-
- {{ symbol }}
-
-
- USDT
-
+
+
+ USDT
+
+
+ {{ symbol }}
+
+
+
{{ mode === TradeTypeEnum.BUY ? '买入' : '卖出' }}
+
+
+
+