| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- <template>
- <div class="calculator-page">
- <div class="nav-bar">
- <div class="nav-left" @click="$router.back()">
- <div>
- <VanIcon size="18" name="arrow-left"/>
- </div>
- </div>
- <div class="nav-title">计算器</div>
- <div class="nav-right"></div>
- </div>
- <div class="header-info">
- <div class="icon-wrapper">
- <vanIcon size="20" name="bars" />
- </div>
- <h2 class="pair-name">BTCUSDT 永续</h2>
- </div>
- <div class="tabs-wrapper">
- <div class="tabs-content pf600">
- <div
- v-for="(tab, index) in tabs"
- :key="index"
- class="tab-item"
- :class="{ active: currentTab === index }"
- @click="currentTab = index"
- >
- {{ tab }}
- <div class="active-bar" v-if="currentTab === index"></div>
- </div>
- </div>
- </div>
- <div v-if="currentTab === 2" class="mode-selectors">
- <div class="select-box">全仓 ▾</div>
- <div class="select-box">单向持仓</div>
- </div>
- <div class="trade-direction">
- <button
- class="dir-btn long"
- :class="{ active: direction === 'long' }"
- @click="direction = 'long'"
- >
- 买入(做多)
- </button>
- <button
- class="dir-btn short"
- :class="{ active: direction === 'short' }"
- @click="direction = 'short'"
- >
- 卖出(做空)
- </button>
- </div>
- <div v-if="currentTab === 5" class="funding-rate-display">
- <div class="rate-value">0.0030%</div>
- <div class="rate-label" @click="showModal=true">当前资金费率
- <VanIcon name="question-o" />
- </div>
- </div>
- <div v-if="currentTab !== 4 && currentTab !== 5" class="leverage-section">
- <div class="label">倍数</div>
- <div class="leverage-control">
- <button class="ctrl-btn" @click="stepLeverage(-1)">-</button>
- <div class="leverage-val">{{ leverage }}x</div>
- <button class="ctrl-btn" @click="stepLeverage(1)">+</button>
- </div>
- <div class="slider-area">
- <LeverageSlider
- v-model="leverage"
- :marks="leverageMarks"
- :color="themeColor"
- />
- </div>
- <div class="leverage-tip">
- 当前杠杆倍数最高可持有头寸 1,800,000,000 USDT
- </div>
- </div>
- <div class="form-group">
- <div v-if="currentTab !== 4 && currentTab !== 5" class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.entryPrice">开仓价格</span>
- <input type="number" v-model="form.entryPrice" />
- <div class="suffix">USDT <span class="tag-latest">最新</span></div>
- </div>
- </div>
- <template v-if="currentTab === 0">
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.closePrice">平仓价格</span>
- <input type="number" v-model="form.closePrice" />
- <div class="suffix">USDT</div>
- </div>
- </div>
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.amount">成交数量</span>
- <input type="number" v-model="form.amount" />
- <div class="suffix">BTC</div>
- </div>
- </div>
- </template>
- <template v-if="currentTab === 1">
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.amount">成交数量</span>
- <input type="number" v-model="form.amount" />
- <div class="suffix">BTC</div>
- </div>
- </div>
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.pnl">预期收益额</span>
- <input type="number" v-model="form.pnl" />
- <div class="suffix">USDT</div>
- </div>
- </div>
- </template>
- <template v-if="currentTab === 2">
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.amount">开仓数量</span>
- <input type="number" v-model="form.amount" />
- <div class="suffix">BTC</div>
- </div>
- </div>
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.margin">保证金</span>
- <input type="number" v-model="form.margin" />
- <div class="suffix">USDT</div>
- </div>
- </div>
- </template>
- <template v-if="currentTab === 3">
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.balance">账户余额</span>
- <input type="number" v-model="form.balance" />
- <div class="suffix">USDT</div>
- </div>
- </div>
- </template>
- <template v-if="currentTab === 4">
- <div class="grid-header">
- <span>开仓价格(USDT)</span>
- <span>成交数量(BTC)</span>
- </div>
- <div class="dynamic-row" v-for="(item, idx) in avgPriceList" :key="idx">
- <div class="input-container half">
- <input type="number" v-model="item.price" placeholder="0.00" />
- </div>
- <div class="input-container half">
- <input type="number" v-model="item.amount" placeholder="0.00" />
- </div>
- <div style="display: flex; align-items: center;">
- <div class="remove-btn" @click="removeRow(idx)" v-if="avgPriceList.length > 1">
- <span>-</span>
- </div>
- </div>
- </div>
- <button class="add-position-btn" @click="addRow">增加仓位</button>
- </template>
- <template v-if="currentTab === 5">
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.entryPrice">标记价格</span>
- <input type="number" v-model="form.entryPrice" />
- <div class="suffix">USDT <span class="tag-latest">最新</span></div>
- </div>
- </div>
- <div class="input-row">
- <div class="input-container">
- <span class="placeholder" v-show="!form.amount">持仓数量</span>
- <input type="number" v-model="form.amount" />
- <div class="suffix">BTC</div>
- </div>
- </div>
- </template>
- </div>
- <div class="result-section">
- <h3>计算结果</h3>
- <p class="sub-text">实际市场存在变动,计算价格仅供参考</p>
- <div v-if="currentTab === 0">
- <div class="res-row"><span>保证金</span><span>{{ result.margin }} USDT</span></div>
- <div class="res-row"><span>收益</span><span :class="resultClass">{{ result.pnl }} USDT</span></div>
- <div class="res-row"><span>收益率</span><span :class="resultClass">{{ result.roe }}%</span></div>
- </div>
- <div v-if="currentTab === 1">
- <div class="res-row"><span>目标价格</span><span>-- USDT</span></div>
- </div>
- <div v-if="currentTab === 2">
- <div class="res-row"><span>强平价格</span><span>-- USDT</span></div>
- </div>
- <div v-if="currentTab === 3">
- <div class="res-row"><span>可开</span><span>-- USDT</span></div>
- <div class="res-row"><span>可开</span><span>-- BTC</span></div>
- <p class="small-tip">此计算最大可开数量时,将不考虑您的开仓损失</p>
- </div>
- <div v-if="currentTab === 4">
- <div class="res-row"><span>开仓均价</span><span>-- USDT</span></div>
- </div>
- <div v-if="currentTab === 5">
- <div class="res-row"><span>资金费用</span><span>-- USDT</span></div>
- </div>
- </div>
- <div class="footer-btns">
- <button class="btn-reset" @click="reset">重置</button>
- <button class="btn-calc" :style="{ background: themeColor }" @click="calculate">计算</button>
- </div>
- <div>
- <ProfitAndLossPrompt
- v-model:visible="showModal"
- ></ProfitAndLossPrompt>
- </div>
- </div>
- </template>
- <script setup>
- import { Icon as VanIcon } from 'vant';
- import {ref, computed, defineAsyncComponent} from 'vue';
- // 请确保路径正确,指向刚才那个更新过的子组件
- const LeverageSlider = defineAsyncComponent(()=>import('./calculator/LeverageSlider.vue'));
- const ProfitAndLossPrompt = defineAsyncComponent(() => import('./calculator/FundingRateReminder.vue'));
- //资金费率提示
- const showModal = ref(false);
- // 状态定义
- const tabs = ['收益', '目标价格', '强平价格', '可开', '开仓均价', '资金费用'];
- const currentTab = ref(0);
- const direction = ref('long');
- const leverage = ref(10); // 默认从 10x 开始
- // ★ 固定阶梯数组 (10, 50, 100, 500, 1000)
- const leverageMarks = [10, 50, 100, 500, 1000];
- const form = ref({
- entryPrice: '',
- closePrice: '',
- amount: '',
- margin: '',
- balance: '',
- pnl: ''
- });
- const avgPriceList = ref([{ price: '', amount: '' }, { price: '', amount: '' }]);
- const result = ref({ margin: '--', pnl: '--', roe: '--' });
- // 计算属性
- const themeColor = computed(() => direction.value === 'long' ? '#2ebd85' : '#f6465d');
- const resultClass = computed(() => {
- if (parseFloat(result.value.pnl) > 0) return 'text-green';
- if (parseFloat(result.value.pnl) < 0) return 'text-red';
- return '';
- });
- // ★ 核心修改:步进式调整杠杆 (点击加减号时)
- const stepLeverage = (dir) => {
- // 1. 找到当前值在数组中的索引
- let currentIndex = leverageMarks.indexOf(leverage.value);
- // 如果当前值不在数组里(比如手动改成了20),就找一个最近的索引
- if (currentIndex === -1) {
- // 简单逻辑:默认归位到 0
- currentIndex = 0;
- }
- // 2. 计算下一个索引
- let nextIndex = currentIndex + dir;
- // 3. 边界限制
- if (nextIndex < 0) nextIndex = 0;
- if (nextIndex >= leverageMarks.length) nextIndex = leverageMarks.length - 1;
- // 4. 更新值
- leverage.value = leverageMarks[nextIndex];
- };
- // 简单计算逻辑 (Tab 0 收益)
- const calculate = () => {
- if (currentTab.value === 0) {
- const entry = parseFloat(form.value.entryPrice);
- const close = parseFloat(form.value.closePrice);
- const amt = parseFloat(form.value.amount);
- const lev = leverage.value;
- if (entry && close && amt && lev) {
- const margin = (entry * amt) / lev;
- let pnl = 0;
- if (direction.value === 'long') {
- pnl = (close - entry) * amt;
- } else {
- pnl = (entry - close) * amt;
- }
- const roe = (pnl / margin) * 100;
- result.value = {
- margin: margin.toFixed(2),
- pnl: pnl.toFixed(2),
- roe: roe.toFixed(2)
- };
- }
- }
- };
- // 其他辅助函数
- const addRow = () => avgPriceList.value.push({ price: '', amount: '' });
- const removeRow = (idx) => avgPriceList.value.splice(idx, 1);
- const reset = () => {
- form.value = { entryPrice: '', closePrice: '', amount: '', margin: '', balance: '', pnl: '' };
- result.value = { margin: '--', pnl: '--', roe: '--' };
- leverage.value = 10;
- };
- </script>
- <style scoped>
- * {
- box-sizing: border-box;
- }
- .calculator-page {
- font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif;
- background: #fff;
- min-height: 100vh;
- padding-bottom: 90px;
- color: #333;
- z-index: 200;
- position: fixed; /* 固定定位 */
- top: 0;
- left: 0;
- display: flex;
- flex-direction: column;
- width: 100%;
- }
- /* 导航 & 头部 */
- .nav-bar { display: flex; justify-content: space-between; align-items: center;
- height: 44px; padding: 0 16px; }
- .nav-left { font-size: 24px; cursor: pointer; }
- .nav-title { font-size: 18px; font-weight: 600; }
- .nav-right { width: 20px; }
- .header-info {
- display: flex;
- align-items: center;
- padding: 8px 12px;
- background-color: #fff;
- width: 100%;
- }
- .icon-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- margin-bottom: 1px;
- }
- .pair-name {
- margin: 0;
- font-size: 18px;
- font-weight: bold;
- color: #333;
- line-height: 1;
- }
- /* Tabs */
- .tabs-wrapper { overflow-x: auto; scrollbar-width: none; margin-bottom: 15px; }
- .tabs-content { display: flex; justify-content: space-between; padding: 0 15px;
- border-bottom: 1px solid #f5f5f5; }
- .tab-item { white-space: nowrap; padding: 12px 0; font-size: 14px; color: #999; position: relative;
- cursor: pointer; }
- .tab-item.active { color: #111; font-weight: 600; }
- .active-bar { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 20px;
- height: 3px; background: #111; border-radius: 2px; }
- /* 模式选择 */
- .mode-selectors { display: flex; gap: 10px; padding: 0 16px; margin-bottom: 15px; }
- .select-box { flex: 1; background: #f5f5f5; height: 36px; display: flex; align-items: center;
- padding: 0 12px; border-radius: 4px; font-size: 14px; color: #333; }
- /* 做多做空 */
- .trade-direction { display: flex; gap: 10px; padding: 0 16px; margin-bottom: 20px; }
- .dir-btn { flex: 1; padding: 10px 0; border: none; border-radius: 4px; font-size: 14px;
- cursor: pointer; background: #f5f5f5; color: #999; transition: all 0.2s; }
- .dir-btn.long.active { background: #2ebd85; color: #fff; font-weight: 600; }
- .dir-btn.short.active { background: #f6465d; color: #fff; font-weight: 600; }
- /* 资金费率 */
- .funding-rate-display { text-align: center; padding: 20px 0; }
- .rate-value { font-size: 32px; color: #666; font-weight: 500; }
- .rate-label { font-size: 12px; color: #999; margin-top: 5px; }
- /* 杠杆区域 */
- .leverage-section { padding: 0 16px; margin-bottom: 20px; }
- .label { font-size: 12px; color: #666; margin-bottom: 8px; }
- /* 蓝色边框输入框 */
- .leverage-control {
- display: flex;
- align-items: center;
- background: #f7f8fa;
- border-radius: 6px;
- height: 44px;
- margin-bottom: 10px;
- overflow: hidden;
- }
- .ctrl-btn {
- width: 50px;
- height: 100%;
- border: none;
- background: transparent;
- font-size: 22px;
- color: #999;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- padding-bottom: 4px;
- }
- .leverage-val {
- flex: 1;
- text-align: center;
- font-size: 16px;
- font-weight: 500;
- color: #333;
- }
- .slider-area { padding: 0 10px; }
- .leverage-tip { font-size: 12px; color: #333; margin-top: 10px; }
- /* 表单 */
- .form-group { padding: 0 16px; }
- .input-row { margin-bottom: 12px; }
- .input-container { position: relative; display: flex; align-items: center; background: #f5f5f5;
- height: 44px; border-radius: 4px; padding: 0 12px; }
- .placeholder { position: absolute; color: #bbb; font-size: 14px; pointer-events: none; }
- .input-container input { flex: 1; border: none; background: transparent; outline: none;
- font-size: 14px; z-index: 1; height: 100%; width: 100%; }
- .suffix { font-size: 14px; color: #666; margin-left: 8px; z-index: 2; white-space: nowrap; }
- .tag-latest { color: #f6465d; margin-left: 4px; }
- /* 表格布局 */
- .grid-header { display: flex; font-size: 12px; color: #333; margin-bottom: 8px; }
- .grid-header span { flex: 1; }
- .dynamic-row { display: flex; gap: 10px; margin-bottom: 10px; align-items: center; }
- .input-container.half { flex: 1; }
- .remove-btn { width: 24px; height: 24px; border: 1px solid #f6465d; color: #f6465d;
- border-radius: 50%; display: flex; align-items: center; justify-content: center;
- font-size: 18px; cursor: pointer; margin-left: 5px; }
- .remove-btn span { margin-bottom: 5px; }
- .add-position-btn { width: 100%; height: 44px; background: #f6465d; color: white;
- border: none; border-radius: 22px; margin-top: 10px; font-size: 14px; }
- /* 结果 */
- .result-section { padding: 20px 16px; }
- .result-section h3 { margin: 0 0 5px; font-size: 16px; font-weight: 600; }
- .sub-text { font-size: 12px; color: #999; margin-bottom: 15px; }
- .res-row { display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 14px; }
- .res-row span:first-child { color: #666; }
- .res-row span:last-child { color: #333; font-weight: 500; }
- .text-green { color: #2ebd85; }
- .text-red { color: #f6465d; }
- .small-tip { font-size: 12px; color: #999; margin-top: 5px; }
- /* 底部按钮 */
- .footer-btns { position: fixed; bottom: 0; left: 0; width: 100%; background: #fff;
- padding: 10px 16px 20px; display: flex; gap: 15px; box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
- box-sizing: border-box; z-index: 100; }
- .btn-reset { flex: 1; height: 44px; border: none; background: #bbb; color: #fff;
- font-size: 16px; border-radius: 4px; }
- .btn-calc { flex: 2; height: 44px; border: none; color: #fff; font-size: 16px;
- border-radius: 4px; font-weight: 600; transition: background 0.3s; }
- </style>
|