| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738 |
- <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="hyu">
- 当前资金费率
- <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 hyu = () => {
- console.log("kkkkkkk");
- showModal.value = true;
- };
- // 状态定义
- 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 很重要,为了防止底部内容被固定的 footer 遮挡,保留它 */
- padding-bottom: 90px;
- color: #333;
- z-index: 200;
- /* 删掉了 position: fixed/top/left */
- display: flex;
- flex-direction: column;
- width: 100%;
- }
- /* 导航 & 头部 */
- .nav-bar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- height: 44px;
- padding: 0 16px;
- /* 新增以下 3 行,实现吸顶效果 */
- position: sticky;
- top: 0;
- background: #fff; /* 防止透明背景导致内容重叠 */
- z-index: 100;
- }
- .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: 0px 15px;
- 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>
|