Observability
系统化梳理日志、指标与追踪的协同机制及成本控制策略。
下载 29
基于 React 的预构建交易界面组件,支持快速搭建去中心化交易平台。
openclaw skills install @tarnadas/orderly-ui-components命令、参数、文件名以原文为准
本技能涵盖使用 @orderly.network/react 提供的预构建 React 组件来构建交易界面。
@orderly.network/reactnpm install @orderly.network/react @orderly.network/hooks @orderly.network/types
# 或使用 yarn
yarn add @orderly.network/react @orderly.network/hooks @orderly.network/types在应用根部包裹所需的提供者:
import {
OrderlyAppProvider,
TradingPageProvider,
SymbolProvider,
WalletConnector
} from '@orderly.network/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<OrderlyAppProvider
brokerId="woofi_dex"
chainFilter={[42161, 421614]}
>
<SymbolProvider>
<TradingPageProvider>
<Layout>
<WalletConnector />
<TradingPage />
</Layout>
</TradingPageProvider>
</SymbolProvider>
</OrderlyAppProvider>
</QueryClientProvider>
);
}import { OrderEntry, OrderEntryProvider } from '@orderly.network/ui-order-entry';
import { useOrderEntry } from '@orderly.network/hooks';
function OrderEntryContainer() {
const { onSubmit } = useOrderEntry();
const handleSubmit = async (params: any) => {
try {
await onSubmit(params);
console.log('订单提交成功');
} catch (e) {
console.error('订单提交失败', e);
}
};
return (
<div className="bg-gray-900 rounded-lg p-4 border border-gray-800">
<OrderEntry
onSubmit={handleSubmit}
defaultTab="limit"
hideMarket={false}
/>
</div>
);
}
export function OrderEntryWidget() {
return (
<OrderEntryProvider symbol="PERP_BTC_USDC">
<OrderEntryContainer />
</OrderEntryProvider>
);
}interface OrderEntryProps {
onSubmit?: (params: OrderParams) => Promise<void>;
defaultTab?: 'limit' | 'market';
hideMarket?: boolean;
hideLimit?: boolean;
showLeverage?: boolean;
className?: string;
}import { Positions } from '@orderly.network/ui-positions';
export function PositionsWidget() {
const handleSymbolClick = (symbol: string) => {
console.log('跳转至:', symbol);
};
return (
<div className="w-full">
<Positions
filter={{ symbol: 'PERP_ETH_USDC' }} // 可选筛选条件
onSymbolClick={handleSymbolClick}
showPagination={true}
/>
</div>
);
}interface PositionsProps {
filter?: {
symbol?: string;
side?: 'BUY' | 'SELL';
};
onSymbolClick?: (symbol: string) => void;
showPagination?: boolean;
pageSize?: number;
className?: string;
}import { Orderbook, OrderbookProvider } from '@orderly.network/ui-orderbook';
export function OrderbookWidget({ symbol }: { symbol: string }) {
return (
<OrderbookProvider symbol={symbol}>
<Orderbook
level={10} // 显示的层级数量
showTotal={true} // 是否显示总计列
onItemClick={(price, side) => {
console.log('价格被点击:', price, side);
}}
/>
</OrderbookProvider>
);
}import { WalletConnect } from '@orderly.network/react';
export function Header() {
return (
<header className="flex justify-between items-center p-4">
<div className="logo">我的 DEX</div>
<WalletConnect />
</header>
);
}import { useAccount, useWalletConnector } from '@orderly.network/hooks';
export function CustomWalletButton() {
const { account, state } = useAccount();
const wallet = useWalletConnector();
if (state.status !== 'connected') {
return (
<button
onClick={() => wallet.connect()}
className="btn-primary"
>
连接钱包
</button>
);
}
return (
<div className="wallet-menu">
<span>{account.address?.slice(0, 6)}...{account.address?.slice(-4)}</span>
<button onClick={() => wallet.disconnect()}>
断开连接
</button>
</div>
);
}import { TradingView } from '@orderly.network/ui-chart';
export function ChartWidget({ symbol }: { symbol: string }) {
return (
<div className="h-[500px]">
<TradingView
symbol={symbol}
interval="1h"
theme="dark"
autosize={true}
/>
</div>
);
}import { LightweightChart } from '@orderly.network/ui-chart';
export function SimpleChart({ symbol }: { symbol: string }) {
return (
<div className="h-[400px]">
<LightweightChart
symbol={symbol}
interval="15m"
chartType="candlestick"
/>
</div>
);
}import { Table } from '@orderly.network/ui';
type TradeRow = {
id: string;
price: number;
size: number;
time: string;
side: 'BUY' | 'SELL';
};
export function TradesTable({ data }: { data: TradeRow[] }) {
return (
<Table<TradeRow>
dataSource={data}
columns={[
{
title: '价格',
dataIndex: 'price',
render: (value, record) => (
<span className={record.side === 'BUY' ? 'text-green-500' : 'text-red-500'}>
{value.toFixed(2)}
</span>
),
},
{
title: '数量',
dataIndex: 'size',
render: (value) => value.toFixed(4),
},
{
title: '时间',
dataIndex: 'time',
},
]}
rowKey="id"
pagination={{ pageSize: 20 }}
/>
);
}import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetFooter } from '@orderly.network/ui';
export function OrderDetailsSheet({ order }: { order: Order }) {
return (
<Sheet>
<SheetTrigger asChild>
<button className="btn-secondary">查看详情</button>
</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<h2>订单详情</h2>
</SheetHeader>
<div className="py-4">
<div className="grid grid-cols-2 gap-2">
<span>合约:</span>
<span>{order.symbol}</span>
<span>方向:</span>
<span>{order.side}</span>
<span>价格:</span>
<span>{order.price}</span>
<span>数量:</span>
<span>{order.order_qty}</span>
<span>状态:</span>
<span>{order.status}</span>
</div>
</div>
<SheetFooter>
<button className="btn-primary">平仓</button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}import { Modal, ModalTrigger, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@orderly.network/ui';
export function ConfirmOrderModal({ order, onConfirm }: { order: Order; onConfirm: () => void }) {
return (
<Modal>
<ModalTrigger asChild>
<button className="btn-primary">下单</button>
</ModalTrigger>
<ModalContent>
<ModalHeader>
<h2>确认订单</h2>
</ModalHeader>
<ModalBody>
<p>确定要提交此订单吗?</p>
<div className="order-summary">
<p>{order.side} {order.quantity} {order.symbol} @ {order.price}</p>
</div>
</ModalBody>
<ModalFooter>
<button className="btn-secondary">取消</button>
<button className="btn-primary" onClick={onConfirm}>确认</button>
</ModalFooter>
</ModalContent>
</Modal>
);
}import { NoData } from '@orderly.network/ui';
export function EmptyOrders() {
return (
<div className="flex flex-col items-center justify-center py-12">
<NoData width={200} height={200} className="text-gray-500" />
<p className="mt-4 text-gray-400">暂无未成交订单</p>
<button className="mt-2 btn-secondary">下单</button>
</div>
);
}import { Toast, ToastProvider, useToast } from '@orderly.network/ui';
function TradingPage() {
const { toast } = useToast();
const handleOrderSubmit = async () => {
try {
await submitOrder();
toast({
title: '订单已提交',
description: '您的订单已成功提交',
variant: 'success',
});
} catch (error) {
toast({
title: '订单提交失败',
description: error.message,
variant: 'error',
});
}
};
return <button onClick={handleOrderSubmit}>下单</button>;
}
// 在应用根部
function App() {
return (
<ToastProvider>
<TradingPage />
</ToastProvider>
);
}import {
OrderlyAppProvider,
TradingPageProvider,
SymbolProvider,
WalletConnect,
} from '@orderly.network/react';
import { OrderEntry, OrderEntryProvider } from '@orderly.network/ui-order-entry';
import { Positions } from '@orderly.network/ui-positions';
import { Orderbook, OrderbookProvider } from '@orderly.network/ui-orderbook';
import { TradingView } from '@orderly.network/ui-chart';
import { useOrderEntry } from '@orderly.network/hooks';
function TradingPage() {
const symbol = 'PERP_BTC_USDC';
return (
<div className="trading-layout">
{/* 头部 */}
<header className="flex justify-between items-center p-4 border-b">
<h1>我的去中心化交易所</h1>
<WalletConnect />
</header>
{/* 主体内容 */}
<div className="grid grid-cols-12 gap-4 p-4">
{/* 左侧:盘口 */}
<div className="col-span-2">
<OrderbookProvider symbol={symbol}>
<Orderbook level={15} />
</OrderbookProvider>
</div>
{/* 中心:图表 + 下单区 */}
<div className="col-span-7">
<div className="h-[500px] mb-4">
<TradingView symbol={symbol} />
</div>
<OrderEntryProvider symbol={symbol}>
<OrderEntry defaultTab="limit" />
</OrderEntryProvider>
</div>
{/* 右侧:持仓 */}
<div className="col-span-3">
<Positions showPagination={false} />
</div>
</div>
</div>
);
}
export function App() {
return (
<OrderlyAppProvider brokerId="woofi_dex">
<SymbolProvider>
<TradingPageProvider>
<TradingPage />
</TradingPageProvider>
</SymbolProvider>
</OrderlyAppProvider>
);
}Orderly 组件使用 Tailwind CSS 类名。可通过以下方式自定义样式:
/* 覆盖组件样式 */
.order-entry-root {
/* 自定义样式 */
}
.positions-table {
/* 自定义样式 */
}
/* 使用 Tailwind 的暗色模式 */
.dark .order-entry-root {
/* 暗色模式下的样式 */
}SymbolProvider 是否位于依赖符号的组件之上className 属性可用,建议使用该属性进行自定义样式已收录 1 个 Skill