<template>
|
<div class="contract-page">
|
<!-- 主标签 -->
|
<div class="main-tabs">
|
<span class="tab-item">闪兑</span>
|
<span class="tab-item active">合约</span>
|
<span class="tab-item">
|
股票
|
<span class="red-dot"></span>
|
</span>
|
<span class="tab-icon">📄</span>
|
<span class="tab-icon">📈</span>
|
<span class="tab-icon">⋯</span>
|
</div>
|
|
<!-- 资产信息 -->
|
<div class="asset-info">
|
<div class="asset-left">
|
<span class="asset-pair">BTCUSDT</span>
|
<span class="asset-type">永续</span>
|
<span class="dropdown">▼</span>
|
</div>
|
<div class="asset-right">
|
<span class="chart-icon">📈</span>
|
<span class="more-icon">⋯</span>
|
</div>
|
</div>
|
|
<div class="asset-price">
|
<span class="price">$91,174.8</span>
|
<span class="change down">-0.12%</span>
|
</div>
|
|
<!-- 时间周期 -->
|
<div class="timeframes">
|
<span class="timeframe" :class="{ active: activeTimeframe === '1m' }" @click="activeTimeframe = '1m'">1m</span>
|
<span class="timeframe active" :class="{ active: activeTimeframe === '15m' }" @click="activeTimeframe = '15m'">15m</span>
|
<span class="timeframe" :class="{ active: activeTimeframe === '1h' }" @click="activeTimeframe = '1h'">1h</span>
|
<span class="timeframe" :class="{ active: activeTimeframe === '4h' }" @click="activeTimeframe = '4h'">4h</span>
|
<span class="chart-settings">⚙️</span>
|
</div>
|
|
<!-- K线图表 -->
|
<div class="chart-container">
|
<div ref="chartRef" class="chart"></div>
|
</div>
|
|
<!-- 资金费率 -->
|
<div class="funding-rate">
|
<span>资金费率</span>
|
<span>0.0027% / 06:51:10</span>
|
</div>
|
|
<!-- 做多做空按钮 -->
|
<div class="position-buttons">
|
<button class="position-btn long active">做多</button>
|
<button class="position-btn short">做空</button>
|
</div>
|
|
<!-- 主要内容区域 -->
|
<div class="main-content">
|
<!-- 左侧订单簿 -->
|
<div class="orderbook">
|
<div class="orderbook-header">
|
<span>价格 (USDT)</span>
|
<span>数量 (BTC)</span>
|
</div>
|
<div class="orderbook-sell">
|
<div class="order-item">
|
<span class="order-price sell">91,169.2</span>
|
<span class="order-amount">0.0068</span>
|
</div>
|
<div class="order-item">
|
<span class="order-price sell">91,168.5</span>
|
<span class="order-amount">0.0705</span>
|
</div>
|
<div class="order-item">
|
<span class="order-price sell">91,168.4</span>
|
<span class="order-amount">0.0374</span>
|
</div>
|
<div class="order-item">
|
<span class="order-price sell">91,168.0</span>
|
<span class="order-amount">0.0001</span>
|
</div>
|
<div class="order-item">
|
<span class="order-price sell">91,166.2</span>
|
<span class="order-amount">0.0001</span>
|
</div>
|
<div class="order-item highlight">
|
<span class="order-price sell">91,166.1</span>
|
<span class="order-amount">15.1950</span>
|
</div>
|
</div>
|
<div class="orderbook-price">
|
<div class="current-price">91,166.1</div>
|
<div class="last-price">91,174.8</div>
|
</div>
|
<div class="orderbook-buy">
|
<div class="order-item">
|
<span class="order-price buy">91,200.1</span>
|
<span class="order-amount">12.8730</span>
|
</div>
|
<div class="order-item">
|
<span class="order-price buy">91,200</span>
|
<span class="order-amount">0.0001</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 右侧下单面板 -->
|
<div class="order-panel">
|
<div class="order-type">
|
<button class="type-btn active">市价单</button>
|
<span class="dropdown">▼</span>
|
</div>
|
<div class="leverage">
|
<button class="leverage-btn">全仓 | 20x</button>
|
<span class="dropdown">▼</span>
|
</div>
|
<div class="quantity-input">
|
<div class="input-label">数量</div>
|
<div class="input-wrapper">
|
<input type="text" placeholder="0" />
|
<span class="token-unit">BTC</span>
|
<span class="dropdown">▼</span>
|
</div>
|
<div class="input-fiat">≈ 0 USDT</div>
|
</div>
|
<div class="slider">
|
<div class="slider-value">
|
<span>0.1</span>
|
<span class="dropdown">▼</span>
|
<span class="slider-icon">☰</span>
|
</div>
|
<div class="slider-track">
|
<div class="slider-fill" style="width: 10%"></div>
|
</div>
|
</div>
|
<div class="balance-info">
|
<div class="balance-item">
|
<span>可用</span>
|
<span>0.00 USDT +</span>
|
</div>
|
<div class="balance-item">
|
<span>可开</span>
|
<span>0.0 BTC</span>
|
</div>
|
</div>
|
<button class="open-account-btn">开通账户</button>
|
</div>
|
</div>
|
|
<!-- 底部标签 -->
|
<div class="bottom-tabs">
|
<span class="bottom-tab active">持仓</span>
|
<span class="bottom-tab">委托</span>
|
</div>
|
|
<!-- 账户提示 -->
|
<div class="account-alert">
|
<div class="alert-message">该账户没有余额,请充值</div>
|
<button class="recharge-btn">去充币</button>
|
</div>
|
|
<!-- 底部导航 -->
|
<BottomNav />
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { createChart, IChartApi, CandlestickData, Time } from 'lightweight-charts'
|
import BottomNav from '@/components/BottomNav.vue'
|
|
const chartRef = ref<HTMLElement>()
|
const activeTimeframe = ref('15m')
|
let chart: IChartApi | null = null
|
let candlestickSeries: any = null
|
|
// 生成符合截图的K线数据
|
function generateKlineData(): CandlestickData[] {
|
const data: CandlestickData[] = []
|
|
// 根据截图的时间点生成数据
|
// 时间轴:18:30, 01/08 20:30, 01/08 22:45, 01/09 01:00
|
// 价格轴:91216.6, 91166.0, 90596.6, 89885.8, 89265.8
|
|
const baseTime = new Date('2024-01-08T18:30:00').getTime() / 1000
|
const prices = [
|
{ time: baseTime, open: 91166, high: 91216.6, low: 91100, close: 91150 },
|
{ time: baseTime + 900, open: 91150, high: 91180, low: 91100, close: 91120 },
|
{ time: baseTime + 1800, open: 91120, high: 91150, low: 91050, close: 91080 },
|
{ time: baseTime + 2700, open: 91080, high: 91100, low: 91000, close: 91020 },
|
{ time: baseTime + 3600, open: 91020, high: 91050, low: 90900, close: 90950 },
|
{ time: baseTime + 4500, open: 90950, high: 91000, low: 90800, close: 90850 },
|
{ time: baseTime + 5400, open: 90850, high: 90900, low: 90700, close: 90750 },
|
{ time: baseTime + 6300, open: 90750, high: 90800, low: 90600, close: 90650 },
|
{ time: baseTime + 7200, open: 90650, high: 90700, low: 90500, close: 90596.6 },
|
{ time: baseTime + 8100, open: 90596.6, high: 90650, low: 90400, close: 90450 },
|
{ time: baseTime + 9000, open: 90450, high: 90500, low: 90300, close: 90350 },
|
{ time: baseTime + 9900, open: 90350, high: 90400, low: 90200, close: 90250 },
|
{ time: baseTime + 10800, open: 90250, high: 90300, low: 90100, close: 90150 },
|
{ time: baseTime + 11700, open: 90150, high: 90200, low: 90000, close: 90050 },
|
{ time: baseTime + 12600, open: 90050, high: 90100, low: 89900, close: 89950 },
|
{ time: baseTime + 13500, open: 89950, high: 90000, low: 89800, close: 89885.8 },
|
{ time: baseTime + 14400, open: 89885.8, high: 89950, low: 89750, close: 89800 },
|
{ time: baseTime + 15300, open: 89800, high: 89850, low: 89650, close: 89700 },
|
{ time: baseTime + 16200, open: 89700, high: 89750, low: 89550, close: 89600 },
|
{ time: baseTime + 17100, open: 89600, high: 89650, low: 89450, close: 89500 },
|
{ time: baseTime + 18000, open: 89500, high: 89550, low: 89350, close: 89400 },
|
{ time: baseTime + 18900, open: 89400, high: 89450, low: 89250, close: 89265.8 },
|
{ time: baseTime + 19800, open: 89265.8, high: 89350, low: 89200, close: 89300 },
|
{ time: baseTime + 20700, open: 89300, high: 89400, low: 89250, close: 89350 },
|
{ time: baseTime + 21600, open: 89350, high: 89450, low: 89300, close: 89400 },
|
{ time: baseTime + 22500, open: 89400, high: 89500, low: 89350, close: 89450 },
|
{ time: baseTime + 23400, open: 89450, high: 89550, low: 89400, close: 89500 },
|
{ time: baseTime + 24300, open: 89500, high: 89600, low: 89450, close: 89550 },
|
{ time: baseTime + 25200, open: 89550, high: 89650, low: 89500, close: 89600 },
|
{ time: baseTime + 26100, open: 89600, high: 89700, low: 89550, close: 89650 },
|
{ time: baseTime + 27000, open: 89650, high: 89750, low: 89600, close: 89700 },
|
{ time: baseTime + 27900, open: 89700, high: 89800, low: 89650, close: 89750 },
|
{ time: baseTime + 28800, open: 89750, high: 89850, low: 89700, close: 89800 },
|
{ time: baseTime + 29700, open: 89800, high: 89900, low: 89750, close: 89850 },
|
{ time: baseTime + 30600, open: 89850, high: 89950, low: 89800, close: 89900 },
|
{ time: baseTime + 31500, open: 89900, high: 90000, low: 89850, close: 89950 },
|
{ time: baseTime + 32400, open: 89950, high: 90050, low: 89900, close: 90000 },
|
{ time: baseTime + 33300, open: 90000, high: 90100, low: 89950, close: 90050 },
|
{ time: baseTime + 34200, open: 90050, high: 90150, low: 90000, close: 90100 },
|
{ time: baseTime + 35100, open: 90100, high: 90200, low: 90050, close: 90150 },
|
{ time: baseTime + 36000, open: 90150, high: 90250, low: 90100, close: 90200 },
|
{ time: baseTime + 36900, open: 90200, high: 90350, low: 90150, close: 90300 },
|
{ time: baseTime + 37800, open: 90300, high: 90450, low: 90250, close: 90400 },
|
{ time: baseTime + 38700, open: 90400, high: 90550, low: 90350, close: 90500 },
|
{ time: baseTime + 39600, open: 90500, high: 90650, low: 90450, close: 90600 },
|
{ time: baseTime + 40500, open: 90600, high: 90750, low: 90550, close: 90700 },
|
{ time: baseTime + 41400, open: 90700, high: 90850, low: 90650, close: 90800 },
|
{ time: baseTime + 42300, open: 90800, high: 90950, low: 90750, close: 90900 },
|
{ time: baseTime + 43200, open: 90900, high: 91050, low: 90850, close: 91000 },
|
{ time: baseTime + 44100, open: 91000, high: 91150, low: 90950, close: 91100 },
|
{ time: baseTime + 45000, open: 91100, high: 91166, low: 91050, close: 91166 }
|
]
|
|
return prices.map(p => ({
|
time: p.time as Time,
|
open: p.open,
|
high: p.high,
|
low: p.low,
|
close: p.close
|
}))
|
}
|
|
function initChart() {
|
if (!chartRef.value) return
|
|
chart = createChart(chartRef.value, {
|
width: chartRef.value.clientWidth,
|
height: chartRef.value.clientHeight,
|
layout: {
|
background: { color: 'transparent' },
|
textColor: '#666666',
|
fontSize: 10
|
},
|
grid: {
|
vertLines: {
|
visible: true,
|
color: '#f0f0f0',
|
style: 2
|
},
|
horzLines: {
|
visible: true,
|
color: '#f0f0f0',
|
style: 2
|
}
|
},
|
timeScale: {
|
timeVisible: true,
|
secondsVisible: false,
|
borderColor: '#e8e8e8'
|
},
|
rightPriceScale: {
|
borderColor: '#e8e8e8',
|
scaleMargins: {
|
top: 0.1,
|
bottom: 0.1
|
}
|
}
|
})
|
|
candlestickSeries = chart.addCandlestickSeries({
|
upColor: '#14f195',
|
downColor: '#ff4757',
|
borderVisible: false,
|
wickUpColor: '#14f195',
|
wickDownColor: '#ff4757'
|
})
|
|
const data = generateKlineData()
|
candlestickSeries.setData(data)
|
chart.timeScale().fitContent()
|
}
|
|
watch(activeTimeframe, () => {
|
if (chart && candlestickSeries) {
|
const data = generateKlineData()
|
candlestickSeries.setData(data)
|
chart.timeScale().fitContent()
|
}
|
})
|
|
onMounted(() => {
|
initChart()
|
window.addEventListener('resize', () => {
|
if (chart && chartRef.value) {
|
chart.applyOptions({
|
width: chartRef.value.clientWidth,
|
height: chartRef.value.clientHeight
|
})
|
}
|
})
|
})
|
|
onUnmounted(() => {
|
if (chart) {
|
chart.remove()
|
}
|
})
|
</script>
|
|
<style lang="scss" scoped>
|
@import '@/styles/variables.scss';
|
|
.contract-page {
|
background: $bg-primary;
|
padding-bottom: 60px;
|
min-height: 100vh;
|
}
|
|
.main-tabs {
|
display: flex;
|
align-items: center;
|
padding: $spacing-md $spacing-lg;
|
gap: $spacing-xxl;
|
margin-bottom: $spacing-lg;
|
background: $bg-tertiary;
|
|
.tab-item {
|
font-size: $font-md;
|
color: $text-secondary;
|
position: relative;
|
|
&.active {
|
color: $text-primary;
|
font-weight: 500;
|
}
|
|
.red-dot {
|
position: absolute;
|
top: -2px;
|
right: -8px;
|
width: 6px;
|
height: 6px;
|
background: $down-red;
|
border-radius: 50%;
|
}
|
}
|
|
.tab-icon {
|
margin-left: auto;
|
font-size: $font-md;
|
color: $text-secondary;
|
cursor: pointer;
|
|
&:not(:last-child) {
|
margin-right: 12px;
|
}
|
}
|
}
|
|
.asset-info {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 12px 16px;
|
|
.asset-left {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
|
.asset-pair {
|
font-size: $font-lg;
|
font-weight: 500;
|
color: $text-primary;
|
}
|
|
.asset-type {
|
font-size: $font-xs;
|
color: $text-secondary;
|
background: $bg-secondary;
|
padding: 2px 6px;
|
border-radius: $radius-sm;
|
}
|
|
.dropdown {
|
font-size: $font-xs;
|
color: $text-tertiary;
|
}
|
}
|
|
.asset-right {
|
display: flex;
|
gap: 12px;
|
color: $text-secondary;
|
}
|
}
|
|
.asset-price {
|
padding: 0 16px 12px;
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
|
.price {
|
font-size: $font-xl;
|
font-weight: 500;
|
color: $text-primary;
|
}
|
|
.change {
|
font-size: $font-sm;
|
|
&.down {
|
color: $down-red;
|
}
|
}
|
}
|
|
.timeframes {
|
display: flex;
|
align-items: center;
|
padding: $spacing-sm $spacing-lg;
|
gap: $spacing-lg;
|
margin-bottom: $spacing-sm;
|
background: $bg-tertiary;
|
|
.timeframe {
|
font-size: $font-sm;
|
color: $text-secondary;
|
padding: 4px 8px;
|
border-radius: $radius-sm;
|
cursor: pointer;
|
|
&.active {
|
background: $primary-light;
|
color: $primary;
|
}
|
}
|
|
.chart-settings {
|
margin-left: auto;
|
font-size: $font-md;
|
color: $text-secondary;
|
cursor: pointer;
|
}
|
}
|
|
.chart-container {
|
padding: 16px;
|
|
.chart {
|
width: 100%;
|
height: 300px;
|
background: $bg-primary;
|
}
|
}
|
|
.funding-rate {
|
display: flex;
|
justify-content: space-between;
|
padding: 8px 16px;
|
font-size: $font-sm;
|
color: $text-secondary;
|
margin-bottom: $spacing-sm;
|
padding: $spacing-sm $spacing-lg;
|
background: $bg-tertiary;
|
border-radius: $radius-md;
|
}
|
|
.position-buttons {
|
display: flex;
|
padding: 12px 16px;
|
gap: 12px;
|
|
.position-btn {
|
flex: 1;
|
padding: 12px;
|
border: none;
|
border-radius: $radius-md;
|
font-size: $font-md;
|
font-weight: 500;
|
cursor: pointer;
|
transition: all 0.2s;
|
|
&.long {
|
background: #14f195;
|
color: white;
|
|
&.active {
|
background: #14f195;
|
}
|
}
|
|
&.short {
|
background: $bg-primary;
|
color: $text-secondary;
|
background: $bg-card;
|
border-radius: $radius-md;
|
|
&.active {
|
background: $down-red;
|
color: white;
|
border-color: $down-red;
|
}
|
}
|
}
|
}
|
|
.main-content {
|
display: flex;
|
gap: 1px;
|
background: $border-light;
|
|
.orderbook {
|
flex: 1;
|
background: $bg-primary;
|
padding: 12px;
|
|
.orderbook-header {
|
display: flex;
|
justify-content: space-between;
|
font-size: $font-xs;
|
color: $text-tertiary;
|
margin-bottom: 8px;
|
}
|
|
.orderbook-sell {
|
.order-item {
|
display: flex;
|
justify-content: space-between;
|
padding: 2px 0;
|
font-size: $font-xs;
|
|
&.highlight {
|
background: rgba(255, 71, 87, 0.05);
|
}
|
|
.order-price {
|
&.sell {
|
color: $down-red;
|
}
|
}
|
}
|
}
|
|
.orderbook-price {
|
text-align: center;
|
padding: 8px 0;
|
background: $bg-tertiary;
|
border-radius: $radius-md;
|
margin: $spacing-sm 0;
|
padding: $spacing-sm 0;
|
|
.current-price {
|
font-size: $font-lg;
|
font-weight: 500;
|
color: $down-red;
|
margin-bottom: 2px;
|
}
|
|
.last-price {
|
font-size: $font-xs;
|
color: $text-secondary;
|
}
|
}
|
|
.orderbook-buy {
|
.order-item {
|
display: flex;
|
justify-content: space-between;
|
padding: 2px 0;
|
font-size: $font-xs;
|
|
.order-price {
|
&.buy {
|
color: #14f195;
|
}
|
}
|
}
|
}
|
}
|
|
.order-panel {
|
flex: 1;
|
background: $bg-primary;
|
padding: 12px;
|
|
.order-type, .leverage {
|
margin-bottom: 12px;
|
position: relative;
|
|
.type-btn, .leverage-btn {
|
width: 100%;
|
padding: 10px;
|
background: $bg-secondary;
|
border: none;
|
border-radius: $radius-md;
|
text-align: left;
|
font-size: $font-md;
|
cursor: pointer;
|
|
&.active {
|
background: $primary-light;
|
color: $primary;
|
}
|
}
|
|
.dropdown {
|
position: absolute;
|
right: 10px;
|
top: 50%;
|
transform: translateY(-50%);
|
font-size: $font-xs;
|
color: $text-tertiary;
|
pointer-events: none;
|
}
|
}
|
|
.quantity-input {
|
margin-bottom: 12px;
|
|
.input-label {
|
font-size: $font-sm;
|
color: $text-secondary;
|
margin-bottom: 8px;
|
}
|
|
.input-wrapper {
|
display: flex;
|
align-items: center;
|
background: $bg-secondary;
|
border-radius: $radius-md;
|
padding: 10px;
|
margin-bottom: 4px;
|
position: relative;
|
|
input {
|
flex: 1;
|
border: none;
|
background: transparent;
|
font-size: $font-md;
|
color: $text-primary;
|
|
&::placeholder {
|
color: $text-tertiary;
|
}
|
}
|
|
.token-unit {
|
margin: 0 8px;
|
font-size: $font-sm;
|
color: $text-secondary;
|
}
|
|
.dropdown {
|
font-size: $font-xs;
|
color: $text-tertiary;
|
}
|
}
|
|
.input-fiat {
|
font-size: $font-xs;
|
color: $text-secondary;
|
padding-left: 4px;
|
}
|
}
|
|
.slider {
|
margin-bottom: 12px;
|
|
.slider-value {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
margin-bottom: 8px;
|
font-size: $font-sm;
|
color: $text-primary;
|
|
.dropdown {
|
font-size: $font-xs;
|
color: $text-tertiary;
|
}
|
|
.slider-icon {
|
margin-left: auto;
|
font-size: $font-md;
|
color: $text-secondary;
|
}
|
}
|
|
.slider-track {
|
height: 4px;
|
background: $bg-secondary;
|
border-radius: 2px;
|
position: relative;
|
|
.slider-fill {
|
height: 100%;
|
background: $primary;
|
border-radius: 2px;
|
}
|
}
|
}
|
|
.balance-info {
|
margin-bottom: 12px;
|
|
.balance-item {
|
display: flex;
|
justify-content: space-between;
|
font-size: $font-xs;
|
color: $text-secondary;
|
margin-bottom: 4px;
|
}
|
}
|
|
.open-account-btn {
|
width: 100%;
|
background: #14f195;
|
color: white;
|
border: none;
|
border-radius: $radius-md;
|
padding: 12px;
|
font-size: $font-md;
|
font-weight: 500;
|
cursor: pointer;
|
transition: opacity 0.2s;
|
|
&:active {
|
opacity: 0.8;
|
}
|
}
|
}
|
}
|
|
.bottom-tabs {
|
display: flex;
|
padding: 12px 16px;
|
gap: 24px;
|
background: $bg-tertiary;
|
margin-top: $spacing-lg;
|
|
.bottom-tab {
|
font-size: $font-md;
|
color: $text-secondary;
|
position: relative;
|
cursor: pointer;
|
|
&.active {
|
color: $text-primary;
|
font-weight: 500;
|
|
&::after {
|
content: '';
|
position: absolute;
|
bottom: -12px;
|
left: 0;
|
right: 0;
|
height: 2px;
|
background: $primary-blue;
|
}
|
}
|
}
|
}
|
|
.account-alert {
|
padding: 24px 16px;
|
text-align: center;
|
|
.alert-message {
|
font-size: $font-md;
|
color: $text-secondary;
|
margin-bottom: 16px;
|
}
|
|
.recharge-btn {
|
background: #14f195;
|
color: white;
|
border: none;
|
border-radius: $radius-lg;
|
padding: 12px 32px;
|
font-size: $font-md;
|
font-weight: 500;
|
cursor: pointer;
|
transition: opacity 0.2s;
|
|
&:active {
|
opacity: 0.8;
|
}
|
}
|
}
|
</style>
|