新增:点击卡片的YES/NO弹出交易框
This commit is contained in:
parent
14c4ec322f
commit
642f7990dc
@ -31,12 +31,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Options Section -->
|
<!-- Options Section:点击 Yes/No 弹出交易框,阻止冒泡不触发卡片跳转 -->
|
||||||
<div class="options-section">
|
<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>
|
<span class="option-text-yes">Yes</span>
|
||||||
</v-btn>
|
</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>
|
<span class="option-text-no">No</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
@ -58,6 +70,9 @@ import { computed } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
openTrade: [side: 'yes' | 'no', market?: { id: string; title: string }]
|
||||||
|
}>()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
marketTitle: {
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -274,7 +274,132 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-card>
|
</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>
|
<template v-else>
|
||||||
<div class="mobile-trade-bar-spacer" aria-hidden="true"></div>
|
<div class="mobile-trade-bar-spacer" aria-hidden="true"></div>
|
||||||
<div class="mobile-trade-bar">
|
<div class="mobile-trade-bar">
|
||||||
@ -427,11 +552,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
const { mobile } = useDisplay()
|
const { mobile } = useDisplay()
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{ initialOption?: 'yes' | 'no'; embeddedInSheet?: boolean }>(),
|
||||||
|
{ initialOption: undefined, embeddedInSheet: false }
|
||||||
|
)
|
||||||
|
|
||||||
// 移动端:底部栏与弹出层
|
// 移动端:底部栏与弹出层
|
||||||
const sheetOpen = ref(false)
|
const sheetOpen = ref(false)
|
||||||
|
|
||||||
@ -447,7 +577,7 @@ function openSheet(option: 'yes' | 'no') {
|
|||||||
const activeTab = ref('buy')
|
const activeTab = ref('buy')
|
||||||
const limitType = ref('Limit')
|
const limitType = ref('Limit')
|
||||||
const expirationEnabled = ref(false)
|
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 limitPrice = ref(0.82) // 初始限价,单位:美元
|
||||||
const shares = ref(20) // 初始份额
|
const shares = ref(20) // 初始份额
|
||||||
const expirationTime = ref('5m') // 初始过期时间
|
const expirationTime = ref('5m') // 初始过期时间
|
||||||
@ -484,6 +614,22 @@ const actionButtonText = computed(() => {
|
|||||||
return `${activeTab.value} ${selectedOption.value.charAt(0).toUpperCase() + selectedOption.value.slice(1)}`
|
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
|
// Methods
|
||||||
const handleOptionChange = (option: 'yes' | 'no') => {
|
const handleOptionChange = (option: 'yes' | 'no') => {
|
||||||
selectedOption.value = option
|
selectedOption.value = option
|
||||||
|
|||||||
@ -13,7 +13,12 @@
|
|||||||
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
|
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
|
||||||
<div class="pull-to-refresh-inner">
|
<div class="pull-to-refresh-inner">
|
||||||
<div class="home-list">
|
<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>
|
||||||
<div class="load-more-footer">
|
<div class="load-more-footer">
|
||||||
<div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" />
|
<div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" />
|
||||||
@ -35,8 +40,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-pull-to-refresh>
|
</v-pull-to-refresh>
|
||||||
</div>
|
</div>
|
||||||
</v-container>
|
|
||||||
|
<!-- 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">
|
<footer class="home-footer">
|
||||||
<div class="footer-inner">
|
<div class="footer-inner">
|
||||||
@ -114,8 +146,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 MarketCard from '../components/MarketCard.vue'
|
||||||
|
import TradeComponent from '../components/TradeComponent.vue'
|
||||||
|
|
||||||
|
const { mobile } = useDisplay()
|
||||||
|
const isMobile = computed(() => mobile.value)
|
||||||
|
|
||||||
const activeTab = ref('overview')
|
const activeTab = ref('overview')
|
||||||
|
|
||||||
@ -126,7 +163,16 @@ const maxItems = 50
|
|||||||
const listLength = ref(INITIAL_COUNT)
|
const listLength = ref(INITIAL_COUNT)
|
||||||
const loadingMore = ref(false)
|
const loadingMore = ref(false)
|
||||||
const footerLang = ref('English')
|
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)
|
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)
|
const sentinelRef = ref<HTMLElement | null>(null)
|
||||||
let observer: IntersectionObserver | null = null
|
let observer: IntersectionObserver | null = null
|
||||||
|
|
||||||
@ -269,6 +315,18 @@ onUnmounted(() => {
|
|||||||
margin-top: 4px;
|
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 列以上 */
|
/* 大屏最多 4 列,避免过宽时出现 5 列以上 */
|
||||||
@media (min-width: 1320px) {
|
@media (min-width: 1320px) {
|
||||||
.home-list {
|
.home-list {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user