feat: 更新依赖版本,优化路由组件,重构文件预览和订单面板,添加市场组件
This commit is contained in:
@@ -185,40 +185,28 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/trade-rwa/:id",
|
||||
props: true,
|
||||
component: () => import("@/views/trade-rwa/outlet.vue"),
|
||||
component: () => import("@/views/trade-rwa/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/trade-rwa/:id/edit",
|
||||
name: "trade-rwa-edit",
|
||||
props: true,
|
||||
component: () => import("@/views/trade-rwa/edit.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/views/trade-rwa/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "edit",
|
||||
name: "trade-rwa-edit",
|
||||
component: () => import("@/views/trade-rwa/edit.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/trade-tokenized/:id",
|
||||
props: true,
|
||||
component: () => import("@/views/trade-tokenized/outlet.vue"),
|
||||
component: () => import("@/views/trade-tokenized/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/trade-tokenized/:id/edit",
|
||||
name: "trade-tokenized-edit",
|
||||
props: true,
|
||||
component: () => import("@/views/trade-tokenized/edit.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/views/trade-tokenized/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "edit",
|
||||
name: "trade-tokenized-edit",
|
||||
component: () => import("@/views/trade-tokenized/edit.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/market/orders",
|
||||
|
||||
22
src/ui/file-preview/index.vue
Normal file
22
src/ui/file-preview/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang='ts' setup>
|
||||
import IcRoundDownload from "~icons/ic/round-download";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const props = defineProps<{
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const { data } = await safeClient(client.api.file_storage({ id: props.id }).get());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>{{ data?.fileName }}</div>
|
||||
|
||||
<div>
|
||||
<IcRoundDownload />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped></style>
|
||||
@@ -125,7 +125,7 @@ function handleSubmit(values: GenericObject) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Field v-slot="{ field }" name="proofDocuments">
|
||||
<Field v-slot="{ field }" name="proofDocumentIds">
|
||||
<ui-file-upload
|
||||
v-bind="field"
|
||||
:fetch-options="{
|
||||
@@ -136,10 +136,9 @@ function handleSubmit(values: GenericObject) {
|
||||
:max-files="5"
|
||||
:max-size="10"
|
||||
accept="application/pdf,image/*,.doc,.docx"
|
||||
@update:model-value="field.onChange($event.join(','))"
|
||||
/>
|
||||
</Field>
|
||||
<ErrorMessage name="proofDocuments" />
|
||||
<ErrorMessage name="proofDocumentIds" />
|
||||
</div>
|
||||
|
||||
<ion-button type="submit" expand="block" shape="round" color="success">
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<script lang='ts' setup>
|
||||
import type { RwaData } from "@/api/types";
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const props = defineProps<{
|
||||
data: RwaData | null;
|
||||
}>();
|
||||
|
||||
const { data: file } = safeClient(client.api.file_storage({ id: props.data?.product.proofDocumentIds || "" }).get());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -13,9 +16,7 @@ const props = defineProps<{
|
||||
<div class="font-semibold">
|
||||
相关文档
|
||||
</div>
|
||||
<div class="text-xs mt-2">
|
||||
{{ data?.product.proofDocuments }}
|
||||
</div>
|
||||
<a class="link" :href="file?.publicUrl || ''" target="_blank" rel="noopener noreferrer">{{ file?.fileName }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang='ts' setup>
|
||||
import type { RwaData } from "@/api/types";
|
||||
import { toastController } from "@ionic/vue";
|
||||
import CryptocurrencyColorNuls from "~icons/cryptocurrency-color/nuls";
|
||||
import IcSharpEditNote from "~icons/ic/sharp-edit-note";
|
||||
@@ -8,9 +7,11 @@ import RwaAbout from "./components/about.vue";
|
||||
import RwaBase from "./components/base.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
data: RwaData;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const { data } = safeClient(client.api.rwa.subscription.available_editions({ editionId: props.id }).get());
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -18,7 +19,7 @@ const model = useTemplateRef<ModalInstance>("model");
|
||||
|
||||
async function handleSubscribe(val: number) {
|
||||
await safeClient(client.api.rwa.subscription.apply.post({
|
||||
editionId: props.data.id,
|
||||
editionId: data.value!.id,
|
||||
quantity: String(val),
|
||||
}));
|
||||
const toast = await toastController.create({
|
||||
@@ -72,7 +73,7 @@ function gotoEdit() {
|
||||
<ui-tab-pane name="overview" :title="t('market.tradeRwa.tabs.overview')">
|
||||
<RwaBase :data="data" />
|
||||
</ui-tab-pane>
|
||||
<ui-tab-pane name="about" title="相关文档">
|
||||
<ui-tab-pane name="about" title="相关文档" lazy>
|
||||
<RwaAbout :data="data" />
|
||||
</ui-tab-pane>
|
||||
</ui-tabs>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const props = defineProps<{
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const { data } = safeClient(client.api.rwa.subscription.available_editions({ editionId: props.id }).get());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar class="ion-toolbar">
|
||||
<ui-back-button slot="start" />
|
||||
<ion-title>
|
||||
{{ data?.product.code }}
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true" class="ion-padding">
|
||||
<RouterView :data="data" />
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -14,7 +14,7 @@ const props = defineProps<{
|
||||
相关文档
|
||||
</div>
|
||||
<div class="text-xs mt-2">
|
||||
{{ data?.proofDocuments }}
|
||||
{{ data?.proofDocumentIds }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ const props = defineProps<{
|
||||
相关文档
|
||||
</div>
|
||||
<div class="text-xs mt-2">
|
||||
{{ data?.product?.proofDocuments }}
|
||||
{{ data?.product?.proofDocumentIds }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,10 +10,9 @@ const { t } = useI18n();
|
||||
|
||||
<template>
|
||||
<div class="mt-2">
|
||||
<!-- Rwa about -->
|
||||
<div>
|
||||
<div class="font-semibold">
|
||||
{{ t('market.tradeRwa.about') }}
|
||||
关于
|
||||
</div>
|
||||
<div class="text-xs mt-2">
|
||||
{{ data?.product?.description || t('market.tradeRwa.noDescription') }}
|
||||
@@ -31,7 +30,7 @@ const { t } = useI18n();
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<div class="label">
|
||||
{{ t('market.tradeRwa.fields.valuation') }}
|
||||
市值
|
||||
</div>
|
||||
<div>${{ formatAmountWithUnit(data?.product?.estimatedValue) }}</div>
|
||||
</ion-col>
|
||||
@@ -64,4 +63,10 @@ const { t } = useI18n();
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped></style>
|
||||
<style lang='css' scoped>
|
||||
@reference "tailwindcss";
|
||||
|
||||
.label {
|
||||
@apply text-(--ion-text-color-step-300) mb-1 text-sm;
|
||||
}
|
||||
</style>
|
||||
|
||||
35
src/views/trade-tokenized/components/market.vue
Normal file
35
src/views/trade-tokenized/components/market.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang='ts' setup>
|
||||
import type { ChartingLibraryWidgetOptions } from "#/charting_library";
|
||||
import type { TradableData } from "@/api/types";
|
||||
import type { TradingViewInst } from "@/tradingview";
|
||||
import { client, safeClient } from "@/api";
|
||||
import { TradingViewChart } from "@/tradingview";
|
||||
import OrderBook from "./order-book.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
data: TradableData | null;
|
||||
}>();
|
||||
|
||||
const tradingviewOptions: Partial<ChartingLibraryWidgetOptions> = {
|
||||
disabled_features: [
|
||||
"create_volume_indicator_by_default",
|
||||
],
|
||||
};
|
||||
|
||||
const tradingViewInst = useTemplateRef<TradingViewInst>("tradingViewInst");
|
||||
const symbol = computed(() => props.data?.asset?.tradingPairsAsBase[0].symbol || "");
|
||||
|
||||
const { data } = await safeClient(client.api.trading_pairs.orderbook.get({ query: { symbol: symbol.value, depth: 30 } }));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TradingViewChart v-if="symbol" ref="tradingViewInst" class="mb-5" height="300px" :symbol="symbol" :options="tradingviewOptions" />
|
||||
|
||||
<ui-tabs size="small" class="my-3">
|
||||
<ui-tab-pane name="order_book" title="订单表">
|
||||
<OrderBook :symbol="symbol" />
|
||||
</ui-tab-pane>
|
||||
</ui-tabs>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped></style>
|
||||
125
src/views/trade-tokenized/components/order-book.vue
Normal file
125
src/views/trade-tokenized/components/order-book.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<script lang='ts' setup>
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const props = defineProps<{ symbol: string }>();
|
||||
|
||||
const { data } = await safeClient(client.api.trading_pairs.orderbook.get({ query: { symbol: props.symbol, depth: 30 } }));
|
||||
|
||||
// 计算买单和卖单的最大数量,用于计算占比
|
||||
const maxBidAmount = computed(() => {
|
||||
if (!data.value?.bids)
|
||||
return 0;
|
||||
return Math.max(...data.value.bids.map(item => Number(item[1])));
|
||||
});
|
||||
|
||||
const maxAskAmount = computed(() => {
|
||||
if (!data.value?.asks)
|
||||
return 0;
|
||||
return Math.max(...data.value.asks.map(item => Number(item[1])));
|
||||
});
|
||||
|
||||
// 计算每个订单的占比百分比
|
||||
function getBidPercentage(amount: string) {
|
||||
const num = Number(amount);
|
||||
return maxBidAmount.value > 0 ? (num / maxBidAmount.value) * 100 : 0;
|
||||
}
|
||||
|
||||
function getAskPercentage(amount: string) {
|
||||
const num = Number(amount);
|
||||
return maxAskAmount.value > 0 ? (num / maxAskAmount.value) * 100 : 0;
|
||||
}
|
||||
|
||||
// 格式化价格
|
||||
function formatPrice(price: string) {
|
||||
return Number(price).toFixed(2);
|
||||
}
|
||||
|
||||
// 格式化数量
|
||||
function formatAmount(amount: string) {
|
||||
return Number(amount).toFixed(4);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full w-full overflow-hidden bg-(--ion-background-color)">
|
||||
<div class="grid h-full grid-cols-2 gap-0.5">
|
||||
<!-- 买单区域 (Bids) -->
|
||||
<div class="flex flex-col">
|
||||
<!-- 标题 -->
|
||||
<div class="grid grid-cols-2 gap-2 border-b border-(--ion-border-color) px-3 py-2 text-xs text-(--ion-color-medium)">
|
||||
<div class="text-left">
|
||||
价格
|
||||
</div>
|
||||
<div class="text-right">
|
||||
数量
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 买单列表 -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div
|
||||
v-for="(bid, index) in data?.bids"
|
||||
:key="index"
|
||||
class="relative grid grid-cols-2 gap-2 px-3 py-1 text-xs"
|
||||
>
|
||||
<!-- 背景色显示占比 -->
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 bg-(--ion-color-success)/10"
|
||||
:style="{ width: `${getBidPercentage(bid[1])}%` }"
|
||||
/>
|
||||
|
||||
<!-- 价格 -->
|
||||
<div class="relative z-1 text-left text-(--ion-color-success)">
|
||||
{{ formatPrice(bid[0]) }}
|
||||
</div>
|
||||
|
||||
<!-- 数量 -->
|
||||
<div class="relative z-1 text-right text-(--ion-color-step-600)">
|
||||
{{ formatAmount(bid[1]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 卖单区域 (Asks) -->
|
||||
<div class="flex flex-col">
|
||||
<!-- 标题 -->
|
||||
<div class="grid grid-cols-2 gap-2 border-b border-(--ion-border-color) px-3 py-2 text-xs text-(--ion-color-medium)">
|
||||
<div class="text-left">
|
||||
价格
|
||||
</div>
|
||||
<div class="text-right">
|
||||
数量
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 卖单列表 -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div
|
||||
v-for="(ask, index) in data?.asks"
|
||||
:key="index"
|
||||
class="relative grid grid-cols-2 gap-2 px-3 py-1 text-xs"
|
||||
>
|
||||
<!-- 背景色显示占比 -->
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 bg-(--ion-color-danger)/10"
|
||||
:style="{ width: `${getAskPercentage(ask[1])}%` }"
|
||||
/>
|
||||
|
||||
<!-- 价格 -->
|
||||
<div class="relative z-1 text-left text-(--ion-color-danger)">
|
||||
{{ formatPrice(ask[0]) }}
|
||||
</div>
|
||||
|
||||
<!-- 数量 -->
|
||||
<div class="relative z-1 text-right text-(--ion-color-step-600)">
|
||||
{{ formatAmount(ask[1]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped></style>
|
||||
@@ -1,19 +1,22 @@
|
||||
<script lang='ts' setup>
|
||||
import type { TradableData } from "@/api/types";
|
||||
import CryptocurrencyColorNuls from "~icons/cryptocurrency-color/nuls";
|
||||
import { client, safeClient } from "@/api";
|
||||
import { TradeTypeEnum } from "@/api/enum";
|
||||
import About from "./components/about.vue";
|
||||
import Base from "./components/base.vue";
|
||||
import Market from "./components/market.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
data: TradableData;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const { data } = safeClient(client.api.rwa.tokenization.tradable_products({ id: props.id }).get());
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
function gotoTrade(mode: TradeTypeEnum) {
|
||||
router.push(`/layout/trade?mode=${mode}&symbol=${props.data.asset?.tradingPairsAsBase[0].symbol}`);
|
||||
router.push(`/layout/trade?mode=${mode}&symbol=${data.value?.asset?.tradingPairsAsBase[0].symbol}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -50,6 +53,9 @@ function gotoTrade(mode: TradeTypeEnum) {
|
||||
</div>
|
||||
|
||||
<ui-tabs size="small" class="my-3">
|
||||
<ui-tab-pane name="market" title="行情">
|
||||
<Market :data="data" />
|
||||
</ui-tab-pane>
|
||||
<ui-tab-pane name="overview" :title="t('market.tradeRwa.tabs.overview')">
|
||||
<Base :data="data" />
|
||||
</ui-tab-pane>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<script lang='ts' setup>
|
||||
import { client, safeClient } from "@/api";
|
||||
|
||||
const props = defineProps<{
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const { data } = safeClient(client.api.rwa.tokenization.tradable_products({ id: props.id }).get());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar class="ion-toolbar">
|
||||
<ui-back-button slot="start" />
|
||||
<ion-title>
|
||||
{{ data?.product?.code }}
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true" class="ion-padding">
|
||||
<RouterView :data="data" />
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -7,10 +7,12 @@ import { orderStatusMap } from "../config";
|
||||
type Item = Treaty.Data<typeof client.api.spot_order.list.get>["orders"][number];
|
||||
type TabType = "current" | "history";
|
||||
|
||||
const props = defineProps<{ symbol: string }>();
|
||||
|
||||
const activeTab = ref<TabType>("current");
|
||||
const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{ item: Item }>();
|
||||
|
||||
const { data } = await safeClient(client.api.spot_order.list.get());
|
||||
const { data } = await safeClient(client.api.spot_order.list.get({ query: { symbol: props.symbol } }));
|
||||
const currentOrders = computed(() =>
|
||||
data.value?.orders.filter(order => order.status !== "filled" && order.status !== "cancelled") || [],
|
||||
);
|
||||
|
||||
@@ -146,7 +146,7 @@ async function handleSubmit() {
|
||||
</div>
|
||||
|
||||
<div class="mt-6 px-4 pb-4">
|
||||
<OrdersPanel />
|
||||
<OrdersPanel :symbol="form.symbol" />
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
|
||||
@@ -32,7 +32,7 @@ async function handleScan() {
|
||||
});
|
||||
|
||||
if (result) {
|
||||
console.log("扫描结果:", result.text);
|
||||
console.log("扫描结果:", result);
|
||||
// TODO: 根据扫描结果进行相应处理
|
||||
// 例如:跳转到对应页面、显示信息等
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user