1503 lines
44 KiB
Vue
1503 lines
44 KiB
Vue
<template>
|
||
<!-- 桌面端:完整交易卡片 -->
|
||
<v-card v-if="!mobile" class="trade-component">
|
||
<!-- Header -->
|
||
<div class="header">
|
||
<v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact">
|
||
<v-tab value="buy" class="minimal-tab">Buy</v-tab>
|
||
<v-tab value="sell" class="minimal-tab">Sell</v-tab>
|
||
</v-tabs>
|
||
<v-menu>
|
||
<template v-slot:activator="{ props, isActive }">
|
||
<v-btn v-bind="props" class="limit-btn" text end>
|
||
{{ limitType }}
|
||
<v-icon right>{{ isActive ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||
</v-btn>
|
||
</template>
|
||
<v-list>
|
||
<v-list-item @click="limitType = 'Market'">
|
||
<v-list-item-title>Market</v-list-item-title>
|
||
</v-list-item>
|
||
<v-list-item @click="limitType = 'Limit'">
|
||
<v-list-item-title>Limit</v-list-item-title>
|
||
</v-list-item>
|
||
<v-divider></v-divider>
|
||
<v-list-item @click="openMergeDialog">
|
||
<v-list-item-title>Merge</v-list-item-title>
|
||
</v-list-item>
|
||
<v-list-item @click="openSplitDialog">
|
||
<v-list-item-title>Split</v-list-item-title>
|
||
</v-list-item>
|
||
</v-list>
|
||
</v-menu>
|
||
</div>
|
||
|
||
<!-- Market Mode View -->
|
||
<template v-if="isMarketMode">
|
||
<!-- Balance > 0: Show Trade Interface -->
|
||
<template v-if="balance > 0">
|
||
<!-- Price Options -->
|
||
<div class="price-options">
|
||
<v-btn
|
||
class="yes-btn"
|
||
:class="{ active: selectedOption === 'yes' }"
|
||
text
|
||
@click="handleOptionChange('yes')"
|
||
>
|
||
Yes {{ yesPriceCents }}¢
|
||
</v-btn>
|
||
<v-btn
|
||
class="no-btn"
|
||
:class="{ active: selectedOption === 'no' }"
|
||
text
|
||
@click="handleOptionChange('no')"
|
||
>
|
||
No {{ noPriceCents }}¢
|
||
</v-btn>
|
||
</div>
|
||
|
||
<!-- Total and To Win (Buy模式) 或 You'll receive (Sell模式) -->
|
||
<div class="total-section">
|
||
<!-- Buy模式 -->
|
||
<template v-if="activeTab === 'buy'">
|
||
<div class="total-row">
|
||
<span class="label">Total</span>
|
||
<span class="total-value">${{ totalPrice }}</span>
|
||
</div>
|
||
<div class="total-row">
|
||
<span class="label">To win</span>
|
||
<span class="to-win-value">
|
||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||
$20
|
||
</span>
|
||
</div>
|
||
</template>
|
||
<!-- Sell模式 -->
|
||
<template v-else>
|
||
<div class="total-row">
|
||
<span class="label">You'll receive</span>
|
||
<span class="to-win-value">
|
||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||
${{ totalPrice }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- Action Button -->
|
||
<v-btn class="action-btn" @click="submitOrder">
|
||
{{ actionButtonText }}
|
||
</v-btn>
|
||
</template>
|
||
|
||
<!-- Balance <= 0: Show Deposit Interface -->
|
||
<template v-else>
|
||
<!-- Price Options -->
|
||
<div class="price-options">
|
||
<v-btn
|
||
class="yes-btn"
|
||
:class="{ active: selectedOption === 'yes' }"
|
||
text
|
||
@click="handleOptionChange('yes')"
|
||
>
|
||
Yes {{ yesPriceCents }}¢
|
||
</v-btn>
|
||
<v-btn
|
||
class="no-btn"
|
||
:class="{ active: selectedOption === 'no' }"
|
||
text
|
||
@click="handleOptionChange('no')"
|
||
>
|
||
No {{ noPriceCents }}¢
|
||
</v-btn>
|
||
</div>
|
||
|
||
<!-- Amount Section -->
|
||
<div class="input-group">
|
||
<div class="amount-header">
|
||
<div>
|
||
<span class="label amount-label">Amount</span>
|
||
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
|
||
</div>
|
||
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||
</div>
|
||
|
||
<!-- Amount Buttons -->
|
||
<div class="amount-buttons">
|
||
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
|
||
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
|
||
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
|
||
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Deposit Button -->
|
||
<v-btn
|
||
class="deposit-btn"
|
||
@click="deposit"
|
||
>
|
||
Deposit
|
||
</v-btn>
|
||
</template>
|
||
</template>
|
||
|
||
<!-- Limit Mode View -->
|
||
<template v-else>
|
||
<!-- Price Options -->
|
||
<div class="price-options">
|
||
<v-btn
|
||
class="yes-btn"
|
||
:class="{ active: selectedOption === 'yes' }"
|
||
text
|
||
@click="handleOptionChange('yes')"
|
||
>
|
||
Yes {{ yesPriceCents }}¢
|
||
</v-btn>
|
||
<v-btn
|
||
class="no-btn"
|
||
:class="{ active: selectedOption === 'no' }"
|
||
text
|
||
@click="handleOptionChange('no')"
|
||
>
|
||
No {{ noPriceCents }}¢
|
||
</v-btn>
|
||
</div>
|
||
|
||
<!-- Limit Price -->
|
||
<div class="input-group limit-price-group">
|
||
<div class="limit-price-header">
|
||
<span class="label">Limit Price</span>
|
||
<div class="price-input">
|
||
<v-btn class="adjust-btn" icon @click="decreasePrice">
|
||
<v-icon>mdi-minus</v-icon>
|
||
</v-btn>
|
||
<v-text-field
|
||
v-model.number="limitPrice"
|
||
type="number"
|
||
min="0.01"
|
||
step="0.01"
|
||
class="price-input-field"
|
||
hide-details
|
||
density="compact"
|
||
></v-text-field>
|
||
<v-btn class="adjust-btn" icon @click="increasePrice">
|
||
<v-icon>mdi-plus</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Shares -->
|
||
<div class="input-group shares-group">
|
||
<div class="shares-header">
|
||
<span class="label">Shares</span>
|
||
<div class="shares-input">
|
||
<v-text-field
|
||
v-model.number="shares"
|
||
type="number"
|
||
min="0"
|
||
class="shares-input-field"
|
||
hide-details
|
||
density="compact"
|
||
></v-text-field>
|
||
</div>
|
||
</div>
|
||
<!-- Buy模式的份额调整按钮 -->
|
||
<div v-if="activeTab === 'buy'" class="shares-buttons">
|
||
<v-btn class="share-btn" @click="adjustShares(-100)">-100</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(-10)">-10</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(10)">+10</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(100)">+100</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(200)">+200</v-btn>
|
||
</div>
|
||
<!-- Sell模式的份额调整按钮 -->
|
||
<div v-else class="shares-buttons">
|
||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||
</div>
|
||
<div v-if="activeTab === 'buy'" class="matching-info">
|
||
<v-icon size="14">mdi-information</v-icon>
|
||
<span>20.00 matching</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Set Expiration -->
|
||
<div class="input-group expiration-group">
|
||
<div class="expiration-header">
|
||
<span class="label">Set Expiration</span>
|
||
<v-switch v-model="expirationEnabled" class="expiration-switch" hide-details></v-switch>
|
||
</div>
|
||
<!-- Expiration Time Dropdown -->
|
||
<v-select
|
||
v-if="expirationEnabled"
|
||
v-model="expirationTime"
|
||
:items="expirationOptions"
|
||
class="expiration-select"
|
||
hide-details
|
||
density="compact"
|
||
></v-select>
|
||
</div>
|
||
|
||
<!-- Total and To Win (Buy模式) 或 You'll receive (Sell模式) -->
|
||
<div class="total-section">
|
||
<!-- Buy模式 -->
|
||
<template v-if="activeTab === 'buy'">
|
||
<div class="total-row">
|
||
<span class="label">Total</span>
|
||
<span class="total-value">${{ totalPrice }}</span>
|
||
</div>
|
||
<div class="total-row">
|
||
<span class="label">To win</span>
|
||
<span class="to-win-value">
|
||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||
$20
|
||
</span>
|
||
</div>
|
||
</template>
|
||
<!-- Sell模式 -->
|
||
<template v-else>
|
||
<div class="total-row">
|
||
<span class="label">You'll receive</span>
|
||
<span class="to-win-value">
|
||
<v-icon size="16" color="green">mdi-currency-usd</v-icon>
|
||
${{ totalPrice }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- Action Button -->
|
||
<v-btn class="action-btn" @click="submitOrder">
|
||
{{ actionButtonText }}
|
||
</v-btn>
|
||
</template>
|
||
</v-card>
|
||
|
||
<!-- 移动端且由首页卡片嵌入:只渲染交易表单,无底部栏、无内部 sheet -->
|
||
<template v-else-if="embeddedInSheet">
|
||
<v-sheet class="trade-sheet-paper trade-sheet-paper--embedded" rounded="lg">
|
||
<div class="trade-component trade-sheet-inner">
|
||
<div class="header">
|
||
<v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact">
|
||
<v-tab value="buy" class="minimal-tab">Buy</v-tab>
|
||
<v-tab value="sell" class="minimal-tab">Sell</v-tab>
|
||
</v-tabs>
|
||
<v-menu class="limit-dropdown hide-in-mobile-sheet">
|
||
<template v-slot:activator="{ props: limitProps, isActive }">
|
||
<v-btn v-bind="limitProps" class="limit-btn" text end>
|
||
{{ limitType }}
|
||
<v-icon right>{{ isActive ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||
</v-btn>
|
||
</template>
|
||
<v-list>
|
||
<v-list-item @click="limitType = 'Market'"><v-list-item-title>Market</v-list-item-title></v-list-item>
|
||
<v-list-item @click="limitType = 'Limit'"><v-list-item-title>Limit</v-list-item-title></v-list-item>
|
||
<v-divider></v-divider>
|
||
<v-list-item @click="openMergeDialog"><v-list-item-title>Merge</v-list-item-title></v-list-item>
|
||
<v-list-item @click="openSplitDialog"><v-list-item-title>Split</v-list-item-title></v-list-item>
|
||
</v-list>
|
||
</v-menu>
|
||
</div>
|
||
<template v-if="isMarketMode">
|
||
<template v-if="balance > 0">
|
||
<div class="price-options hide-in-mobile-sheet">
|
||
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
||
</div>
|
||
<div class="total-section">
|
||
<template v-if="activeTab === 'buy'">
|
||
<div class="total-row"><span class="label">Total</span><span class="total-value">${{ totalPrice }}</span></div>
|
||
<div class="total-row"><span class="label">To win</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> $20</span></div>
|
||
</template>
|
||
<template v-else>
|
||
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
||
</template>
|
||
</div>
|
||
<v-btn class="action-btn" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
||
</template>
|
||
<template v-else>
|
||
<div class="price-options hide-in-mobile-sheet">
|
||
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
||
</div>
|
||
<div class="input-group">
|
||
<div class="amount-header">
|
||
<div><span class="label amount-label">Amount</span><span class="balance-label">Balance ${{ balance.toFixed(2) }}</span></div>
|
||
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||
</div>
|
||
<div class="amount-buttons">
|
||
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
|
||
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
|
||
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
|
||
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
|
||
</div>
|
||
</div>
|
||
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn>
|
||
</template>
|
||
</template>
|
||
<template v-else>
|
||
<div class="price-options hide-in-mobile-sheet">
|
||
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
||
</div>
|
||
<div class="input-group limit-price-group">
|
||
<div class="limit-price-header">
|
||
<span class="label">Limit Price</span>
|
||
<div class="price-input">
|
||
<v-btn class="adjust-btn" icon @click="decreasePrice"><v-icon>mdi-minus</v-icon></v-btn>
|
||
<v-text-field v-model.number="limitPrice" type="number" min="0.01" step="0.01" class="price-input-field" hide-details density="compact"></v-text-field>
|
||
<v-btn class="adjust-btn" icon @click="increasePrice"><v-icon>mdi-plus</v-icon></v-btn>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="input-group shares-group">
|
||
<div class="shares-header">
|
||
<span class="label">Shares</span>
|
||
<div class="shares-input">
|
||
<v-text-field v-model.number="shares" type="number" min="0" class="shares-input-field" hide-details density="compact"></v-text-field>
|
||
</div>
|
||
</div>
|
||
<div v-if="activeTab === 'buy'" class="shares-buttons">
|
||
<v-btn class="share-btn" @click="adjustShares(-100)">-100</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(-10)">-10</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(10)">+10</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(100)">+100</v-btn>
|
||
</div>
|
||
<div v-else class="shares-buttons">
|
||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||
</div>
|
||
</div>
|
||
<div class="input-group expiration-group">
|
||
<div class="expiration-header">
|
||
<span class="label">Set expiration</span>
|
||
<v-switch v-model="expirationEnabled" class="expiration-switch" hide-details color="primary"></v-switch>
|
||
</div>
|
||
<v-select
|
||
v-if="expirationEnabled"
|
||
v-model="expirationTime"
|
||
:items="expirationOptions"
|
||
class="expiration-select"
|
||
hide-details
|
||
density="compact"
|
||
></v-select>
|
||
</div>
|
||
<div class="total-section">
|
||
<template v-if="activeTab === 'buy'">
|
||
<div class="total-row"><span class="label">Total</span><span class="total-value">${{ totalPrice }}</span></div>
|
||
<div class="total-row"><span class="label">To win</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> $20</span></div>
|
||
</template>
|
||
<template v-else>
|
||
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
||
</template>
|
||
</div>
|
||
<v-btn class="action-btn" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
||
</template>
|
||
</div>
|
||
</v-sheet>
|
||
</template>
|
||
|
||
<!-- 移动端:底部紧凑栏 + 底部弹出层 -->
|
||
<template v-else>
|
||
<div class="mobile-trade-bar-spacer" aria-hidden="true"></div>
|
||
<div class="mobile-trade-bar">
|
||
<v-btn
|
||
class="mobile-bar-btn mobile-bar-yes"
|
||
variant="flat"
|
||
color="success"
|
||
rounded="pill"
|
||
block
|
||
@click="openSheet('yes')"
|
||
>
|
||
Buy Yes {{ yesPriceCents }}¢
|
||
</v-btn>
|
||
<v-btn
|
||
class="mobile-bar-btn mobile-bar-no"
|
||
variant="flat"
|
||
color="error"
|
||
rounded="pill"
|
||
block
|
||
@click="openSheet('no')"
|
||
>
|
||
Buy No {{ noPriceCents }}¢
|
||
</v-btn>
|
||
</div>
|
||
|
||
<v-bottom-sheet v-model="sheetOpen" class="trade-sheet">
|
||
<v-sheet class="trade-sheet-paper" rounded="lg">
|
||
<div class="trade-component trade-sheet-inner">
|
||
<div class="header">
|
||
<v-tabs v-model="activeTab" class="buy-sell-tabs minimal-tabs" density="compact">
|
||
<v-tab value="buy" class="minimal-tab">Buy</v-tab>
|
||
<v-tab value="sell" class="minimal-tab">Sell</v-tab>
|
||
</v-tabs>
|
||
<v-menu class="limit-dropdown hide-in-mobile-sheet">
|
||
<template v-slot:activator="{ props: limitProps, isActive }">
|
||
<v-btn v-bind="limitProps" class="limit-btn" text end>
|
||
{{ limitType }}
|
||
<v-icon right>{{ isActive ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||
</v-btn>
|
||
</template>
|
||
<v-list>
|
||
<v-list-item @click="limitType = 'Market'"><v-list-item-title>Market</v-list-item-title></v-list-item>
|
||
<v-list-item @click="limitType = 'Limit'"><v-list-item-title>Limit</v-list-item-title></v-list-item>
|
||
<v-divider></v-divider>
|
||
<v-list-item @click="openMergeDialog"><v-list-item-title>Merge</v-list-item-title></v-list-item>
|
||
<v-list-item @click="openSplitDialog"><v-list-item-title>Split</v-list-item-title></v-list-item>
|
||
</v-list>
|
||
</v-menu>
|
||
</div>
|
||
<template v-if="isMarketMode">
|
||
<template v-if="balance > 0">
|
||
<div class="price-options hide-in-mobile-sheet">
|
||
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
||
</div>
|
||
<div class="total-section">
|
||
<template v-if="activeTab === 'buy'">
|
||
<div class="total-row"><span class="label">Total</span><span class="total-value">${{ totalPrice }}</span></div>
|
||
<div class="total-row"><span class="label">To win</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> $20</span></div>
|
||
</template>
|
||
<template v-else>
|
||
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
||
</template>
|
||
</div>
|
||
<v-btn class="action-btn" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
||
</template>
|
||
<template v-else>
|
||
<div class="price-options hide-in-mobile-sheet">
|
||
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
||
</div>
|
||
<div class="input-group">
|
||
<div class="amount-header">
|
||
<div><span class="label amount-label">Amount</span><span class="balance-label">Balance ${{ balance.toFixed(2) }}</span></div>
|
||
<div class="amount-value">${{ amount.toFixed(2) }}</div>
|
||
</div>
|
||
<div class="amount-buttons">
|
||
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
|
||
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
|
||
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
|
||
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
|
||
</div>
|
||
</div>
|
||
<v-btn class="deposit-btn" @click="deposit">Deposit</v-btn>
|
||
</template>
|
||
</template>
|
||
<template v-else>
|
||
<div class="price-options hide-in-mobile-sheet">
|
||
<v-btn class="yes-btn" :class="{ active: selectedOption === 'yes' }" text @click="handleOptionChange('yes')">Yes {{ yesPriceCents }}¢</v-btn>
|
||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ noPriceCents }}¢</v-btn>
|
||
</div>
|
||
<div class="input-group limit-price-group">
|
||
<div class="limit-price-header">
|
||
<span class="label">Limit Price</span>
|
||
<div class="price-input">
|
||
<v-btn class="adjust-btn" icon @click="decreasePrice"><v-icon>mdi-minus</v-icon></v-btn>
|
||
<v-text-field v-model.number="limitPrice" type="number" min="0.01" step="0.01" class="price-input-field" hide-details density="compact"></v-text-field>
|
||
<v-btn class="adjust-btn" icon @click="increasePrice"><v-icon>mdi-plus</v-icon></v-btn>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="input-group shares-group">
|
||
<div class="shares-header">
|
||
<span class="label">Shares</span>
|
||
<div class="shares-input">
|
||
<v-text-field v-model.number="shares" type="number" min="0" class="shares-input-field" hide-details density="compact"></v-text-field>
|
||
</div>
|
||
</div>
|
||
<div v-if="activeTab === 'buy'" class="shares-buttons">
|
||
<v-btn class="share-btn" @click="adjustShares(-100)">-100</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(-10)">-10</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(10)">+10</v-btn>
|
||
<v-btn class="share-btn" @click="adjustShares(100)">+100</v-btn>
|
||
</div>
|
||
<div v-else class="shares-buttons">
|
||
<v-btn class="share-btn" @click="setSharesPercentage(25)">25%</v-btn>
|
||
<v-btn class="share-btn" @click="setSharesPercentage(50)">50%</v-btn>
|
||
<v-btn class="share-btn" @click="setSharesPercentage(100)">Max</v-btn>
|
||
</div>
|
||
</div>
|
||
<div class="input-group expiration-group">
|
||
<div class="expiration-header">
|
||
<span class="label">Set expiration</span>
|
||
<v-switch v-model="expirationEnabled" class="expiration-switch" hide-details color="primary"></v-switch>
|
||
</div>
|
||
<v-select
|
||
v-if="expirationEnabled"
|
||
v-model="expirationTime"
|
||
:items="expirationOptions"
|
||
class="expiration-select"
|
||
hide-details
|
||
density="compact"
|
||
></v-select>
|
||
</div>
|
||
<div class="total-section">
|
||
<template v-if="activeTab === 'buy'">
|
||
<div class="total-row"><span class="label">Total</span><span class="total-value">${{ totalPrice }}</span></div>
|
||
<div class="total-row"><span class="label">To win</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> $20</span></div>
|
||
</template>
|
||
<template v-else>
|
||
<div class="total-row"><span class="label">You'll receive</span><span class="to-win-value"><v-icon size="16" color="green">mdi-currency-usd</v-icon> ${{ totalPrice }}</span></div>
|
||
</template>
|
||
</div>
|
||
<v-btn class="action-btn" @click="submitOrder">{{ actionButtonText }}</v-btn>
|
||
</template>
|
||
</div>
|
||
</v-sheet>
|
||
</v-bottom-sheet>
|
||
</template>
|
||
|
||
<!-- Merge shares dialog(与桌面/移动端分支并列,始终挂载才能响应 openMergeDialog) -->
|
||
<v-dialog v-model="mergeDialogOpen" max-width="440" persistent content-class="merge-dialog" transition="dialog-transition">
|
||
<v-card class="merge-dialog-card" rounded="lg">
|
||
<div class="merge-dialog-header">
|
||
<h3 class="merge-dialog-title">Merge shares</h3>
|
||
<v-btn icon variant="text" size="small" class="merge-dialog-close" @click="mergeDialogOpen = false">
|
||
<v-icon>mdi-close</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
<v-card-text class="merge-dialog-body">
|
||
<p class="merge-dialog-desc">
|
||
Merge a share of Yes and No to get 1 USDC. You can do this to save cost when trying to get rid of a position.
|
||
</p>
|
||
<div class="merge-amount-row">
|
||
<label class="merge-amount-label">Amount</label>
|
||
<v-text-field
|
||
v-model.number="mergeAmount"
|
||
type="number"
|
||
min="0"
|
||
hide-details
|
||
density="compact"
|
||
variant="outlined"
|
||
class="merge-amount-input"
|
||
/>
|
||
</div>
|
||
<p class="merge-available">
|
||
Available shares: {{ availableMergeShares }}
|
||
<button type="button" class="merge-max-link" @click="setMergeMax">Max</button>
|
||
</p>
|
||
<p v-if="!props.market?.marketId" class="merge-no-market">Please select a market first (e.g. click Buy Yes/No on a market).</p>
|
||
<p v-if="mergeError" class="merge-error">{{ mergeError }}</p>
|
||
</v-card-text>
|
||
<v-card-actions class="merge-dialog-actions">
|
||
<v-btn
|
||
color="primary"
|
||
variant="flat"
|
||
block
|
||
class="merge-submit-btn"
|
||
:loading="mergeLoading"
|
||
:disabled="mergeLoading || mergeAmount <= 0"
|
||
@click="submitMerge"
|
||
>
|
||
Merge Shares
|
||
</v-btn>
|
||
</v-card-actions>
|
||
</v-card>
|
||
</v-dialog>
|
||
|
||
<!-- Split dialog:对接 /PmMarket/split -->
|
||
<v-dialog v-model="splitDialogOpen" max-width="440" persistent content-class="split-dialog" transition="dialog-transition">
|
||
<v-card class="split-dialog-card" rounded="lg">
|
||
<div class="split-dialog-header">
|
||
<h3 class="split-dialog-title">Split</h3>
|
||
<v-btn icon variant="text" size="small" class="split-dialog-close" @click="splitDialogOpen = false">
|
||
<v-icon>mdi-close</v-icon>
|
||
</v-btn>
|
||
</div>
|
||
<v-card-text class="split-dialog-body">
|
||
<p class="split-dialog-desc">
|
||
Use USDC to get one share of Yes and one share of No for this market. 1 USDC ≈ 1 complete set.
|
||
</p>
|
||
<div class="split-amount-row">
|
||
<label class="split-amount-label">Amount (USDC)</label>
|
||
<v-text-field
|
||
v-model.number="splitAmount"
|
||
type="number"
|
||
min="0"
|
||
step="1"
|
||
hide-details
|
||
density="compact"
|
||
variant="outlined"
|
||
class="split-amount-input"
|
||
/>
|
||
</div>
|
||
<p v-if="!props.market?.marketId" class="split-no-market">Please select a market first (e.g. click Buy Yes/No on a market).</p>
|
||
<p v-if="splitError" class="split-error">{{ splitError }}</p>
|
||
</v-card-text>
|
||
<v-card-actions class="split-dialog-actions">
|
||
<v-btn
|
||
color="primary"
|
||
variant="flat"
|
||
block
|
||
class="split-submit-btn"
|
||
:loading="splitLoading"
|
||
:disabled="splitLoading || splitAmount <= 0"
|
||
@click="submitSplit"
|
||
>
|
||
Split
|
||
</v-btn>
|
||
</v-card-actions>
|
||
</v-card>
|
||
</v-dialog>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, watch, onMounted } from 'vue'
|
||
import { useDisplay } from 'vuetify'
|
||
import { useUserStore } from '../stores/user'
|
||
import { pmMarketMerge, pmMarketSplit } from '../api/market'
|
||
|
||
const { mobile } = useDisplay()
|
||
const userStore = useUserStore()
|
||
|
||
export interface TradeMarketPayload {
|
||
marketId?: string
|
||
yesPrice: number
|
||
noPrice: number
|
||
title?: string
|
||
}
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
initialOption?: 'yes' | 'no'
|
||
embeddedInSheet?: boolean
|
||
/** 从外部传入的市场数据(如 EventMarkets 点击 Yes/No 传入),yesPrice/noPrice 为 0–1 */
|
||
market?: TradeMarketPayload
|
||
}>(),
|
||
{ initialOption: undefined, embeddedInSheet: false, market: undefined }
|
||
)
|
||
|
||
// 移动端:底部栏与弹出层
|
||
const sheetOpen = ref(false)
|
||
|
||
// Merge shares dialog:对接 /PmMarket/merge
|
||
const mergeDialogOpen = ref(false)
|
||
const mergeAmount = ref(0)
|
||
const availableMergeShares = ref(0)
|
||
const mergeLoading = ref(false)
|
||
const mergeError = ref('')
|
||
function openMergeDialog() {
|
||
mergeAmount.value = 0
|
||
mergeError.value = ''
|
||
mergeDialogOpen.value = true
|
||
}
|
||
function setMergeMax() {
|
||
mergeAmount.value = availableMergeShares.value
|
||
}
|
||
async function submitMerge() {
|
||
const marketId = props.market?.marketId
|
||
if (mergeAmount.value <= 0) return
|
||
if (!marketId) return
|
||
mergeLoading.value = true
|
||
mergeError.value = ''
|
||
try {
|
||
const res = await pmMarketMerge(
|
||
{ marketID: marketId, amount: String(mergeAmount.value) },
|
||
{ headers: userStore.getAuthHeaders() }
|
||
)
|
||
if (res.code === 0 || res.code === 200) {
|
||
mergeDialogOpen.value = false
|
||
userStore.fetchUsdcBalance()
|
||
} else {
|
||
mergeError.value = res.msg || 'Merge failed'
|
||
}
|
||
} catch (e) {
|
||
mergeError.value = e instanceof Error ? e.message : 'Request failed'
|
||
} finally {
|
||
mergeLoading.value = false
|
||
}
|
||
}
|
||
|
||
// Split dialog:对接测试服务器 /PmMarket/split
|
||
const splitDialogOpen = ref(false)
|
||
const splitAmount = ref(0)
|
||
const splitLoading = ref(false)
|
||
const splitError = ref('')
|
||
function openSplitDialog() {
|
||
splitAmount.value = splitAmount.value > 0 ? splitAmount.value : 1
|
||
splitError.value = ''
|
||
splitDialogOpen.value = true
|
||
}
|
||
async function submitSplit() {
|
||
const marketId = props.market?.marketId
|
||
if (splitAmount.value <= 0) return
|
||
if (!marketId) return // 无市场时仅依赖模板中的 split-no-market 提示,不重复设置 splitError
|
||
splitLoading.value = true
|
||
splitError.value = ''
|
||
try {
|
||
const res = await pmMarketSplit(
|
||
{ marketID: marketId, usdcAmount: String(splitAmount.value) },
|
||
{ headers: userStore.getAuthHeaders() }
|
||
)
|
||
if (res.code === 0 || res.code === 200) {
|
||
splitDialogOpen.value = false
|
||
} else {
|
||
splitError.value = res.msg || 'Split failed'
|
||
}
|
||
} catch (e) {
|
||
splitError.value = e instanceof Error ? e.message : 'Request failed'
|
||
} finally {
|
||
splitLoading.value = false
|
||
}
|
||
}
|
||
|
||
const yesPriceCents = computed(() =>
|
||
props.market ? Math.round(props.market.yesPrice * 100) : 19
|
||
)
|
||
const noPriceCents = computed(() =>
|
||
props.market ? Math.round(props.market.noPrice * 100) : 82
|
||
)
|
||
|
||
function openSheet(option: 'yes' | 'no') {
|
||
handleOptionChange(option)
|
||
sheetOpen.value = true
|
||
}
|
||
|
||
// State
|
||
const activeTab = ref('buy')
|
||
const limitType = ref('Limit')
|
||
const expirationEnabled = ref(false)
|
||
const selectedOption = ref<'yes' | 'no'>(props.initialOption ?? 'no')
|
||
const limitPrice = ref(0.82) // 初始限价,单位:美元
|
||
const shares = ref(20) // 初始份额
|
||
const expirationTime = ref('5m') // 初始过期时间
|
||
const expirationOptions = ref(['5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d']) // 过期时间选项
|
||
|
||
// Market mode state
|
||
const isMarketMode = computed(() => limitType.value === 'Market')
|
||
const amount = ref(0) // Market mode amount
|
||
const balance = ref(0) // Market mode balance
|
||
|
||
// Emits
|
||
const emit = defineEmits<{
|
||
optionChange: [option: 'yes' | 'no']
|
||
submit: [payload: {
|
||
side: 'buy' | 'sell'
|
||
option: 'yes' | 'no'
|
||
limitPrice: number
|
||
shares: number
|
||
expirationEnabled: boolean
|
||
expirationTime: string
|
||
marketId?: string
|
||
}]
|
||
}>()
|
||
|
||
// Computed properties
|
||
const currentPrice = computed(() => {
|
||
return `${(limitPrice.value * 100).toFixed(0)}¢`
|
||
})
|
||
|
||
const totalPrice = computed(() => {
|
||
return (limitPrice.value * shares.value).toFixed(2)
|
||
})
|
||
|
||
const actionButtonText = computed(() => {
|
||
return `${activeTab.value} ${selectedOption.value.charAt(0).toUpperCase() + selectedOption.value.slice(1)}`
|
||
})
|
||
|
||
function applyInitialOption(option: 'yes' | 'no') {
|
||
selectedOption.value = option
|
||
syncLimitPriceFromMarket()
|
||
}
|
||
|
||
/** 根据当前 props.market 与 selectedOption 同步 limitPrice(组件显示或 market 更新时调用) */
|
||
function syncLimitPriceFromMarket() {
|
||
const yesP = props.market?.yesPrice ?? 0.19
|
||
const noP = props.market?.noPrice ?? 0.82
|
||
limitPrice.value = selectedOption.value === 'yes' ? yesP : noP
|
||
}
|
||
|
||
onMounted(() => {
|
||
if (props.initialOption) applyInitialOption(props.initialOption)
|
||
else if (props.market) syncLimitPriceFromMarket()
|
||
})
|
||
watch(() => props.initialOption, (option) => {
|
||
if (option) applyInitialOption(option)
|
||
}, { immediate: true })
|
||
|
||
watch(() => props.market, (m) => {
|
||
if (m) {
|
||
if (props.initialOption) applyInitialOption(props.initialOption)
|
||
else syncLimitPriceFromMarket()
|
||
}
|
||
}, { deep: true })
|
||
|
||
// Methods
|
||
const handleOptionChange = (option: 'yes' | 'no') => {
|
||
selectedOption.value = option
|
||
const yesP = props.market?.yesPrice ?? 0.19
|
||
const noP = props.market?.noPrice ?? 0.82
|
||
limitPrice.value = option === 'yes' ? yesP : noP
|
||
emit('optionChange', option)
|
||
}
|
||
|
||
// 限价调整方法
|
||
const decreasePrice = () => {
|
||
limitPrice.value = Math.max(0.01, limitPrice.value - 0.01)
|
||
}
|
||
|
||
const increasePrice = () => {
|
||
limitPrice.value += 0.01
|
||
}
|
||
|
||
// 份额调整方法
|
||
const adjustShares = (amount: number) => {
|
||
shares.value = Math.max(0, shares.value + amount)
|
||
}
|
||
|
||
// 份额百分比调整方法(仅在Sell模式下使用)
|
||
const setSharesPercentage = (percentage: number) => {
|
||
// 假设最大份额为100,实际应用中可能需要根据用户的可用份额来计算
|
||
const maxShares = 100
|
||
shares.value = Math.round((maxShares * percentage) / 100)
|
||
}
|
||
|
||
// Market mode methods
|
||
const adjustAmount = (value: number) => {
|
||
amount.value += value
|
||
if (amount.value < 0) amount.value = 0
|
||
}
|
||
|
||
const setMaxAmount = () => {
|
||
amount.value = balance.value
|
||
}
|
||
|
||
const deposit = () => {
|
||
console.log('Depositing amount:', amount.value)
|
||
// 实际应用中,这里应该调用存款API
|
||
}
|
||
|
||
// 提交订单(含 Set expiration)
|
||
function submitOrder() {
|
||
emit('submit', {
|
||
side: activeTab.value as 'buy' | 'sell',
|
||
option: selectedOption.value,
|
||
limitPrice: limitPrice.value,
|
||
shares: shares.value,
|
||
expirationEnabled: expirationEnabled.value,
|
||
expirationTime: expirationTime.value,
|
||
...(props.market?.marketId != null && { marketId: props.market.marketId }),
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.trade-component {
|
||
width: 100%;
|
||
max-width: 400px;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.buy-sell-tabs {
|
||
flex: 1;
|
||
}
|
||
|
||
.minimal-tabs {
|
||
--tabs-height: 32px !important;
|
||
margin: 0 !important;
|
||
}
|
||
|
||
.minimal-tab {
|
||
height: 32px !important;
|
||
min-height: 32px !important;
|
||
padding: 4px 12px !important;
|
||
font-size: 14px !important;
|
||
}
|
||
|
||
/* 移除了.tab-btn样式,使用v-tabs的默认样式 */
|
||
|
||
.limit-btn {
|
||
font-size: 14px;
|
||
text-transform: none;
|
||
color: #666666;
|
||
}
|
||
|
||
.more-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
}
|
||
|
||
.price-options {
|
||
display: flex;
|
||
gap: 10px;
|
||
padding: 12px;
|
||
}
|
||
|
||
.yes-btn {
|
||
flex: 1;
|
||
background-color: #f0f0f0;
|
||
border-radius: 4px;
|
||
text-transform: none;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.yes-btn.active {
|
||
background-color: #b8e0b8;
|
||
color: #006600;
|
||
}
|
||
|
||
.no-btn {
|
||
flex: 1;
|
||
background-color: #f0f0f0;
|
||
color: #000000;
|
||
border-radius: 4px;
|
||
text-transform: none;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.no-btn.active {
|
||
background-color: #cc0000;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.input-group {
|
||
padding: 12px;
|
||
border-top: 1px solid #e0e0e0;
|
||
}
|
||
|
||
/* Reduce height for Set Expiration section */
|
||
.expiration-group {
|
||
padding: 8px 12px !important;
|
||
margin: 0 !important;
|
||
}
|
||
|
||
/* Limit Price header flex layout */
|
||
.limit-price-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
|
||
/* Adjust label margin for flex layout */
|
||
.limit-price-header .label {
|
||
margin-bottom: 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Adjust price input width for flex layout */
|
||
.limit-price-header .price-input {
|
||
flex: 1;
|
||
max-width: 200px;
|
||
margin-left: 12px;
|
||
}
|
||
|
||
.label {
|
||
font-size: 14px;
|
||
color: #666666;
|
||
margin-bottom: 6px;
|
||
display: block;
|
||
}
|
||
|
||
.price-input {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
background-color: #f8f8f8;
|
||
border-radius: 4px;
|
||
padding: 6px 10px;
|
||
}
|
||
|
||
.adjust-btn {
|
||
color: #666666;
|
||
background-color: transparent !important;
|
||
border: none !important;
|
||
box-shadow: none !important;
|
||
}
|
||
|
||
.price-value {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.price-input-field {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
background-color: transparent;
|
||
border: none;
|
||
box-shadow: none;
|
||
max-width: 100px;
|
||
}
|
||
|
||
/* Shares header flex layout */
|
||
.shares-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
/* Adjust label margin for flex layout */
|
||
.shares-header .label {
|
||
margin-bottom: 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Adjust shares input width for flex layout */
|
||
.shares-header .shares-input {
|
||
flex: 1;
|
||
max-width: 200px;
|
||
margin-left: 12px;
|
||
text-align: right;
|
||
}
|
||
|
||
.shares-input {
|
||
background-color: #f8f8f8;
|
||
border-radius: 4px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.shares-value {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
text-align: right;
|
||
}
|
||
|
||
.shares-input-field {
|
||
width: 100%;
|
||
text-align: right;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
background-color: transparent;
|
||
border: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.shares-buttons {
|
||
display: flex;
|
||
gap: 6px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.share-btn {
|
||
flex: 1;
|
||
background-color: #f0f0f0;
|
||
border-radius: 4px;
|
||
text-transform: none;
|
||
font-size: 12px;
|
||
padding: 4px;
|
||
}
|
||
|
||
.matching-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 12px;
|
||
color: #3d8b40;
|
||
}
|
||
|
||
.expiration-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
|
||
.expiration-switch {
|
||
margin: 0;
|
||
}
|
||
|
||
.expiration-select {
|
||
margin-top: 8px;
|
||
width: 100%;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.total-section {
|
||
padding: 12px;
|
||
border-top: 1px solid #e0e0e0;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
|
||
.total-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.total-value {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
color: #0066cc;
|
||
}
|
||
|
||
.to-win-value {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
color: #3d8b40;
|
||
}
|
||
|
||
.action-btn {
|
||
width: 100%;
|
||
background-color: #0066cc;
|
||
color: #ffffff;
|
||
border-radius: 0;
|
||
padding: 16px;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
text-transform: none;
|
||
}
|
||
|
||
/* Market Mode Styles */
|
||
.amount-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.amount-label {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.balance-label {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
display: block;
|
||
}
|
||
|
||
.amount-value {
|
||
font-size: 24px;
|
||
font-weight: 500;
|
||
color: #000000;
|
||
}
|
||
|
||
.amount-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.amount-btn {
|
||
flex: 1;
|
||
background-color: #f0f0f0;
|
||
border-radius: 4px;
|
||
text-transform: none;
|
||
font-size: 14px;
|
||
padding: 8px;
|
||
}
|
||
|
||
.deposit-btn {
|
||
width: 100%;
|
||
background-color: #0066cc;
|
||
color: #ffffff;
|
||
border-radius: 0;
|
||
padding: 16px;
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
text-transform: none;
|
||
}
|
||
|
||
/* 移动端底部交易栏(红框样式) */
|
||
.mobile-trade-bar-spacer {
|
||
height: 72px;
|
||
}
|
||
|
||
.mobile-trade-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 100;
|
||
display: flex;
|
||
align-items: stretch;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 12px 16px;
|
||
padding-bottom: max(12px, env(safe-area-inset-bottom));
|
||
background: #fff;
|
||
border-top: 1px solid #eee;
|
||
}
|
||
|
||
.mobile-bar-btn {
|
||
border: none;
|
||
border-radius: 9999px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.mobile-bar-yes {
|
||
flex: 1;
|
||
min-width: 0;
|
||
background: #16a34a;
|
||
color: #fff;
|
||
padding: 14px 16px;
|
||
}
|
||
|
||
.mobile-bar-no {
|
||
flex: 1;
|
||
min-width: 0;
|
||
background: #dc2626;
|
||
color: #fff;
|
||
padding: 14px 16px;
|
||
}
|
||
|
||
.trade-sheet-paper {
|
||
padding: 0;
|
||
max-height: 85vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.trade-sheet-inner {
|
||
box-shadow: none;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* 手机端底部弹层内不显示:Limit 下拉、Yes/No 按钮 */
|
||
.trade-sheet-inner .hide-in-mobile-sheet {
|
||
display: none !important;
|
||
}
|
||
|
||
/* 响应式调整 - 小屏幕设备 */
|
||
@media (max-width: 600px) {
|
||
.header {
|
||
padding: 10px;
|
||
}
|
||
|
||
.price-options {
|
||
padding: 10px;
|
||
}
|
||
|
||
.input-group {
|
||
padding: 10px;
|
||
}
|
||
|
||
.total-section {
|
||
padding: 10px;
|
||
}
|
||
|
||
.label {
|
||
font-size: 13px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.price-value,
|
||
.shares-value,
|
||
.total-value,
|
||
.to-win-value {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 15px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.share-btn {
|
||
font-size: 11px;
|
||
padding: 3px;
|
||
}
|
||
|
||
.amount-label {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.amount-value {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.amount-btn {
|
||
font-size: 13px;
|
||
padding: 6px;
|
||
}
|
||
|
||
.deposit-btn {
|
||
padding: 15px;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
/* Merge shares dialog */
|
||
.merge-dialog-card {
|
||
padding: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.merge-dialog-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20px 20px 0;
|
||
}
|
||
|
||
.merge-dialog-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
margin: 0;
|
||
}
|
||
|
||
.merge-dialog-close {
|
||
color: #6b7280;
|
||
}
|
||
|
||
.merge-dialog-body {
|
||
padding: 16px 20px 8px;
|
||
}
|
||
|
||
.merge-dialog-desc {
|
||
font-size: 14px;
|
||
color: #374151;
|
||
line-height: 1.5;
|
||
margin: 0 0 20px;
|
||
}
|
||
|
||
.merge-amount-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.merge-amount-label {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
min-width: 60px;
|
||
}
|
||
|
||
.merge-amount-input {
|
||
flex: 1;
|
||
max-width: 160px;
|
||
}
|
||
|
||
.merge-amount-input :deep(.v-field) {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.merge-available {
|
||
font-size: 13px;
|
||
color: #6b7280;
|
||
margin: 0;
|
||
}
|
||
|
||
.merge-max-link {
|
||
color: #2563eb;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
margin-left: 4px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.merge-max-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.merge-no-market,
|
||
.merge-error {
|
||
font-size: 13px;
|
||
margin: 8px 0 0;
|
||
}
|
||
|
||
.merge-no-market {
|
||
color: #6b7280;
|
||
}
|
||
|
||
.merge-error {
|
||
color: #dc2626;
|
||
}
|
||
|
||
.merge-dialog-actions {
|
||
padding: 16px 20px 20px;
|
||
padding-top: 8px;
|
||
}
|
||
|
||
.merge-submit-btn {
|
||
text-transform: none;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Split dialog */
|
||
.split-dialog-card {
|
||
padding: 0;
|
||
overflow: hidden;
|
||
}
|
||
.split-dialog-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16px 20px 0;
|
||
}
|
||
.split-dialog-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
margin: 0;
|
||
}
|
||
.split-dialog-close {
|
||
color: #6b7280;
|
||
}
|
||
.split-dialog-body {
|
||
padding: 16px 20px 8px;
|
||
}
|
||
.split-dialog-desc {
|
||
font-size: 14px;
|
||
color: #374151;
|
||
line-height: 1.5;
|
||
margin: 0 0 16px 0;
|
||
}
|
||
.split-amount-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.split-amount-label {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
flex-shrink: 0;
|
||
}
|
||
.split-amount-input {
|
||
flex: 1;
|
||
max-width: 160px;
|
||
}
|
||
.split-amount-input :deep(.v-field) {
|
||
font-size: 14px;
|
||
}
|
||
.split-no-market,
|
||
.split-error {
|
||
font-size: 13px;
|
||
margin: 8px 0 0;
|
||
}
|
||
.split-no-market {
|
||
color: #6b7280;
|
||
}
|
||
.split-error {
|
||
color: #dc2626;
|
||
}
|
||
.split-dialog-actions {
|
||
padding: 16px 20px 20px;
|
||
padding-top: 8px;
|
||
}
|
||
.split-submit-btn {
|
||
text-transform: none;
|
||
font-weight: 600;
|
||
}
|
||
</style> |