194 lines
6.8 KiB
Vue
194 lines
6.8 KiB
Vue
<script setup lang="ts">
|
|
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";
|
|
import MaterialSymbolsCandlestickChartOutline from "~icons/material-symbols/candlestick-chart-outline";
|
|
import { client, safeClient } from "@/api";
|
|
import { TradeTypeEnum } from "@/api/enum";
|
|
import { TradingViewChart } from "@/tradingview/index";
|
|
import ConfirmOrder from "./components/confirm-order.vue";
|
|
import OrderBook from "./components/order-book.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 { 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", data.value?.data[0].symbol);
|
|
const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = {
|
|
disabled_features: [
|
|
"create_volume_indicator_by_default",
|
|
],
|
|
};
|
|
const tradingViewInst = useTemplateRef<TradingViewInst>("tradingViewInst");
|
|
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() {
|
|
const modal = await modalController.create({
|
|
component: TradePairsModal,
|
|
breakpoints: [0, 0.95],
|
|
initialBreakpoint: 0.95,
|
|
handle: true,
|
|
});
|
|
await modal.present();
|
|
const { data: result } = await modal.onWillDismiss<string>();
|
|
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 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);
|
|
}
|
|
}
|
|
function gotoTokenized() {
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ion-page>
|
|
<ion-header>
|
|
<ion-toolbar class="ion-toolbar">
|
|
<div slot="start" class="flex items-center gap-1 px-4" @click="openTradePairs">
|
|
<div class="text-md font-bold">
|
|
{{ symbol }}
|
|
</div>
|
|
<ui-tag size="mini" type="tertiary">
|
|
现货
|
|
</ui-tag>
|
|
<ion-icon :icon="caretDownOutline" />
|
|
</div>
|
|
<ion-buttons slot="end">
|
|
<ion-button @click="gotoTokenized">
|
|
<MaterialSymbolsCandlestickChartOutline class="text-xl" />
|
|
</ion-button>
|
|
<ion-button>
|
|
<ion-icon :icon="ellipsisHorizontal" />
|
|
</ion-button>
|
|
</ion-buttons>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
<ion-content :fullscreen="true">
|
|
<TradingViewChart ref="tradingViewInst" class="mb-5" height="300px" :symbol="symbol" :options="tradingviewOptions" />
|
|
|
|
<div class="grid grid-cols-6 px-4">
|
|
<div class="col-span-4 space-y-2">
|
|
<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="请输入价格">
|
|
<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>
|
|
<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">
|
|
<OrderBook :symbol="form.symbol" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 px-4 pb-4">
|
|
<OrdersPanel :symbol="form.symbol" />
|
|
</div>
|
|
</ion-content>
|
|
</ion-page>
|
|
</template>
|
|
|
|
<style lang="css" scoped>
|
|
@reference "tailwindcss";
|
|
|
|
ion-input.count {
|
|
--background: var(--ion-color-faint);
|
|
--padding-start: 16px;
|
|
--padding-end: 16px;
|
|
--padding-top: 4px;
|
|
--padding-bottom: 4px;
|
|
--border-radius: 6px;
|
|
font-size: 12px;
|
|
min-height: 40px;
|
|
}
|
|
ion-range.range {
|
|
--bar-height: 3px;
|
|
--knob-size: 14px;
|
|
--pin-color: #fff;
|
|
--bar-background-active: #3e3e3e;
|
|
--bar-background: rgb(226, 226, 226);
|
|
padding: 0 8px;
|
|
}
|
|
ion-range.range::part(tick) {
|
|
z-index: 1;
|
|
border-radius: 100%;
|
|
background: #dcdcdc;
|
|
}
|
|
ion-range.range::part(tick),
|
|
ion-range.range::part(tick-active) {
|
|
height: 6px;
|
|
width: 6px;
|
|
top: 18px;
|
|
border-radius: 100%;
|
|
}
|
|
.confirm-modal {
|
|
--height: auto;
|
|
}
|
|
</style>
|