新增:点击卡片的YES/NO弹出交易框
This commit is contained in:
parent
14c4ec322f
commit
642f7990dc
@ -31,12 +31,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options Section -->
|
||||
<!-- Options Section:点击 Yes/No 弹出交易框,阻止冒泡不触发卡片跳转 -->
|
||||
<div class="options-section">
|
||||
<v-btn class="option-yes" :color="'#e6f9e6'" :rounded="'sm'" :text="true">
|
||||
<v-btn
|
||||
class="option-yes"
|
||||
:color="'#e6f9e6'"
|
||||
:rounded="'sm'"
|
||||
:text="true"
|
||||
@click.stop="openTrade('yes')"
|
||||
>
|
||||
<span class="option-text-yes">Yes</span>
|
||||
</v-btn>
|
||||
<v-btn class="option-no" :color="'#ffe6e6'" :rounded="'sm'" :text="true">
|
||||
<v-btn
|
||||
class="option-no"
|
||||
:color="'#ffe6e6'"
|
||||
:rounded="'sm'"
|
||||
:text="true"
|
||||
@click.stop="openTrade('no')"
|
||||
>
|
||||
<span class="option-text-no">No</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
@ -58,6 +70,9 @@ import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const emit = defineEmits<{
|
||||
openTrade: [side: 'yes' | 'no', market?: { id: string; title: string }]
|
||||
}>()
|
||||
|
||||
const props = defineProps({
|
||||
marketTitle: {
|
||||
@ -114,6 +129,11 @@ const navigateToDetail = () => {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 点击 Yes/No 时弹出交易框(由父组件监听 openTrade 并打开弹层)
|
||||
function openTrade(side: 'yes' | 'no') {
|
||||
emit('openTrade', side, { id: props.id, title: props.marketTitle })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -274,7 +274,132 @@
|
||||
</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><v-list-item-title>Merge</v-list-item-title></v-list-item>
|
||||
<v-list-item><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 {{ selectedOption === 'yes' ? '19¢' : '18¢' }}</v-btn>
|
||||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ selectedOption === 'no' ? '82¢' : '81¢' }}</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 {{ selectedOption === 'yes' ? '19¢' : '18¢' }}</v-btn>
|
||||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ selectedOption === 'no' ? '82¢' : '81¢' }}</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 {{ selectedOption === 'yes' ? '19¢' : '18¢' }}</v-btn>
|
||||
<v-btn class="no-btn" :class="{ active: selectedOption === 'no' }" text @click="handleOptionChange('no')">No {{ selectedOption === 'no' ? '82¢' : '81¢' }}</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">
|
||||
@ -427,11 +552,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const { mobile } = useDisplay()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{ initialOption?: 'yes' | 'no'; embeddedInSheet?: boolean }>(),
|
||||
{ initialOption: undefined, embeddedInSheet: false }
|
||||
)
|
||||
|
||||
// 移动端:底部栏与弹出层
|
||||
const sheetOpen = ref(false)
|
||||
|
||||
@ -447,7 +577,7 @@ function openSheet(option: 'yes' | 'no') {
|
||||
const activeTab = ref('buy')
|
||||
const limitType = ref('Limit')
|
||||
const expirationEnabled = ref(false)
|
||||
const selectedOption = ref('no') // 'yes' or 'no'
|
||||
const selectedOption = ref<'yes' | 'no'>(props.initialOption ?? 'no')
|
||||
const limitPrice = ref(0.82) // 初始限价,单位:美元
|
||||
const shares = ref(20) // 初始份额
|
||||
const expirationTime = ref('5m') // 初始过期时间
|
||||
@ -484,6 +614,22 @@ const actionButtonText = computed(() => {
|
||||
return `${activeTab.value} ${selectedOption.value.charAt(0).toUpperCase() + selectedOption.value.slice(1)}`
|
||||
})
|
||||
|
||||
function applyInitialOption(option: 'yes' | 'no') {
|
||||
selectedOption.value = option
|
||||
if (option === 'yes') {
|
||||
limitPrice.value = 0.19
|
||||
} else {
|
||||
limitPrice.value = 0.82
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.initialOption) applyInitialOption(props.initialOption)
|
||||
})
|
||||
watch(() => props.initialOption, (option) => {
|
||||
if (option) applyInitialOption(option)
|
||||
}, { immediate: true })
|
||||
|
||||
// Methods
|
||||
const handleOptionChange = (option: 'yes' | 'no') => {
|
||||
selectedOption.value = option
|
||||
|
||||
@ -13,7 +13,12 @@
|
||||
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
|
||||
<div class="pull-to-refresh-inner">
|
||||
<div class="home-list">
|
||||
<MarketCard v-for="id in listLength" :key="id" :id="String(id)" />
|
||||
<MarketCard
|
||||
v-for="id in listLength"
|
||||
:key="id"
|
||||
:id="String(id)"
|
||||
@open-trade="onCardOpenTrade"
|
||||
/>
|
||||
</div>
|
||||
<div class="load-more-footer">
|
||||
<div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" />
|
||||
@ -35,8 +40,35 @@
|
||||
</div>
|
||||
</div>
|
||||
</v-pull-to-refresh>
|
||||
</div>
|
||||
</v-container>
|
||||
</div>
|
||||
|
||||
<!-- PC:对话框;手机:底部 sheet,直接显示交易表单 -->
|
||||
<v-dialog
|
||||
v-if="!isMobile"
|
||||
v-model="tradeDialogOpen"
|
||||
max-width="420"
|
||||
scrollable
|
||||
content-class="trade-dialog trade-dialog--bare"
|
||||
transition="dialog-transition"
|
||||
@click:outside="tradeDialogOpen = false"
|
||||
>
|
||||
<TradeComponent
|
||||
:key="`trade-${tradeDialogMarket?.id}-${tradeDialogSide}`"
|
||||
:initial-option="tradeDialogSide"
|
||||
/>
|
||||
</v-dialog>
|
||||
<v-bottom-sheet
|
||||
v-else
|
||||
v-model="tradeDialogOpen"
|
||||
content-class="trade-bottom-sheet"
|
||||
>
|
||||
<TradeComponent
|
||||
:key="`trade-${tradeDialogMarket?.id}-${tradeDialogSide}`"
|
||||
:initial-option="tradeDialogSide"
|
||||
embedded-in-sheet
|
||||
/>
|
||||
</v-bottom-sheet>
|
||||
</v-container>
|
||||
|
||||
<footer class="home-footer">
|
||||
<div class="footer-inner">
|
||||
@ -114,8 +146,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import MarketCard from '../components/MarketCard.vue'
|
||||
import TradeComponent from '../components/TradeComponent.vue'
|
||||
|
||||
const { mobile } = useDisplay()
|
||||
const isMobile = computed(() => mobile.value)
|
||||
|
||||
const activeTab = ref('overview')
|
||||
|
||||
@ -126,7 +163,16 @@ const maxItems = 50
|
||||
const listLength = ref(INITIAL_COUNT)
|
||||
const loadingMore = ref(false)
|
||||
const footerLang = ref('English')
|
||||
const tradeDialogOpen = ref(false)
|
||||
const tradeDialogSide = ref<'yes' | 'no'>('yes')
|
||||
const tradeDialogMarket = ref<{ id: string; title: string } | null>(null)
|
||||
const scrollRef = ref<HTMLElement | null>(null)
|
||||
|
||||
function onCardOpenTrade(side: 'yes' | 'no', market?: { id: string; title: string }) {
|
||||
tradeDialogSide.value = side
|
||||
tradeDialogMarket.value = market ?? null
|
||||
tradeDialogOpen.value = true
|
||||
}
|
||||
const sentinelRef = ref<HTMLElement | null>(null)
|
||||
let observer: IntersectionObserver | null = null
|
||||
|
||||
@ -269,6 +315,18 @@ onUnmounted(() => {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.trade-dialog--bare :deep(.v-overlay__content) {
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
.trade-dialog--bare :deep(.v-card) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.trade-bottom-sheet :deep(.v-overlay__content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 大屏最多 4 列,避免过宽时出现 5 列以上 */
|
||||
@media (min-width: 1320px) {
|
||||
.home-list {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user