| | |
| | | <template> |
| | | <!-- 持有仓位列表 --> |
| | | <div id="cryptos"> |
| | | <!-- <div class="flex justify-between" v-if="listData.length">--> |
| | | <!-- <div class="flex" @click.stop="changeIcon">--> |
| | | <!-- <img v-show="!iconShow" src="../../assets/image/public/grey-rounded.png" alt="" class="w-38 h-38"/>--> |
| | | <!-- <img v-show="iconShow" src="../../assets/image/public/blue-rounded.png" alt="" class="w-38 h-38"/>--> |
| | | <!-- <div class="ml-37xw">{{ $t('隐藏其它合约') }}</div>--> |
| | | <!-- </div>--> |
| | | <!-- <button class="border-none all-cancel-btn pl-34 pr-34 pt-10 pb-10 font-25" @click="onSellAll">{{ $t('一键平仓') }}</button>--> |
| | | <!-- </div>--> |
| | | <div class="border-b-color" v-for="item in listData" :key="item.order_no"> |
| | | <div class="flex justify-between pt-44 pb-44"> |
| | | <div class="flex flex-col"> |
| | | <div class="flex items-center"> |
| | | <div class="pl-18 pr-18 pt-3 pb-3 text-white open-btn font-28" |
| | | :class="item.direction == 'buy' ? ' bg-green' : 'bg-red'"> |
| | | {{ item.direction == 'buy' ? $t('开多') : $t('开空') }} |
| | | </div> |
| | | <div class="ml-22 font-31 font-600 "> |
| | | <span class="textColor">{{ item.name }} {{ $t('永续') }}</span> |
| | | <span class="text-grey font-28 font-400 ml-17 mr-17"> |
| | | {{ item.locationType == 1 ? $t('全仓') : $t('逐仓') }} |
| | | {{ item.lever_rate }}x</span> |
| | | </div> |
| | | <img v-if="item.direction == 'buy'" src="@/assets/image/public/green-leverage.png" alt="" |
| | | class="w-32 h-32" /> |
| | | <img v-else src="@/assets/image/public/red-leverage.png" alt="" class="w-32 h-32" /> |
| | | <!-- 持有仓位列表:参考图样式 border-b-color 细浅灰、右上分享、保证金加号弹框 --> |
| | | <div > |
| | | <div class="position-card border-b-color" v-for="item in listData" :key="item.order_no"> |
| | | <!-- 顶部:Buy/Sell 标签 + 交易对 Cross 100X + 刷新 + 分享 --> |
| | | <div class="flex justify-between items-center pt-44 pb-24"> |
| | | <div class="flex items-center"> |
| | | <div class="position-tag font-28" :class="item.direction == 'buy' ? 'bg-green' : 'bg-red'"> |
| | | {{ item.direction == 'buy' ? $t('买入') : $t('卖出') }} |
| | | </div> |
| | | <div class="ml-22 font-31 font-600 textColor"> |
| | | <span>{{ item.name }}</span> |
| | | <span class="text-grey font-28 font-400 ml-17"> |
| | | {{ item.locationType == 1 ? 'Cross' : 'Part' }} {{ item.lever_rate }}X</span> |
| | | </div> |
| | | </div> |
| | | <div class="flex items-center" style="gap: 12px;"> |
| | | <van-icon name="replay" class="position-icon text-grey" size="20" /> |
| | | <van-icon name="share-o" class="position-icon text-grey" size="20" @click.stop="openShare(item)" /> |
| | | </div> |
| | | </div> |
| | | <!-- PnL --> |
| | | <div class="flex justify-between font-28 pb-24"> |
| | | <div> |
| | | <div class="text-grey">{{ $t('PnL(USDT)') || 'PnL(USDT)' }}</div> |
| | | <div class="mt-12" :class="item.profit > 0 ? 'text-green' : 'text-red'"> |
| | | {{ item.profit > 0 ? '+' : '' }}{{ (item.profit != null ? item.profit : 0).toFixed(4) }} |
| | | <span class="ml-8">[{{ item.change_ratio != null ? item.change_ratio : 0 }}%]</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="flex justify-between font-28"> |
| | | <!-- Size / Margin(USDT)+加号 / Margin Ratio --> |
| | | <div class="flex font-28 pb-20"> |
| | | <div class="flex-1"> |
| | | <div class="text-grey">{{ routeType == 'cryptos' ? $t('未实现盈亏(USDT)') : $t('未实现盈亏(USD)') }}</div> |
| | | <div class="mt-20" :class="item.profit > 0 ? 'text-green' : 'text-red'"> |
| | | {{ item.profit > 0 ? '+' + item.profit.toFixed(4) : item.profit.toFixed(4) }}</div> |
| | | <div class="text-grey">{{ $t('Size') || 'Size' }}</div> |
| | | <div class="mt-12 textColor">{{ item.volume != null ? item.volume : (item.deposit != null ? item.deposit : '-') }}</div> |
| | | </div> |
| | | <div class="flex-1"> |
| | | <div class="text-grey text-center">ROE</div> |
| | | <div class="mt-20 text-center" :class="item.change_ratio / 1 > 0 ? 'text-green' : 'text-red'">{{ |
| | | item.change_ratio |
| | | }}% |
| | | <div class="flex-1 flex items-center justify-center"> |
| | | <div> |
| | | <div class="text-grey">{{ $t('margin') }}(USDT)</div> |
| | | <div class="mt-12 flex items-center justify-center"> |
| | | <span class="textColor">{{ item.deposit }}</span> |
| | | <span class="position-plus ml-8" @click.stop="openQuickAdd(item)" v-if="item.locationType == 0">+</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="flex-1 flex justify-end"> |
| | | <button class="font-30 detail-btn border-light-blue greyBg colorMain w-125 h-60" @click="goDetail(item)">{{ |
| | | $t('详情') }}</button> |
| | | <div class="flex-1 text-right"> |
| | | <div class="text-grey">{{ $t('Margin Ratio') || 'Margin Ratio' }}</div> |
| | | <div class="mt-12 textColor">{{ item.margin_ratio != null ? item.margin_ratio + '%' : '-' }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="flex pt-44 pb-32 font-28"> |
| | | <!-- Entry Price / Mark Price / Liq. Price --> |
| | | <div class="flex font-28 pb-24"> |
| | | <div class="flex-1"> |
| | | <div class="text-grey">{{ $t('持仓数量') }}</div> |
| | | <div class="mt-20 textColor">{{ reserve(item.volume / (item.lever_rate ? item.lever_rate : 1),4) }}*{{ |
| | | item.lever_rate ? item.lever_rate : 1 }}x</div> |
| | | <div class="text-grey">{{ $t('Entry Price') || 'Entry Price' }}</div> |
| | | <div class="mt-12 textColor">{{ item.trade_avg_price }}</div> |
| | | </div> |
| | | <div class="flex-1 text-center font-28"> |
| | | <div class="text-grey">{{ $t('交易金额') }} ( {{ routeType == 'cryptos' ? 'USDT' : 'USD' }})</div> |
| | | <div class="mt-20 textColor">{{ item.deposit }}</div> |
| | | <div class="flex-1 text-center"> |
| | | <div class="text-grey">{{ $t('Mark Price') || 'Mark Price' }}</div> |
| | | <div class="mt-12 textColor">{{ item.mark_price }}</div> |
| | | </div> |
| | | <div class="flex-1 flex flex-col items-end font-28"> |
| | | <div class="text-grey">{{ $t('开仓价格') }}</div> |
| | | <div class="mt-20 textColor">{{ item.trade_avg_price }}</div> |
| | | <div class="flex-1 text-right"> |
| | | <div class="text-grey">{{ $t('Liq. Price') || 'Liq. Price' }}</div> |
| | | <div class="mt-12 textColor">{{ item.force_close_rice || '-' }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="flex pb-32 font-28"> |
| | | <div class="flex-1"> |
| | | <div class="text-grey">{{ $t('标记价格') }}</div> |
| | | <div class="mt-20 textColor">{{ item.mark_price }}</div> |
| | | </div> |
| | | <div class="flex-1"> |
| | | <div class="text-grey text-center">{{ $t('强平价格') }}</div> |
| | | <div class="mt-20 textColor text-center">{{ item.force_close_rice }}</div> |
| | | </div> |
| | | <div class="flex-1 flex flex-col items-end justify-end"> |
| | | <button class="greyBg textColor border-none pl-34 pr-34 pt-10 pb-10 rounded-ban" |
| | | @click="onSell(item.order_no)"> |
| | | {{ $t('平仓') }}</button> |
| | | <!-- 细浅灰分隔线 + 底部时间与 TP/SL、Close --> |
| | | <div class="position-card-border"></div> |
| | | <div class="flex justify-between items-center pt-24 pb-32 font-24"> |
| | | <span class="text-grey">{{ formatTime(item.create_time) }}</span> |
| | | <div class="flex" style="gap: 12px;"> |
| | | <button class="position-bottom-btn" @click.stop="onSell(item.order_no)">{{ $t('平仓') }}</button> |
| | | <button class="position-bottom-btn" @click.stop="goTpSl(item)">{{ $t('TP/SL') || 'TP/SL' }}</button> |
| | | </div> |
| | | </div> |
| | | <!-- <div class="flex pb-32 font-28"> |
| | | <div class="flex-1 flex flex-col items-center justify-end"> |
| | | <button class="font-30 detail-btn border-light-blue greyBg colorMain w-125 h-60" @click="goDetail(item)">{{ |
| | | $t('详情') }}</button> |
| | | </div> |
| | | </div> --> |
| | | </div> |
| | | <div class="text-grey text-center py-300 font-30" v-if="!listData.length">{{ $t('您目前没有持仓') }}</div> |
| | | |
| | | <!-- Quick addition 弹框 图三样式 --> |
| | | <van-popup v-model:show="showQuickAdd" position="bottom" round class="quick-add-popup"> |
| | | <div class="quick-add-wrap"> |
| | | <div class="quick-add-header"> |
| | | <span class="quick-add-title">{{ $t('Quick addition') }}</span> |
| | | <span class="quick-add-close" @click="showQuickAdd = false">×</span> |
| | | </div> |
| | | <div class="quick-add-row"> |
| | | <label class="quick-add-label">{{ $t('Quantity') || 'Quantity' }}:</label> |
| | | <input v-model.number="quickAddAmount" type="number" class="quick-add-input" /> |
| | | <span class="quick-add-unit">USDT</span> |
| | | </div> |
| | | <div class="quick-add-slider-wrap"> |
| | | <van-slider v-model="quickAddPercent" :min="0" :max="100" bar-height="4px" active-color="#7c3aed" /> |
| | | <span class="quick-add-percent">{{ quickAddPercent }}%</span> |
| | | </div> |
| | | <div class="quick-add-balance">{{ $t('Futures asset balance') }}: {{ quickAddBalance }} USDT</div> |
| | | <div class="quick-add-submit" @click="submitQuickAdd">{{ $t('Submit') }}</div> |
| | | </div> |
| | | </van-popup> |
| | | |
| | | <!-- TP/SL 止盈止损弹框:底部弹出,展示仓位信息 + 止盈/止损值 + Submit --> |
| | | <van-popup v-model:show="showTpSl" position="bottom" round class="tpsl-popup"> |
| | | <div class="tpsl-wrap"> |
| | | <div class="tpsl-header"> |
| | | <h2 class="tpsl-title">{{ tpSlItem.name || 'BTC USDT' }} {{ tpSlItem.direction == 'buy' ? $t('Long') : $t('Short') }} {{ tpSlItem.lever_rate }}X {{ $t('Take Profit') }} {{ $t('Stop Loss') }}</h2> |
| | | <span class="tpsl-close" @click="showTpSl = false">×</span> |
| | | </div> |
| | | <div class="tpsl-info-grid"> |
| | | <div class="tpsl-info-item"> |
| | | <div class="tpsl-info-label">{{ $t('Entry Price') }}</div> |
| | | <div class="tpsl-info-value">{{ tpSlItem.trade_avg_price || '-' }}</div> |
| | | </div> |
| | | <div class="tpsl-info-item"> |
| | | <div class="tpsl-info-label">{{ $t('Mark Price') }}</div> |
| | | <div class="tpsl-info-value">{{ tpSlItem.mark_price || '-' }}</div> |
| | | </div> |
| | | <div class="tpsl-info-item"> |
| | | <div class="tpsl-info-label">{{ $t('Margin(USDT)') }}</div> |
| | | <div class="tpsl-info-value">{{ tpSlItem.deposit || '-' }}</div> |
| | | </div> |
| | | <div class="tpsl-info-item"> |
| | | <div class="tpsl-info-label">{{ $t('Liq. Price') }}</div> |
| | | <div class="tpsl-info-value tpsl-liq">{{ tpSlItem.force_close_rice || '-' }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="tpsl-row"> |
| | | <label class="tpsl-row-label">{{ $t('Take Profit') }}</label> |
| | | <input v-model="tpSlTakeProfit" type="number" class="tpsl-input" :placeholder="$t('Please enter') || 'Please enter'" /> |
| | | </div> |
| | | <div class="tpsl-row"> |
| | | <label class="tpsl-row-label">{{ $t('Stop Loss') }}</label> |
| | | <input v-model="tpSlStopLoss" type="number" class="tpsl-input" :placeholder="$t('Please enter') || 'Please enter'" /> |
| | | </div> |
| | | <div class="tpsl-submit" @click="submitTpSl">{{ $t('Submit') }}</div> |
| | | </div> |
| | | </van-popup> |
| | | |
| | | <!-- 分享弹框:按参考图样式,背景 share-order.jpg,居中圆角卡片 --> |
| | | <van-popup v-model:show="showShare" position="bottom" round class="share-popup-wrap" :style="{ height: '100%', backgroundColor: 'transparent' }" @click.self="showShare = false"> |
| | | <div class="share-mask" :style="{ backgroundColor: 'transparent' }" @click="showShare = false"></div> |
| | | <div class="share-popup-body"> |
| | | <div ref="shareCardRef" class="share-card" :style="shareCardBgStyle" @click.stop> |
| | | <div class="share-card-close" @click="showShare = false">×</div> |
| | | <div class="share-card-top" :style="{ marginTop: '20%' }"> |
| | | <div class="share-card-pair">{{ shareItem.name || 'BTC USDT' }}</div> |
| | | <div class="share-card-pos">{{ shareItem.direction == 'buy' ? $t('Long') : $t('Short') }} / {{ shareItem.lever_rate }}X</div> |
| | | </div> |
| | | <div class="share-card-pnl" :class="(shareItem.profit != null && shareItem.profit >= 0) ? 'share-pnl-green' : 'share-pnl-red'">{{ shareItem.change_ratio != null ? shareItem.change_ratio : 0 }}%</div> |
| | | <div class="share-card-entry" :style="{ marginTop: '60px' }">{{ $t('Entry Price') }}: {{ shareItem.trade_avg_price || '-' }}</div> |
| | | <div class="share-card-pnl-usdt">{{ $t('PnL(USDT)') }}: {{ shareItem.profit != null ? shareItem.profit.toFixed(4) : '-' }}</div> |
| | | <div class="share-card-ref-row"> |
| | | <div class="share-card-ref-left"> |
| | | <div class="share-card-ref">{{ $t('Referral Code') }}</div> |
| | | <div class="share-card-code">{{ userInfo.usercode || '91872511' }}</div> |
| | | </div> |
| | | <div class="share-card-qr"><canvas ref="shareQrRef" width="160" height="160"></canvas></div> |
| | | </div> |
| | | <button type="button" class="share-card-copy" @click="copyShareLink">{{ $t('复制链接') }}</button> |
| | | <!-- <div class="share-card-save-link" @click="saveShareImage">{{ $t('保存图片') }}</div> --> |
| | | </div> |
| | | </div> |
| | | </van-popup> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { _orderSellBatch, _contractOrderClose } from "@/service/trade.api"; |
| | | import { _orderSellBatch, _contractOrderClose, _addDepositOpen, _setStopPrice } from "@/service/trade.api"; |
| | | import { showConfirmDialog, showToast } from 'vant'; |
| | | import { reserve } from "@/utils/utis"; |
| | | //import { i18n } from "@/i18n"; |
| | | import { mapGetters } from 'vuex'; |
| | | import QRCode from 'qrcode'; |
| | | import html2canvas from 'html2canvas'; |
| | | import shareOrderBg from '@/assets/imgs/new/share-order.jpg'; |
| | | |
| | | export default { |
| | | name: "perpetualPositionList", |
| | | data() { |
| | | return { |
| | | iconShow: false, |
| | | routeType: 'cryptos' |
| | | routeType: 'cryptos', |
| | | showQuickAdd: false, |
| | | quickAddAmount: 0, |
| | | quickAddPercent: 0, |
| | | quickAddBalance: '0', |
| | | _skipQuickAddSync: false, |
| | | quickAddItem: null, |
| | | showShare: false, |
| | | shareItem: {}, |
| | | showTpSl: false, |
| | | tpSlItem: {}, |
| | | tpSlTakeProfit: '', |
| | | tpSlStopLoss: '' |
| | | } |
| | | }, |
| | | props: { |
| | | type: { |
| | | type: String, |
| | | default: '2' // 2 永续合约历史持仓,3交割合约持仓 |
| | | type: { type: String, default: '2' }, |
| | | listData: { type: Array, default: () => [] }, |
| | | futuresBalance: { type: [String, Number], default: '0' } |
| | | }, |
| | | computed: { |
| | | ...mapGetters('user', ['userInfo']), |
| | | quickAddBalanceNum() { |
| | | return Math.max(0, parseFloat(this.quickAddBalance) || 0); |
| | | }, |
| | | listData: { |
| | | type: Array, |
| | | default() { |
| | | return [] |
| | | } |
| | | shareCardBgStyle() { |
| | | return { |
| | | backgroundImage: `url(${shareOrderBg})`, |
| | | backgroundSize: 'cover', |
| | | backgroundRepeat: 'no-repeat', |
| | | backgroundPosition: 'center' |
| | | }; |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.routeType = this.$route.query.type |
| | | this.routeType = this.$route.query.type || 'cryptos'; |
| | | }, |
| | | watch: { |
| | | showShare(val) { |
| | | if (val && this.shareItem && this.$refs.shareQrRef) { |
| | | this.$nextTick(() => this.drawShareQr()); |
| | | } |
| | | }, |
| | | quickAddPercent(val) { |
| | | if (this._skipQuickAddSync) return; |
| | | this._skipQuickAddSync = true; |
| | | const num = this.quickAddBalanceNum; |
| | | const amount = num <= 0 ? 0 : Math.min(num, (num * val) / 100); |
| | | this.quickAddAmount = Math.round(amount * 100) / 100; |
| | | this.$nextTick(() => { this._skipQuickAddSync = false; }); |
| | | }, |
| | | quickAddAmount(val) { |
| | | if (this._skipQuickAddSync) return; |
| | | const num = this.quickAddBalanceNum; |
| | | if (num <= 0) { |
| | | this._skipQuickAddSync = true; |
| | | this.quickAddPercent = 0; |
| | | this.$nextTick(() => { this._skipQuickAddSync = false; }); |
| | | return; |
| | | } |
| | | this._skipQuickAddSync = true; |
| | | const pct = Math.min(100, Math.max(0, (Number(val) || 0) / num * 100)); |
| | | this.quickAddPercent = Math.round(pct * 100) / 100; |
| | | this.$nextTick(() => { this._skipQuickAddSync = false; }); |
| | | } |
| | | }, |
| | | methods: { |
| | | reserve, |
| | | changeIcon() { |
| | | this.iconShow = !this.iconShow; |
| | | formatTime(t) { |
| | | if (!t) return '-'; |
| | | const d = new Date(typeof t === 'number' ? t * 1000 : t); |
| | | const pad = n => (n + '').padStart(2, '0'); |
| | | return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; |
| | | }, |
| | | goDetail(item) { |
| | | this.$router.push({ path: "/cryptos/orderDetail", query: { order_no: item.order_no } }); |
| | | }, |
| | | onSell(order_no) { // 平仓单个 |
| | | goTpSl(item) { |
| | | this.tpSlItem = { ...item }; |
| | | this.tpSlTakeProfit = item.take_profit_price != null ? String(item.take_profit_price) : (item.profit_price != null ? String(item.profit_price) : ''); |
| | | this.tpSlStopLoss = item.stop_loss_price != null ? String(item.stop_loss_price) : (item.loss_price != null ? String(item.loss_price) : ''); |
| | | this.showTpSl = true; |
| | | }, |
| | | submitTpSl() { |
| | | if (!this.tpSlItem || !this.tpSlItem.order_no) { |
| | | showToast(this.$t('请选择仓位')); |
| | | return; |
| | | } |
| | | const stopPriceProfit = String(this.tpSlTakeProfit || '').trim(); |
| | | const stopPriceLoss = String(this.tpSlStopLoss || '').trim(); |
| | | if (!stopPriceProfit && !stopPriceLoss) { |
| | | showToast(this.$t('请输入止盈或止损价格')); |
| | | return; |
| | | } |
| | | _setStopPrice({ |
| | | orderNo: this.tpSlItem.order_no, |
| | | stopPriceProfit: stopPriceProfit || undefined, |
| | | stopPriceLoss: stopPriceLoss || undefined |
| | | }).then(() => { |
| | | showToast(this.$t('提交成功')); |
| | | this.showTpSl = false; |
| | | this.$emit('sell'); |
| | | }).catch(() => {}); |
| | | }, |
| | | onSell(order_no) { |
| | | showConfirmDialog({ |
| | | confirmButtonText: this.$t('确定'), |
| | | cancelButtonText: this.$t('取消'), |
| | |
| | | message: this.$t('是否平仓?'), |
| | | }).then(() => { |
| | | _contractOrderClose({ order_no }).then(() => { |
| | | showToast(this.$t('平仓成功')) |
| | | this.$emit('sell', order_no) |
| | | }) |
| | | }).catch(() => { }); |
| | | showToast(this.$t('平仓成功')); |
| | | this.$emit('sell', order_no); |
| | | }); |
| | | }).catch(() => {}); |
| | | }, |
| | | onSellAll() { // 平仓全部 |
| | | _orderSellBatch().then(() => { |
| | | this.showToast(this.$t('平仓成功')) |
| | | this.$emit('sell') |
| | | }) |
| | | openQuickAdd(item) { |
| | | this.quickAddItem = item; |
| | | this.quickAddBalance = (this.userInfo && this.userInfo.balance) || '0'; |
| | | this.quickAddAmount = 0; |
| | | this.quickAddPercent = 0; |
| | | this.showQuickAdd = true; |
| | | }, |
| | | submitQuickAdd() { |
| | | if (!this.quickAddItem || !this.quickAddItem.order_no) { |
| | | showToast(this.$t('请选择仓位')); |
| | | return; |
| | | } |
| | | const amount = Number(this.quickAddAmount); |
| | | if (isNaN(amount) || amount <= 0) { |
| | | showToast(this.$t('请输入有效追加金额')); |
| | | return; |
| | | } |
| | | _addDepositOpen({ |
| | | orderNo: this.quickAddItem.order_no, |
| | | addDepositOpen: amount |
| | | }).then(() => { |
| | | showToast(this.$t('提交成功')); |
| | | this.showQuickAdd = false; |
| | | this.$emit('sell'); // 刷新持仓列表 |
| | | }).catch(() => {}); |
| | | }, |
| | | openShare(item) { |
| | | this.shareItem = { ...item }; |
| | | this.showShare = true; |
| | | }, |
| | | drawShareQr() { |
| | | const url = (this.userInfo && this.userInfo.url) || window.location.origin + '/#/register?code=' + (this.userInfo && this.userInfo.usercode); |
| | | const canvas = this.$refs.shareQrRef; |
| | | if (canvas) QRCode.toCanvas(canvas, url, { width: 160, margin: 2 }); |
| | | }, |
| | | copyShareLink() { |
| | | const url = (this.userInfo && this.userInfo.url) || window.location.origin + '/#/register?code=' + (this.userInfo && this.userInfo.usercode); |
| | | navigator.clipboard.writeText(url).then(() => showToast(this.$t('复制成功'))); |
| | | }, |
| | | async saveShareImage() { |
| | | const el = this.$refs.shareCardRef; |
| | | if (!el) return; |
| | | try { |
| | | const canvas = await html2canvas(el, { useCORS: true, backgroundColor: '#1e1e2e', scale: 2 }); |
| | | const link = document.createElement('a'); |
| | | link.download = 'share-position.png'; |
| | | link.href = canvas.toDataURL('image/png'); |
| | | link.click(); |
| | | showToast(this.$t('保存成功')); |
| | | } catch (e) { |
| | | showToast(this.$t('保存失败')); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | <style lang="scss" scoped> |
| | | @import "@/assets/init.scss"; |
| | | |
| | | /* 与参考图一致:细浅灰分隔线 */ |
| | | .border-b-color { |
| | | border-bottom: 1px solid #e8e8e8; |
| | | } |
| | | .position-card-border { |
| | | border-bottom: 1px solid #e8e8e8; |
| | | margin: 0 -20px; |
| | | } |
| | | .position-tag { |
| | | padding: 4px 12px; |
| | | border-radius: 6px; |
| | | color: #fff; |
| | | font-weight: 600; |
| | | } |
| | | .bg-green { background: #0ecb81; } |
| | | .bg-red { background: #f6465d; } |
| | | .position-icon { cursor: pointer; } |
| | | .position-plus { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 24px; |
| | | height: 24px; |
| | | border-radius: 50%; |
| | | background: linear-gradient(135deg, #7c3aed, #5b21b6); |
| | | color: #fff; |
| | | font-size: 18px; |
| | | line-height: 1; |
| | | cursor: pointer; |
| | | } |
| | | .position-bottom-btn { |
| | | padding: 8px 20px; |
| | | font-size: 24px; |
| | | color: #333; |
| | | background: #fff; |
| | | border: 1px solid #e8e8e8; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | /* Quick addition 弹框 图三样式(字体放大1倍,上下左右边距加一倍) */ |
| | | .quick-add-popup.van-popup { background: #fff; border-radius: 16px 16px 0 0; } |
| | | .quick-add-wrap { padding: 48px 40px 64px; } |
| | | .quick-add-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 48px; } |
| | | .quick-add-title { font-size: 36px; font-weight: 600; color: #1e1e1e; } |
| | | .quick-add-close { font-size: 56px; color: #999; cursor: pointer; } |
| | | .quick-add-row { display: flex; align-items: center; margin-bottom: 40px; } |
| | | .quick-add-label { font-size: 28px; color: #333; margin-right: 24px; flex-shrink: 0; } |
| | | .quick-add-input { flex: 1; height: 88px; border: 1px solid #e8e8e8; border-radius: 8px; padding: 0 24px; font-size: 28px; } |
| | | .quick-add-unit { margin-left: 24px; font-size: 28px; color: #666; } |
| | | .quick-add-slider-wrap { margin-bottom: 24px; position: relative; } |
| | | .quick-add-slider-wrap .van-slider { margin: 0;width: 85%; } |
| | | .quick-add-percent { position: absolute; right: 0; top: -8px; font-size: 24px; color: #999; } |
| | | .quick-add-balance { font-size: 24px; color: #999; margin-bottom: 48px; } |
| | | .quick-add-submit { |
| | | height: 96px; line-height: 96px; text-align: center; font-size: 32px; font-weight: 600; color: #fff; |
| | | background: linear-gradient(90deg, #7c3aed, #5b21b6); border-radius: 10px; cursor: pointer; |
| | | } |
| | | |
| | | /* TP/SL 止盈止损弹框:白底、数据一排、输入框 F6F5FA */ |
| | | .tpsl-popup.van-popup { background: #fff; border-radius: 16px 16px 0 0; } |
| | | .tpsl-wrap { padding: 48px 40px 64px; background: #fff; } |
| | | .tpsl-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; } |
| | | .tpsl-title { font-size: 32px; font-weight: 600; color: #1e1e1e; line-height: 1.4; margin: 0; flex: 1; padding-right: 16px; } |
| | | .tpsl-close { font-size: 56px; color: #999; cursor: pointer; flex-shrink: 0; } |
| | | .tpsl-info-grid { display: flex; flex-wrap: nowrap; gap: 16px; margin-bottom: 40px; } |
| | | .tpsl-info-item { flex: 1; min-width: 0; } |
| | | .tpsl-info-label { font-size: 22px; color: #999; margin-bottom: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| | | .tpsl-info-value { font-size: 24px; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| | | .tpsl-info-value.tpsl-liq { color: #7c3aed; } |
| | | .tpsl-row { display: flex; align-items: center; margin-bottom: 32px; } |
| | | .tpsl-row-label { font-size: 28px; color: #333; margin-right: 24px; flex-shrink: 0; width: 140px; } |
| | | .tpsl-input { flex: 1; height: 88px; background: #F6F5FA; border: none; border-radius: 8px; padding: 0 24px; font-size: 28px; } |
| | | .tpsl-submit { |
| | | height: 96px; line-height: 96px; text-align: center; font-size: 32px; font-weight: 600; color: #fff; |
| | | background: linear-gradient(90deg, #7c3aed, #5b21b6); border-radius: 10px; cursor: pointer; margin-top: 48px; |
| | | } |
| | | |
| | | /* 分享弹框:弹层与内容区透明,自管遮罩半透明,背后露出页面 */ |
| | | .share-popup-wrap.van-popup { background: transparent !important; } |
| | | .share-popup-wrap :deep(.van-popup__content) { |
| | | background: transparent !important; |
| | | min-height: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | /* 自管遮罩:铺满、半透明黑,点击关闭 */ |
| | | .share-mask { |
| | | position: absolute; |
| | | inset: 0; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | z-index: 0; |
| | | } |
| | | .share-popup-body { |
| | | position: relative; |
| | | z-index: 1; |
| | | min-height: 100vh; |
| | | height: 100%; |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 40px 20px 60px; |
| | | box-sizing: border-box; |
| | | background: transparent !important; |
| | | pointer-events: none; |
| | | } |
| | | .share-popup-body .share-card { pointer-events: auto; } |
| | | .share-popup-body .share-card { |
| | | max-width: 90vw; |
| | | width: 85%; |
| | | flex-shrink: 0; |
| | | } |
| | | /* 卡片:背景 share-order.jpg,宽高由外层控制,内部用相对单位响应 */ |
| | | .share-card { |
| | | position: relative; |
| | | width: 100%; |
| | | min-height: 40%; |
| | | padding: 5% 6% 5%; |
| | | border-radius: 16px; |
| | | overflow: hidden; |
| | | color: #fff; |
| | | display: flex; |
| | | flex-direction: column; |
| | | box-sizing: border-box; |
| | | font-size: clamp(12px, 2.2vw, 20px); |
| | | } |
| | | .share-card-close { |
| | | position: absolute; |
| | | top: 3%; |
| | | right: 3%; |
| | | width: 2.2em; |
| | | height: 2.2em; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 3em; |
| | | line-height: 1; |
| | | color: #fff; |
| | | cursor: pointer; |
| | | z-index: 2; |
| | | } |
| | | .share-card-top { |
| | | margin-bottom: 0.3em; |
| | | } |
| | | .share-card-pair { |
| | | font-size: 1.5em; |
| | | font-weight: 700; |
| | | color: #fff; |
| | | line-height: 1.3; |
| | | } |
| | | .share-card-pos { |
| | | font-size: 1.15em; |
| | | color: rgba(255,255,255,0.9); |
| | | margin-top: 0.2em; |
| | | } |
| | | .share-card-pnl { |
| | | font-size: 2.2em; |
| | | font-weight: 700; |
| | | margin: 0.5em 0 0.6em; |
| | | line-height: 1.2; |
| | | } |
| | | .share-pnl-green { color: #0ecb81; } |
| | | .share-pnl-red { color: #f6465d; } |
| | | .share-card-entry, |
| | | .share-card-pnl-usdt { |
| | | font-size: 1.1em; |
| | | color: #fff; |
| | | margin-bottom: 0.25em; |
| | | } |
| | | .share-card-ref-row { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-top: 1em; |
| | | margin-bottom: 0.8em; |
| | | gap: 0.5em; |
| | | } |
| | | .share-card-ref-left { flex: 1; min-width: 0; } |
| | | .share-card-ref { |
| | | font-size: 1em; |
| | | color: rgba(255,255,255,0.9); |
| | | margin-bottom: 0.2em; |
| | | } |
| | | .share-card-code { |
| | | font-size: 1.35em; |
| | | font-weight: 700; |
| | | color: #fff; |
| | | } |
| | | .share-card-qr { |
| | | width: 22%; |
| | | max-width: 110px; |
| | | aspect-ratio: 1; |
| | | flex-shrink: 0; |
| | | background: #fff; |
| | | padding: 4%; |
| | | border-radius: 8px; |
| | | } |
| | | .share-card-qr canvas { width: 100% !important; height: 100% !important; display: block; } |
| | | .share-card-copy { |
| | | width: 100%; |
| | | height: 2.8em; |
| | | line-height: 2.8em; |
| | | text-align: center; |
| | | font-size: 1em; |
| | | font-weight: 600; |
| | | color: #fff; |
| | | background: linear-gradient(90deg, #7c3aed, #5b21b6); |
| | | border: none; |
| | | border-radius: 10px; |
| | | cursor: pointer; |
| | | z-index: 2; |
| | | margin-top: auto; |
| | | } |
| | | .share-card-save-link { |
| | | text-align: center; |
| | | font-size: 0.85em; |
| | | color: rgba(255,255,255,0.8); |
| | | margin-top: 0.6em; |
| | | cursor: pointer; |
| | | z-index: 2; |
| | | } |
| | | |
| | | #cryptos { |
| | | .open-btn { |
| | | border-radius: 5px; |
| | | } |
| | | |
| | | .detail-btn { |
| | | border: 1px solid; |
| | | border-radius: 2.5rem; |
| | | background: $US_tabActice_background; |
| | | color: $color_main; |
| | | } |
| | | |
| | | .text-green { |
| | | color: #06AD95; |
| | | } |
| | | .text-green { color: #06AD95; } |
| | | } |
| | | </style> |