Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-01-11 15:51:22 +07:00
parent 6f8a8de9be
commit 309606565b
46 changed files with 28649 additions and 28522 deletions

View File

@@ -1,9 +1,130 @@
<script lang='ts' setup>
import type { SpotOrderBody } from "@/api/types";
import { modalController, toastController } from "@ionic/vue";
import { closeOutline } from "ionicons/icons";
import { client, safeClient } from "@/api";
import { tradeWayConfig } from "../config";
const props = defineProps<{
form: SpotOrderBody & { amount: string };
}>();
const currentTradeWay = computed(() => {
return tradeWayConfig.find(item => item.value === props.form.orderType);
});
function onClose() {
modalController.dismiss();
}
async function onConfirm() {
await safeClient(client.api.spot_order.create.post({
orderType: props.form.orderType,
quantity: props.form.quantity,
side: props.form.side,
symbol: props.form.symbol,
memo: props.form.memo,
price: props.form.price,
}));
const toast = await toastController.create({
message: "订单提交成功",
duration: 2000,
position: "top",
color: "success",
});
await toast.present();
modalController.dismiss();
}
</script>
<template>
Hello world
<div class="ion-padding h-80">
<div class="flex justify-between items-center mb-5">
<div class="font-semibold">
下单确认
</div>
<ion-icon :icon="closeOutline" class="text-2xl" @click="onClose" />
</div>
<div>
<div class="flex items-center gap-2 mb-2">
<div class="text-sm">
{{ form.symbol }}
</div>
<ui-tag size="mini" :type="form.side === 'buy' ? 'success' : 'danger'">
{{ form.side === 'buy' ? '买入' : '卖出' }}
</ui-tag>
</div>
<template v-if="form.orderType === 'limit'">
<div class="cell">
<div class="label">
委托价格
</div>
<div class="value">
{{ form.price }} USDT
</div>
</div>
<div class="cell">
<div class="label">
数量
</div>
<div class="value">
{{ form.quantity }}
</div>
</div>
<div class="cell">
<div class="label">
金额
</div>
<div class="value">
{{ form.amount }} USDT
</div>
</div>
<div class="cell">
<div class="label">
类型
</div>
<div class="value">
{{ currentTradeWay?.name }}
</div>
</div>
</template>
<template v-else-if="form.orderType === 'market'">
<div class="cell">
<div class="label">
委托价格
</div>
<div class="value">
{{ form.price }} USDT
</div>
</div>
<div class="cell">
<div class="label">
数量
</div>
<div class="value">
{{ form.quantity }}
</div>
</div>
</template>
</div>
<div class="mt-6">
<ion-button expand="block" color="success" @click="onConfirm">
确认下单
</ion-button>
</div>
</div>
</template>
<style lang='css' scoped></style>
<style lang='css' scoped>
@reference "tailwindcss";
.cell {
@apply flex justify-between items-center py-1;
}
.label {
@apply text-sm text-(--ion-text-color-step-400);
}
.value {
@apply text-sm font-semibold;
}
</style>

View File

@@ -7,6 +7,9 @@ import { caretDownOutline } from "ionicons/icons";
import { tradeWayConfig } from "../config";
const model = defineModel({ type: Object as PropType<SpotOrderBody>, 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) {
<template>
<div id="open-modal" class="bg-faint flex items-center justify-between px-4 py-2 rounded-md">
<div class="text-xs font-medium text-text-300">
市场
{{ currentTradeWay?.name }}
</div>
<ion-icon :icon="caretDownOutline" />
</div>

View File

@@ -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<TradeWayValue>()
.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<TradeWayValue>,
}).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() ?? "",
}));

View File

@@ -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<TradeTypeEnum>("mode", TradeTypeEnum.BUY);
const symbol = useRouteQuery<string>("symbol", "BTCUSD");
const symbol = useRouteQuery<string>("symbol", data.value?.data[0].symbol);
const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = {
disabled_features: [
"create_volume_indicator_by_default",
],
};
const tradingViewInst = useTemplateRef<TradingViewInst>("tradingViewInst");
const [form] = useResetRef<SpotOrderBody>({
const confirmModalInst = useTemplateRef<ModalInstance>("confirmModalInst");
const [form] = useResetRef<SpotOrderBody & { amount: string }>({
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<string>();
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);
}
}
</script>
@@ -90,21 +111,37 @@ function handleSubmit() {
<TradeSwitch v-model:active="mode" @update:active="val => form.side = val" />
<TradeWay v-model="form" />
<template v-if="form.orderType === 'limit'">
<ion-input v-model="form.price" label="价格" class="count" inputmode="decimal" type="number" placeholder="请输入价格(USDT)" />
<ion-input v-model="form.price" label="价格" class="count" inputmode="decimal" type="number" placeholder="请输入价格">
<span slot="end">USDT</span>
</ion-input>
<ion-input v-model="form.quantity" label="数量" class="count" inputmode="decimal" type="number" placeholder="请输入交易数量" @ion-input="handleChangeQuantity">
<span slot="end">{{ symbol }}</span>
</ion-input>
<ion-input v-model="form.amount" label="金额" class="count" inputmode="decimal" type="number" placeholder="请输入交易金额" @ion-input="handleChangeAmount">
<span slot="end">USDT</span>
</ion-input>
</template>
<ion-input v-model="form.quantity" label="数量" class="count" inputmode="decimal" type="number" placeholder="请输入交易数量">
<span slot="end">{{ symbol }}</span>
</ion-input>
<ion-input v-model="form.price" label="金额" class="count" inputmode="decimal" type="number" placeholder="请输入交易金额">
<span slot="end">USDT</span>
</ion-input>
<template v-else-if="form.orderType === 'market'">
<ion-input v-model="form.price" label="价格" class="count" inputmode="decimal" type="number" placeholder="请输入价格">
<span slot="end">USDT</span>
</ion-input>
<ion-input v-model="form.quantity" label="数量" class="count" inputmode="decimal" type="number" placeholder="请输入交易数量" @ion-input="handleChangeQuantity">
<span slot="end">{{ symbol }}</span>
</ion-input>
</template>
<!-- <ion-range class="range" aria-label="Range with ticks" :pin="true" :ticks="true" :snaps="true" :min="0" :max="5" /> -->
<ion-button expand="block" size="small" shape="round" :color="mode === TradeTypeEnum.BUY ? 'success' : 'danger'" @click="handleSubmit">
{{ mode === TradeTypeEnum.BUY ? '买入' : '卖出' }}
</ion-button>
<ion-modal ref="confirmModalInst" class="confirm-modal" :breakpoints="[0, 1]" :initial-breakpoint="1" :handle="false">
<ConfirmOrder :form="form" />
</ion-modal>
</div>
<div class="col-span-2" />
</div>
<div class="mt-6 px-4 pb-4">
<OrdersPanel />
</div>
@@ -145,4 +182,7 @@ ion-range.range::part(tick-active) {
top: 18px;
border-radius: 100%;
}
.confirm-modal {
--height: auto;
}
</style>