优化样式调整

This commit is contained in:
马丁 2026-05-23 12:30:02 +08:00
parent a21869d989
commit e4ecea471c
18 changed files with 375 additions and 397 deletions

View File

@ -10,6 +10,7 @@ import { storeToRefs } from 'pinia'
import { useSearchHistory } from './composables/useSearchHistory' import { useSearchHistory } from './composables/useSearchHistory'
import type { LocaleCode } from './plugins/i18n' import type { LocaleCode } from './plugins/i18n'
import Toast from './components/Toast.vue' import Toast from './components/Toast.vue'
import { getPmTagMain } from './api/category'
const route = useRoute() const route = useRoute()
const { t } = useI18n() const { t } = useI18n()
@ -19,7 +20,7 @@ const localeStore = useLocaleStore()
const menuStore = useMenuStore() const menuStore = useMenuStore()
const localeMenuOpen = ref(false) const localeMenuOpen = ref(false)
const { categoryLayers, layerActiveValues } = storeToRefs(menuStore) const { categoryTree, categoryLayers, layerActiveValues } = storeToRefs(menuStore)
const searchHistory = useSearchHistory() const searchHistory = useSearchHistory()
const searchHistoryList = computed(() => searchHistory.list.value) const searchHistoryList = computed(() => searchHistory.list.value)
const searchExpanded = ref(false) const searchExpanded = ref(false)
@ -105,8 +106,15 @@ function selectHistoryItem(item: string) {
onSearchSubmit() onSearchSubmit()
} }
onMounted(() => { onMounted(async () => {
refreshUserData() await nextTick()
console.log('app onMounted')
if (layerActiveValues.value.length == 0) {
const res = await getPmTagMain()
if (res.code === 0 || res.code === 200) {
categoryTree.value = res.data
}
}
}) })
watch( watch(
() => userStore.isLoggedIn, () => userStore.isLoggedIn,
@ -115,11 +123,19 @@ watch(
}, },
{ immediate: true }, { immediate: true },
) )
async function onMenuClick(id: string) {
if (currentRoute.value != '/') {
router.push('/')
}
await nextTick()
menuStore.onCategorySelect(0, id)
}
</script> </script>
<template> <template>
<v-app> <v-app>
<v-app-bar color="surface" elevation="0" height="112"> <v-app-bar color="surface" elevation="0" height="112" class="flex-row">
<div class="flex-column header-content"> <div class="flex-column header-content">
<div class="app-bar-inner"> <div class="app-bar-inner">
<v-btn v-if="currentRoute !== '/'" icon variant="text" class="back-btn" :aria-label="t('common.back')" <v-btn v-if="currentRoute !== '/'" icon variant="text" class="back-btn" :aria-label="t('common.back')"
@ -138,7 +154,20 @@ watch(
</div> </div>
</v-app-bar-title> </v-app-bar-title>
<v-spacer></v-spacer> <div class="home-category-layer1-row line1">
<div class="app-tab-bar" height="48" @update:model-value="menuStore.onCategorySelect(0, $event)">
<div v-for="item in categoryLayers[0]" class="app-tab-bar-item"
:class="{ 'app-tab-bar-item-active': item.id === layerActiveValues[0] }" :key="item.id" :value="item.id"
@click="onMenuClick(item.id)">
{{ item.label }}
</div>
</div>
<v-btn icon variant="text" class="home-search-btn" :aria-label="t('common.search')" @click="expandSearch">
<v-icon size="24">mdi-magnify</v-icon>
</v-btn>
</div>
<v-spacer class="line2"></v-spacer>
<template v-if="!userStore.isLoggedIn"> <template v-if="!userStore.isLoggedIn">
<v-menu v-model="localeMenuOpen" :close-on-content-click="true" location="bottom" <v-menu v-model="localeMenuOpen" :close-on-content-click="true" location="bottom"
@ -174,13 +203,12 @@ watch(
</div> </div>
<!-- 提取的顶部菜单栏与搜索功能 --> <!-- 提取的顶部菜单栏与搜索功能 -->
<div v-if="currentRoute === '/' && categoryLayers.length > 0" class="home-category-layer1-wrap"> <div v-if="categoryLayers.length > 0" class="home-category-layer1-wrap">
<div class="home-category-layer1-row"> <div class="home-category-layer1-row line2">
<div class="app-tab-bar" height="48" @update:model-value="menuStore.onCategorySelect(0, $event)"> <div class="app-tab-bar" height="48" @update:model-value="menuStore.onCategorySelect(0, $event)">
<div v-for="item in categoryLayers[0]" class="app-tab-bar-item" <div v-for="item in categoryLayers[0]" class="app-tab-bar-item"
:class="{ 'app-tab-bar-item-active': item.id === layerActiveValues[0] }" :key="item.id" :class="{ 'app-tab-bar-item-active': item.id === layerActiveValues[0] }" :key="item.id" :value="item.id"
:value="item.id" @click="onMenuClick(item.id)">
@click="menuStore.onCategorySelect(0, item.id)">
{{ item.label }} {{ item.label }}
</div> </div>
</div> </div>
@ -263,10 +291,11 @@ watch(
<style scoped lang="scss"> <style scoped lang="scss">
.header-content { .header-content {
width: 100%;
max-width: 1440px; max-width: 1440px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06); border-bottom: 1px solid rgba(0, 0, 0, 0.06);
height: 112px; height: 112px;
margin: 0 auto;
flex: 1;
} }
.app-bar-inner { .app-bar-inner {
@ -382,10 +411,6 @@ watch(
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.app-bar-inner {
padding: 0 12px;
}
.brand-lockup { .brand-lockup {
gap: 10px; gap: 10px;
} }
@ -460,12 +485,25 @@ watch(
} }
.home-category-layer1-row { .home-category-layer1-row {
max-width: 1440px; display: none;
margin: 0 auto;
display: flex;
align-items: center; align-items: center;
flex: 1;
} }
.line1 {
@include gt1024 {
display: flex;
};
}
.line2 {
display: none;
@include lt1024 {
display: flex;
};
}
.app-tab-bar { .app-tab-bar {
display: flex; display: flex;
padding-left: 8px; padding-left: 8px;
@ -582,10 +620,4 @@ watch(
cursor: pointer; cursor: pointer;
padding: 0; padding: 0;
} }
@media (max-width: 600px) {
.home-category-layer1-wrap {
margin-left: 12px;
}
}
</style> </style>

View File

@ -60,6 +60,12 @@
} }
} }
@mixin lt1024 {
@media screen and (max-width: 1025px) {
@content;
}
}
@mixin gt1024 { @mixin gt1024 {
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
@content; @content;

View File

@ -1,12 +1,12 @@
/* 全局:禁止 body 滚动,由 app-main-scroll 内部滚动,滚动条不覆盖底部导航 */ /* 全局:禁止 body 滚动,由 app-main-scroll 内部滚动,滚动条不覆盖底部导航 */
:global(html), :global(html),
:global(body) { :global(body) {
background: rgb(252, 252, 252); background: rgb(254, 254, 254);
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
:global(.v-application) { :global(.v-application) {
background: rgb(252, 252, 252); background: rgb(254, 254, 254);
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
@ -36,3 +36,11 @@ button {
background: none; background: none;
cursor: pointer; cursor: pointer;
} }
.v-input--density-compact {
.v-field__input {
padding-inline: var(--v-field-padding-start) var(--v-field-padding-end);
padding-top: var(--v-field-input-padding-top);
padding-bottom: var(--v-field-input-padding-bottom);
}
}

View File

@ -22,3 +22,10 @@ $radius-lg: 12px;
// 阴影 // 阴影
$shadow: 0 2px 12px 0 rgba(0,0,0,0.08); $shadow: 0 2px 12px 0 rgba(0,0,0,0.08);
$yes-bg: #e3f7ea;
$no-bg: #fceded;
$yes-text: #3da763;
$no-text: #e23939;
$header-height: 112px;

View File

@ -12,9 +12,9 @@
<div class="footer-links"> <div class="footer-links">
<span class="footer-copyright">Alpha Market Inc. © 2026</span> <span class="footer-copyright">Alpha Market Inc. © 2026</span>
<span class="sep">·</span> <span class="sep">·</span>
<a href="#">Privacy</a> <a href="/doc/privacy">Privacy</a>
<span class="sep">·</span> <span class="sep">·</span>
<a href="#">Terms of Use</a> <a href="/doc/tos">Terms of Use</a>
<span class="sep">·</span> <span class="sep">·</span>
<a href="#">Market Integrity</a> <a href="#">Market Integrity</a>
<span class="sep">·</span> <span class="sep">·</span>
@ -28,7 +28,7 @@
QCX LLC d/b/a Alpha Market US, a CFTC-regulated Designated Contract Market. This QCX LLC d/b/a Alpha Market US, a CFTC-regulated Designated Contract Market. This
international platform is not regulated by the CFTC and operates independently. Trading international platform is not regulated by the CFTC and operates independently. Trading
involves substantial risk of loss. See our involves substantial risk of loss. See our
<a href="#">Terms of Service</a> &amp; <a href="#">Privacy Policy</a>. <a href="/doc/tos">Terms of Service</a> &amp; <a href="/doc/privacy">Privacy Policy</a>.
</p> </p>
</div> </div>
</footer> </footer>

View File

@ -53,7 +53,6 @@
<div class="options-section"> <div class="options-section">
<v-btn <v-btn
class="option-yes" class="option-yes"
:color="'#b8e0b8'"
:rounded="'sm'" :rounded="'sm'"
:text="true" :text="true"
elevation="0" elevation="0"
@ -63,7 +62,6 @@
</v-btn> </v-btn>
<v-btn <v-btn
class="option-no" class="option-no"
:color="'#f0b8b8'"
:rounded="'sm'" :rounded="'sm'"
:text="true" :text="true"
elevation="0" elevation="0"
@ -87,7 +85,6 @@
<div class="outcome-buttons"> <div class="outcome-buttons">
<v-btn <v-btn
class="option-yes option-yes-no-compact" class="option-yes option-yes-no-compact"
:color="'#b8e0b8'"
:rounded="'sm'" :rounded="'sm'"
:text="true" :text="true"
elevation="0" elevation="0"
@ -97,7 +94,6 @@
</v-btn> </v-btn>
<v-btn <v-btn
class="option-no option-yes-no-compact" class="option-no option-yes-no-compact"
:color="'#f0b8b8'"
:rounded="'sm'" :rounded="'sm'"
:text="true" :text="true"
elevation="0" elevation="0"
@ -269,7 +265,7 @@ function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
} }
</script> </script>
<style scoped> <style scoped lang="scss">
/* 单 market 与多 market 统一高度 */ /* 单 market 与多 market 统一高度 */
.market-card { .market-card {
position: relative; position: relative;
@ -416,7 +412,7 @@ function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
.option-yes { .option-yes {
flex: 1; flex: 1;
background-color: #b8e0b8; background-color: $yes-bg;
border-radius: 6px; border-radius: 6px;
height: 40px; height: 40px;
} }
@ -425,12 +421,12 @@ function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: #006600; color: $yes-text;
} }
.option-no { .option-no {
flex: 1; flex: 1;
background-color: #f0b8b8; background-color: $no-bg;
border-radius: 6px; border-radius: 6px;
height: 40px; height: 40px;
} }
@ -439,7 +435,7 @@ function openTradeMulti(side: 'yes' | 'no', outcome: EventCardOutcome) {
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: #cc0000; color: $no-text;
} }
/* Bottom Section禁止被挤压避免与 multi-section 重叠 */ /* Bottom Section禁止被挤压避免与 multi-section 重叠 */

View File

@ -2216,7 +2216,7 @@ async function submitOrder() {
} }
</script> </script>
<style scoped> <style scoped lang="scss">
/* 扁平化:移除所有阴影 */ /* 扁平化:移除所有阴影 */
.trade-component, .trade-component,
.trade-sheet-paper, .trade-sheet-paper,
@ -2674,16 +2674,16 @@ async function submitOrder() {
.mobile-bar-yes { .mobile-bar-yes {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
background: #16a34a; background: $yes-bg;
color: #fff; color: $yes-text;
padding: 14px 16px; padding: 14px 16px;
} }
.mobile-bar-no { .mobile-bar-no {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
background: #dc2626; background: $no-bg;
color: #fff; color: $no-text;
padding: 14px 16px; padding: 14px 16px;
} }

View File

@ -46,6 +46,7 @@ export const useMenuStore = defineStore('menu', () => {
/** 分类选中时:若有 children 则展开下一层并默认选中第一个,并重新加载列表 */ /** 分类选中时:若有 children 则展开下一层并默认选中第一个,并重新加载列表 */
function onCategorySelect(layerIndex: number, selectedId: string) { function onCategorySelect(layerIndex: number, selectedId: string) {
console.log('onCategorySelect', layerIndex, selectedId)
if (!selectedId || layerActiveValues.value[layerIndex] === selectedId) { if (!selectedId || layerActiveValues.value[layerIndex] === selectedId) {
return return
} }

View File

@ -357,13 +357,16 @@ watch(
onUnmounted(removeLoadMoreObserver) onUnmounted(removeLoadMoreObserver)
</script> </script>
<style scoped> <style scoped lang="scss">
.api-key-page { .api-key-page {
min-height: 100vh; min-height: 100vh;
background: #fcfcfc; background: #fcfcfc;
display: flex; display: flex;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
margin: 0 auto;
max-width: 1440px;
margin-top: $header-height;
} }
.api-key-screen { .api-key-screen {

View File

@ -143,7 +143,7 @@ async function fetchLockInfo() {
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.earn-activity-page { .earn-activity-page {
min-height: 100vh; min-height: 100vh;
background: #f5f5f5; background: #f5f5f5;
@ -151,6 +151,9 @@ async function fetchLockInfo() {
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
padding: 16px; padding: 16px;
margin: 0 auto;
max-width: 1440px;
margin-top: $header-height;
} }
.earn-activity-screen { .earn-activity-screen {

View File

@ -204,7 +204,6 @@
<TradeComponent <TradeComponent
:market="tradeMarketPayload" :market="tradeMarketPayload"
:initial-option="tradeInitialOption" :initial-option="tradeInitialOption"
@submit="onTradeSubmit"
/> />
</div> </div>
</v-col> </v-col>
@ -271,7 +270,6 @@
:market="tradeMarketPayload" :market="tradeMarketPayload"
:initial-option="tradeInitialOption" :initial-option="tradeInitialOption"
embedded-in-sheet embedded-in-sheet
@submit="onTradeSubmit"
@order-success="onOrderSuccess" @order-success="onOrderSuccess"
/> />
</v-bottom-sheet> </v-bottom-sheet>
@ -429,13 +427,6 @@ const eventVolume = computed(() => {
return v != null ? formatVolume(v) : '' return v != null ? formatVolume(v) : ''
}) })
const currentChance = computed(() => {
const m = selectedMarket.value
if (!m) return 0
return marketChance(m)
})
const selectedMarketVolume = computed(() => formatVolume(selectedMarket.value?.volume))
const marketExpiresAt = computed(() => { const marketExpiresAt = computed(() => {
const endDate = eventDetail.value?.endDate const endDate = eventDetail.value?.endDate
return endDate ? formatExpiresAt(endDate) : '' return endDate ? formatExpiresAt(endDate) : ''
@ -477,7 +468,6 @@ const LINE_COLORS = [
'#ea580c', '#ea580c',
'#4f46e5', '#4f46e5',
] ]
const MOBILE_BREAKPOINT = 600
/** 按市场依次请求 getPmPriceHistoryPublicmarket 传 clobTokenIds[0]YES token */ /** 按市场依次请求 getPmPriceHistoryPublicmarket 传 clobTokenIds[0]YES token */
async function loadChartFromApi(): Promise<ChartSeriesItem[]> { async function loadChartFromApi(): Promise<ChartSeriesItem[]> {
@ -595,10 +585,6 @@ const handleResize = () => {
chartInstance.resize(chartContainerRef.value.clientWidth, 320) chartInstance.resize(chartContainerRef.value.clientWidth, 320)
} }
function selectMarket(index: number) {
selectedMarketIndex.value = index
}
/** 点击 Buy Yes/No选中该市场并把数据和方向传给购买组件移动端直接弹出交易弹窗 */ /** 点击 Buy Yes/No选中该市场并把数据和方向传给购买组件移动端直接弹出交易弹窗 */
function openTrade(market: PmEventMarketItem, index: number, side: 'yes' | 'no') { function openTrade(market: PmEventMarketItem, index: number, side: 'yes' | 'no') {
selectedMarketIndex.value = index selectedMarketIndex.value = index
@ -628,18 +614,6 @@ function openSplitFromBar() {
tradeSheetOpen.value = true tradeSheetOpen.value = true
} }
function onTradeSubmit(payload: {
side: 'buy' | 'sell'
option: 'yes' | 'no'
limitPrice: number
shares: number
expirationEnabled: boolean
expirationTime: string
marketId?: string
}) {
// APIpayload marketId
}
const toastStore = useToastStore() const toastStore = useToastStore()
function onOrderSuccess() { function onOrderSuccess() {
tradeSheetOpen.value = false tradeSheetOpen.value = false
@ -823,10 +797,13 @@ watch(
) )
</script> </script>
<style scoped> <style scoped lang="scss">
.event-markets-container { .event-markets-container {
padding: 24px; padding: 24px;
min-height: 100vh; min-height: 100vh;
max-width: 1440px;
margin: 0 auto;
margin-top: $header-height;
} }
.event-markets-row { .event-markets-row {
@ -1194,13 +1171,13 @@ watch(
} }
.buy-yes-btn { .buy-yes-btn {
background-color: #b8e0b8 !important; background-color: $yes-bg;
color: #006600 !important; color: $yes-text;
} }
.buy-no-btn { .buy-no-btn {
background-color: #f0b8b8 !important; background-color: $no-bg;
color: #cc0000 !important; color: $no-text;
} }
/* 已结算子市场列表Pencil vb1xr */ /* 已结算子市场列表Pencil vb1xr */
@ -1327,16 +1304,16 @@ watch(
.mobile-bar-yes { .mobile-bar-yes {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
background: #b8e0b8 !important; background: $yes-bg;
color: #006600 !important; color: $yes-text;
padding: 14px 16px; padding: 14px 16px;
} }
.mobile-bar-no { .mobile-bar-no {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
background: #f0b8b8 !important; background: $no-bg;
color: #cc0000 !important; color: $no-text;
padding: 14px 16px; padding: 14px 16px;
} }

View File

@ -22,6 +22,16 @@
</v-tabs> </v-tabs>
</div> </div>
</div> </div>
<div class="flex-row home-left-menu2-container">
<div class="left-menu2" :class="{ 'position-fixed': isFixed, 'position-absolute': isAbsolute }">
<div v-for="item in categoryLayers[1]" :key="item.id" @click="menuStore.onCategorySelect(1, item.id)"
class="left-menu2-item" :class="{ 'active': layerActiveValues[1] === item.id }">
<div class="item-icon"></div>
<div class="item-label">{{ item.label }}</div>
<div class="item-count">{{ 99 }}</div>
</div>
</div>
<!-- 可滚动容器作为 v-pull-to-refresh 的父元素组件据此判断 scrollTop 仅在顶部时才响应下拉 --> <!-- 可滚动容器作为 v-pull-to-refresh 的父元素组件据此判断 scrollTop 仅在顶部时才响应下拉 -->
<div ref="scrollRef" class="home-list-scroll"> <div ref="scrollRef" class="home-list-scroll">
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh"> <v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
@ -57,6 +67,8 @@
</div> </div>
</v-pull-to-refresh> </v-pull-to-refresh>
</div> </div>
</div>
<AppFooter />
<!-- PC对话框手机底部 sheet直接显示交易表单 --> <!-- PC对话框手机底部 sheet直接显示交易表单 -->
<v-dialog v-if="!isMobile" v-model="tradeDialogOpen" max-width="420" scrollable <v-dialog v-if="!isMobile" v-model="tradeDialogOpen" max-width="420" scrollable
@ -117,6 +129,7 @@ import { useToastStore } from '../stores/toast'
import { useLocaleStore } from '../stores/locale' import { useLocaleStore } from '../stores/locale'
import { useMenuStore } from '../stores/menu' import { useMenuStore } from '../stores/menu'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import AppFooter from '../components/AppFooter.vue'
const { mobile } = useDisplay() const { mobile } = useDisplay()
const { t } = useI18n() const { t } = useI18n()
@ -129,6 +142,37 @@ const { filterVisible } = menuStore
/** 标记是否为分类初始化阶段,用于 watch 时区分初始加载与用户切换 */ /** 标记是否为分类初始化阶段,用于 watch 时区分初始加载与用户切换 */
const isCategoryInitializing = ref(false) const isCategoryInitializing = ref(false)
const isFixed = ref(false)
const isAbsolute = ref(false)
const OFFSET = 100
onMounted(() => {
window.addEventListener('scroll', update)
update()
nextTick(() => {
console.log('home onMounted')
if (layerActiveValues.value.length !== 0) {
loadEvents(1, false)
} else {
}
})
})
onUnmounted(() => {
window.removeEventListener('scroll', update)
})
function update() {
const scrollTop = window.scrollY
const winH = window.innerHeight
const docH = document.documentElement.scrollHeight
const bottomDistance = docH - scrollTop - winH
isFixed.value = bottomDistance <= OFFSET && bottomDistance > 0
isAbsolute.value = bottomDistance <= 0
}
function doSearch(keyword: string) { function doSearch(keyword: string) {
@ -217,7 +261,6 @@ const tradeDialogMarket = ref<{
yesPrice?: number yesPrice?: number
noPrice?: number noPrice?: number
} | null>(null) } | null>(null)
const scrollRef = ref<HTMLElement | null>(null)
function onCardOpenTrade( function onCardOpenTrade(
side: 'yes' | 'no', side: 'yes' | 'no',
@ -403,6 +446,10 @@ async function loadEvents(page: number, append: boolean, keyword?: string) {
total: eventTotal.value, total: eventTotal.value,
pageSize: eventPageSize.value, pageSize: eventPageSize.value,
}) })
nextTick(() => {
})
} catch (e) { } catch (e) {
console.log(e) console.log(e)
if (!append) eventList.value = [] if (!append) eventList.value = []
@ -448,7 +495,6 @@ onBeforeRouteLeave(() => {
onMounted(() => { onMounted(() => {
// emit // emit
// if (props.initialSearchExpanded) expandSearch() // if (props.initialSearchExpanded) expandSearch()
loadCategory()
nextTick(() => { nextTick(() => {
const scrollEl = getMainScrollEl() const scrollEl = getMainScrollEl()
const sentinel = sentinelRef.value const sentinel = sentinelRef.value
@ -515,12 +561,13 @@ onActivated(() => {
<style scoped lang="scss"> <style scoped lang="scss">
.home-container { .home-container {
max-width: 1440px;
flex: 1 1 0; flex: 1 1 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: calc(100vh - 112px);
padding: 0; padding: 0;
margin: 112px 0 0 0; margin: 112px auto 0 auto;
} }
.home-title { .home-title {
@ -532,7 +579,6 @@ onActivated(() => {
.pull-to-refresh { .pull-to-refresh {
width: 100%; width: 100%;
margin-top: 8px;
min-height: 100%; min-height: 100%;
padding: 8px; padding: 8px;
} }
@ -543,8 +589,13 @@ onActivated(() => {
/* 不设固定高度与 overflow列表随页面窗口滚动便于 Vue Router scrollBehavior 自动恢复位置 */ /* 不设固定高度与 overflow列表随页面窗口滚动便于 Vue Router scrollBehavior 自动恢复位置 */
.home-list-scroll { .home-list-scroll {
width: 100%; flex: 1;
overflow-x: visible; overflow-y: auto; //
overflow-x: hidden;
@include gt1024 {
margin-left: 200px;
}
} }
/* 列数由 JS 根据容器宽度与 CARD_MIN_WIDTH 连续计算,避免断点导致 6→4 跳变 */ /* 列数由 JS 根据容器宽度与 CARD_MIN_WIDTH 连续计算,避免断点导致 6→4 跳变 */
@ -811,4 +862,59 @@ onActivated(() => {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
.home-left-menu2-container {
flex: 1;
}
.position-fixed {
position: fixed;
top: 112px;
}
.left-menu2 {
height: calc(100vh - 112px);
width: 190px;
display: none;
flex-direction: column;
margin: 8px 8px 0 0;
flex-shrink: 0;
overflow-y: auto;
overflow-x: hidden;
position: absolute;
top: 112px;
@include gt1024 {
display: flex;
}
.left-menu2-item {
width: 100%;
display: flex;
align-items: center;
padding: 12px;
cursor: pointer;
border-radius: 8px;
.item-icon {
width: 24px;
height: 24px;
}
.item-label {
flex: 1;
}
.item-count {
flex-shrink: 0;
}
}
.active {
font-weight: 700;
background-color: #e5e5e5;
}
}
</style> </style>

View File

@ -201,7 +201,7 @@ function goWallet() {
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.member-center-page { .member-center-page {
min-height: 100vh; min-height: 100vh;
background: #ffffff; background: #ffffff;
@ -209,6 +209,9 @@ function goWallet() {
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
padding: 0; padding: 0;
margin: 0 auto;
max-width: 1440px;
margin-top: $header-height;
} }
.member-screen { .member-screen {

View File

@ -6,13 +6,7 @@
<label class="avatar-wrap" :aria-label="t('profile.changeAvatar')"> <label class="avatar-wrap" :aria-label="t('profile.changeAvatar')">
<input type="file" accept="image/*" class="avatar-input" @change="onAvatarFileChange" /> <input type="file" accept="image/*" class="avatar-input" @change="onAvatarFileChange" />
<div class="avatar"> <div class="avatar">
<v-progress-circular <v-progress-circular v-if="avatarUploading" indeterminate size="32" width="2" class="avatar-loading" />
v-if="avatarUploading"
indeterminate
size="32"
width="2"
class="avatar-loading"
/>
<template v-else> <template v-else>
<img v-if="avatarImage" :src="avatarImage" :alt="displayName" class="avatar-img" /> <img v-if="avatarImage" :src="avatarImage" :alt="displayName" class="avatar-img" />
<span v-else>{{ avatarText }}</span> <span v-else>{{ avatarText }}</span>
@ -64,13 +58,7 @@
<section class="card menu-card"> <section class="card menu-card">
<div class="menu-title">{{ t('profile.accountSettings') }}</div> <div class="menu-title">{{ t('profile.accountSettings') }}</div>
<button <button v-for="item in settingItems" :key="item.label" class="menu-item" type="button" @click="goSetting(item)">
v-for="item in settingItems"
:key="item.label"
class="menu-item"
type="button"
@click="goSetting(item)"
>
<span>{{ item.label }}</span> <span>{{ item.label }}</span>
<span v-if="item.action === 'locale'" class="menu-locale">{{ currentLocaleLabel }}</span> <span v-if="item.action === 'locale'" class="menu-locale">{{ currentLocaleLabel }}</span>
<span v-else-if="item.action === 'wallet'" class="menu-locale">{{ <span v-else-if="item.action === 'wallet'" class="menu-locale">{{
@ -88,13 +76,8 @@
<v-dialog v-model="localeDialogOpen" max-width="360"> <v-dialog v-model="localeDialogOpen" max-width="360">
<v-card class="locale-dialog-card" rounded="xl" elevation="0"> <v-card class="locale-dialog-card" rounded="xl" elevation="0">
<div class="locale-dialog-title">{{ t('profile.selectLanguage') }}</div> <div class="locale-dialog-title">{{ t('profile.selectLanguage') }}</div>
<button <button v-for="opt in localeStore.localeOptions" :key="opt.value" class="locale-option" type="button"
v-for="opt in localeStore.localeOptions" @click="chooseLocale(opt.value)">
:key="opt.value"
class="locale-option"
type="button"
@click="chooseLocale(opt.value)"
>
<span>{{ opt.label }}</span> <span>{{ opt.label }}</span>
<span v-if="opt.value === localeStore.currentLocale" class="locale-selected"></span> <span v-if="opt.value === localeStore.currentLocale" class="locale-selected"></span>
</button> </button>
@ -106,12 +89,7 @@
<div class="wallet-dialog-title">{{ t('profile.currentWalletAddress') }}</div> <div class="wallet-dialog-title">{{ t('profile.currentWalletAddress') }}</div>
<div class="wallet-dialog-address">{{ walletAddressText }}</div> <div class="wallet-dialog-address">{{ walletAddressText }}</div>
<div class="wallet-dialog-actions"> <div class="wallet-dialog-actions">
<button <button class="wallet-copy-btn" type="button" :disabled="!walletAddress" @click="copyWalletAddress">
class="wallet-copy-btn"
type="button"
:disabled="!walletAddress"
@click="copyWalletAddress"
>
{{ t('profile.copyAddress') }} {{ t('profile.copyAddress') }}
</button> </button>
</div> </div>
@ -122,45 +100,21 @@
<v-card class="name-dialog-card" elevation="0"> <v-card class="name-dialog-card" elevation="0">
<div class="name-dialog-header"> <div class="name-dialog-header">
<h2 class="name-dialog-title">{{ t('profile.editNameTitle') }}</h2> <h2 class="name-dialog-title">{{ t('profile.editNameTitle') }}</h2>
<v-btn <v-btn icon variant="text" class="name-dialog-close-btn" :aria-label="t('deposit.close')"
icon @click="closeEditNameDialog">
variant="text"
class="name-dialog-close-btn"
:aria-label="t('deposit.close')"
@click="closeEditNameDialog"
>
<v-icon size="18">mdi-close</v-icon> <v-icon size="18">mdi-close</v-icon>
</v-btn> </v-btn>
</div> </div>
<v-card-text class="name-dialog-body"> <v-card-text class="name-dialog-body">
<v-text-field <v-text-field v-model="editingName" :label="t('profile.newUserName')" variant="outlined" density="comfortable"
v-model="editingName" hide-details="auto" :error-messages="nameError ? [nameError] : []" :hint="t('profile.nameFormatHint')"
:label="t('profile.newUserName')" persistent-hint class="name-dialog-field" />
variant="outlined"
density="comfortable"
hide-details="auto"
:error-messages="nameError ? [nameError] : []"
:hint="t('profile.nameFormatHint')"
persistent-hint
class="name-dialog-field"
/>
<div class="name-dialog-actions"> <div class="name-dialog-actions">
<button <button class="name-dialog-cancel-btn" type="button" :disabled="isSaving" @click="closeEditNameDialog">
class="name-dialog-cancel-btn"
type="button"
:disabled="isSaving"
@click="closeEditNameDialog"
>
{{ t('profile.cancel') }} {{ t('profile.cancel') }}
</button> </button>
<button class="name-dialog-save-btn" type="button" :disabled="isSaving" @click="saveName"> <button class="name-dialog-save-btn" type="button" :disabled="isSaving" @click="saveName">
<v-progress-circular <v-progress-circular v-if="isSaving" indeterminate size="18" width="2" class="save-btn-spinner" />
v-if="isSaving"
indeterminate
size="18"
width="2"
class="save-btn-spinner"
/>
<span v-else>{{ t('profile.save') }}</span> <span v-else>{{ t('profile.save') }}</span>
</button> </button>
</div> </div>
@ -493,14 +447,17 @@ onMounted(() => {
}) })
</script> </script>
<style scoped> <style scoped lang="scss">
.profile-page { .profile-page {
min-height: 100vh; min-height: 100vh;
background: #ffffff;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
padding: 0; padding: 0;
margin: 0 auto;
max-width: 1440px;
margin-top: $header-height;
} }
.profile-screen { .profile-screen {

View File

@ -314,13 +314,16 @@ function openResult(item: SearchResultItem) {
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.search-page { .search-page {
min-height: 100vh; min-height: 100vh;
background: #fcfcfc; background: #fcfcfc;
display: flex; display: flex;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
margin: 0 auto;
max-width: 1440px;
margin-top: $header-height;
} }
.search-screen { .search-screen {

View File

@ -21,10 +21,13 @@
import OrderBook from '../components/OrderBook.vue' import OrderBook from '../components/OrderBook.vue'
</script> </script>
<style scoped> <style scoped lang="scss">
.trade-container { .trade-container {
padding: 40px 20px; padding: 40px 20px;
min-height: 100vh; min-height: 100vh;
margin: 0 auto;
max-width: 1440px;
margin-top: $header-height;
} }
.trade-header { .trade-header {

View File

@ -1,27 +1,18 @@
<template> <template>
<v-container <v-container fluid class="trade-detail-container" :class="{
fluid
class="trade-detail-container"
:class="{
'trade-detail-container--settled-bar': showSettledOutcomeBar, 'trade-detail-container--settled-bar': showSettledOutcomeBar,
}" }">
>
<v-pull-to-refresh class="trade-detail-pull-refresh" @load="onRefresh"> <v-pull-to-refresh class="trade-detail-pull-refresh" @load="onRefresh">
<div class="trade-detail-pull-refresh-inner"> <div class="trade-detail-pull-refresh-inner">
<!-- findPmEvent 请求中仅显示 loading --> <!-- findPmEvent 请求中仅显示 loading -->
<v-card <v-card v-if="detailLoading && !eventDetail" class="trade-detail-loading-card" elevation="0" rounded="lg">
v-if="detailLoading && !eventDetail"
class="trade-detail-loading-card"
elevation="0"
rounded="lg"
>
<div class="trade-detail-loading-placeholder"> <div class="trade-detail-loading-placeholder">
<v-progress-circular indeterminate color="primary" size="48" /> <v-progress-circular indeterminate color="primary" size="48" />
<p>{{ t('common.loading') }}</p> <p>{{ t('common.loading') }}</p>
</div> </div>
</v-card> </v-card>
<v-row v-else align="stretch" no-gutters class="trade-detail-row"> <v-row v-else align="stretch" class="trade-detail-row" style="--v-col-gap-y: 0px;">
<!-- 左侧分时图 + 订单簿宽度弹性 --> <!-- 左侧分时图 + 订单簿宽度弹性 -->
<v-col cols="12" class="chart-col"> <v-col cols="12" class="chart-col">
<!-- 分时图卡片Polymarket 样式 --> <!-- 分时图卡片Polymarket 样式 -->
@ -34,29 +25,14 @@
<p v-if="detailError" class="chart-error">{{ detailError }}</p> <p v-if="detailError" class="chart-error">{{ detailError }}</p>
<div class="chart-controls-row"> <div class="chart-controls-row">
<v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</v-btn> <v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</v-btn>
<v-btn-group <v-btn-group v-if="cryptoSymbol" variant="outlined" density="compact" divided
v-if="cryptoSymbol" class="chart-mode-toggle">
variant="outlined" <v-btn :class="{ active: chartMode === 'yesno' }" size="small" icon
density="compact" :aria-label="t('chart.yesnoTimeSeries')" @click="setChartMode('yesno')">
divided
class="chart-mode-toggle"
>
<v-btn
:class="{ active: chartMode === 'yesno' }"
size="small"
icon
:aria-label="t('chart.yesnoTimeSeries')"
@click="setChartMode('yesno')"
>
<v-icon size="20">mdi-chart-timeline-variant</v-icon> <v-icon size="20">mdi-chart-timeline-variant</v-icon>
</v-btn> </v-btn>
<v-btn <v-btn :class="{ active: chartMode === 'crypto' }" size="small" class="chart-mode-crypto-btn"
:class="{ active: chartMode === 'crypto' }" :aria-label="t('chart.cryptoPrice')" @click="setChartMode('crypto')">
size="small"
class="chart-mode-crypto-btn"
:aria-label="t('chart.cryptoPrice')"
@click="setChartMode('crypto')"
>
<span class="chart-crypto-ticker-label">{{ cryptoSymbol.toUpperCase() }}</span> <span class="chart-crypto-ticker-label">{{ cryptoSymbol.toUpperCase() }}</span>
</v-btn> </v-btn>
</v-btn-group> </v-btn-group>
@ -87,14 +63,9 @@
<span v-if="marketExpiresAt" class="chart-expires">| {{ marketExpiresAt }}</span> <span v-if="marketExpiresAt" class="chart-expires">| {{ marketExpiresAt }}</span>
</div> </div>
<div class="chart-time-ranges"> <div class="chart-time-ranges">
<v-btn <v-btn v-for="r in timeRanges" :key="r.value"
v-for="r in timeRanges" :class="['time-range-btn', { active: selectedTimeRange === r.value }]" variant="text" size="small"
:key="r.value" @click="selectTimeRange(r.value)">
:class="['time-range-btn', { active: selectedTimeRange === r.value }]"
variant="text"
size="small"
@click="selectTimeRange(r.value)"
>
{{ r.label }} {{ r.label }}
</v-btn> </v-btn>
</div> </div>
@ -102,12 +73,7 @@
</v-card> </v-card>
<!-- 移动端无右侧交易栏时展示已关闭说明 --> <!-- 移动端无右侧交易栏时展示已关闭说明 -->
<v-card <v-card v-if="isMobile && currentMarketClosed" class="market-closed-mobile-card" elevation="0" rounded="lg">
v-if="isMobile && currentMarketClosed"
class="market-closed-mobile-card"
elevation="0"
rounded="lg"
>
<div class="market-closed-pane market-closed-pane--mobile"> <div class="market-closed-pane market-closed-pane--mobile">
<v-icon size="40" color="grey-darken-1">mdi-lock-outline</v-icon> <v-icon size="40" color="grey-darken-1">mdi-lock-outline</v-icon>
<h2 class="market-closed-pane-title">{{ t('trade.marketClosedTitle') }}</h2> <h2 class="market-closed-pane-title">{{ t('trade.marketClosedTitle') }}</h2>
@ -117,11 +83,7 @@
<!-- 持仓 / 限价订单簿上方 --> <!-- 持仓 / 限价订单簿上方 -->
<v-card class="positions-orders-card" elevation="0" rounded="lg"> <v-card class="positions-orders-card" elevation="0" rounded="lg">
<v-tabs <v-tabs v-model="positionsOrdersTab" class="positions-orders-tabs" density="comfortable">
v-model="positionsOrdersTab"
class="positions-orders-tabs"
density="comfortable"
>
<v-tab value="positions">{{ t('activity.myPositions') }}</v-tab> <v-tab value="positions">{{ t('activity.myPositions') }}</v-tab>
<v-tab value="orders">{{ t('activity.openOrders') }}</v-tab> <v-tab value="orders">{{ t('activity.openOrders') }}</v-tab>
</v-tabs> </v-tabs>
@ -135,19 +97,10 @@
{{ t('activity.noPositionsInMarket') }} {{ t('activity.noPositionsInMarket') }}
</div> </div>
<div v-else class="positions-list"> <div v-else class="positions-list">
<div <div v-for="pos in marketPositionsFiltered" :key="pos.id" class="position-row-item">
v-for="pos in marketPositionsFiltered"
:key="pos.id"
class="position-row-item"
>
<div class="position-row-header"> <div class="position-row-header">
<div class="position-row-icon" :class="pos.iconClass"> <div class="position-row-icon" :class="pos.iconClass">
<img <img v-if="pos.imageUrl" :src="pos.imageUrl" alt="" class="position-row-icon-img" />
v-if="pos.imageUrl"
:src="pos.imageUrl"
alt=""
class="position-row-icon-img"
/>
<span v-else class="position-row-icon-char">{{ <span v-else class="position-row-icon-char">{{
pos.iconChar || '•' pos.iconChar || '•'
}}</span> }}</span>
@ -167,17 +120,9 @@
</span> </span>
<span class="position-shares">{{ pos.shares }}</span> <span class="position-shares">{{ pos.shares }}</span>
<span class="position-value">{{ pos.value }}</span> <span class="position-value">{{ pos.value }}</span>
<v-btn <v-btn variant="outlined" size="small" color="primary" class="position-sell-btn" :disabled="currentMarketClosed ||
variant="outlined"
size="small"
color="primary"
class="position-sell-btn"
:disabled="
currentMarketClosed ||
!(pos.availableSharesNum != null && pos.availableSharesNum > 0) !(pos.availableSharesNum != null && pos.availableSharesNum > 0)
" " @click="openSellFromPosition(pos)">
@click="openSellFromPosition(pos)"
>
{{ t('trade.sell') }} {{ t('trade.sell') }}
</v-btn> </v-btn>
</div> </div>
@ -196,9 +141,7 @@
<div v-else class="orders-list"> <div v-else class="orders-list">
<div v-for="ord in marketOpenOrders" :key="ord.id" class="order-row-item"> <div v-for="ord in marketOpenOrders" :key="ord.id" class="order-row-item">
<div class="order-row-main"> <div class="order-row-main">
<span <span :class="['order-side-pill', ord.side === 'Yes' ? 'side-yes' : 'side-no']">
:class="['order-side-pill', ord.side === 'Yes' ? 'side-yes' : 'side-no']"
>
{{ ord.actionLabel || `Buy ${ord.outcome}` }} {{ ord.actionLabel || `Buy ${ord.outcome}` }}
</span> </span>
<span class="order-price">{{ ord.price }}</span> <span class="order-price">{{ ord.price }}</span>
@ -206,13 +149,8 @@
<span class="order-total">{{ ord.total }}</span> <span class="order-total">{{ ord.total }}</span>
</div> </div>
<div class="order-row-actions"> <div class="order-row-actions">
<v-btn <v-btn variant="text" size="small" color="error"
variant="text" :disabled="cancelOrderLoading || ord.fullyFilled" @click="cancelMarketOrder(ord)">
size="small"
color="error"
:disabled="cancelOrderLoading || ord.fullyFilled"
@click="cancelMarketOrder(ord)"
>
{{ t('activity.cancelOrder') }} {{ t('activity.cancelOrder') }}
</v-btn> </v-btn>
</div> </div>
@ -224,57 +162,33 @@
<!-- Order Book Section --> <!-- Order Book Section -->
<v-card v-if="!currentMarketClosed" class="order-book-card" elevation="0" rounded="lg"> <v-card v-if="!currentMarketClosed" class="order-book-card" elevation="0" rounded="lg">
<OrderBook <OrderBook :asks-yes="orderBookAsksYes" :bids-yes="orderBookBidsYes" :asks-no="orderBookAsksNo"
:asks-yes="orderBookAsksYes" :bids-no="orderBookBidsNo" :anchor-best-bid-yes="orderBookBestBidYesCents"
:bids-yes="orderBookBidsYes" :anchor-lowest-ask-yes="orderBookLowestAskYesCents" :anchor-best-bid-no="orderBookBestBidNoCents"
:asks-no="orderBookAsksNo"
:bids-no="orderBookBidsNo"
:anchor-best-bid-yes="orderBookBestBidYesCents"
:anchor-lowest-ask-yes="orderBookLowestAskYesCents"
:anchor-best-bid-no="orderBookBestBidNoCents"
:anchor-lowest-ask-no="orderBookLowestAskNoCents" :anchor-lowest-ask-no="orderBookLowestAskNoCents"
:outcome-price-anchor-yes-cents="orderBookOutcomeAnchorYesCents" :outcome-price-anchor-yes-cents="orderBookOutcomeAnchorYesCents"
:outcome-price-anchor-no-cents="orderBookOutcomeAnchorNoCents" :outcome-price-anchor-no-cents="orderBookOutcomeAnchorNoCents" :last-price-yes="clobLastPriceYes"
:last-price-yes="clobLastPriceYes" :last-price-no="clobLastPriceNo" :spread-yes="clobSpreadYes" :spread-no="clobSpreadNo"
:last-price-no="clobLastPriceNo" :loading="clobLoading" :connected="clobConnected" :yes-label="yesLabel" :no-label="noLabel" />
:spread-yes="clobSpreadYes"
:spread-no="clobSpreadNo"
:loading="clobLoading"
:connected="clobConnected"
:yes-label="yesLabel"
:no-label="noLabel"
/>
</v-card> </v-card>
<!-- Comments / Top Holders / Activity与左侧图表订单簿同宽 --> <!-- Comments / Top Holders / Activity与左侧图表订单簿同宽 -->
<v-card class="activity-card" elevation="0" rounded="lg"> <v-card class="activity-card" elevation="0" rounded="lg">
<div class="rules-pane"> <div class="rules-pane">
<div <div v-if="!eventDetail?.description && !eventDetail?.resolutionSource" class="placeholder-pane">
v-if="!eventDetail?.description && !eventDetail?.resolutionSource"
class="placeholder-pane"
>
{{ t('activity.rulesEmpty') }} {{ t('activity.rulesEmpty') }}
</div> </div>
<template v-else> <template v-else>
<div v-if="eventDetail?.description" class="rules-section"> <div v-if="eventDetail?.description" class="rules-section">
<h3 class="rules-title">{{ t('activity.rulesDescription') }}</h3> <h3 class="rules-title">{{ t('activity.rulesDescription') }}</h3>
<div <div class="rules-text" :class="{
class="rules-text"
:class="{
'rules-text--multi-collapsed': 'rules-text--multi-collapsed':
rulesDescriptionCollapsible && !rulesDescriptionExpanded, rulesDescriptionCollapsible && !rulesDescriptionExpanded,
}" }">
>
{{ eventDetail.description }} {{ eventDetail.description }}
</div> </div>
<v-btn <v-btn v-if="rulesDescriptionCollapsible" variant="text" size="small" density="compact"
v-if="rulesDescriptionCollapsible" class="rules-description-toggle" @click="rulesDescriptionExpanded = !rulesDescriptionExpanded">
variant="text"
size="small"
density="compact"
class="rules-description-toggle"
@click="rulesDescriptionExpanded = !rulesDescriptionExpanded"
>
{{ {{
rulesDescriptionExpanded rulesDescriptionExpanded
? t('eventMarkets.collapseDescription') ? t('eventMarkets.collapseDescription')
@ -284,13 +198,8 @@
</div> </div>
<div v-if="eventDetail?.resolutionSource" class="rules-section"> <div v-if="eventDetail?.resolutionSource" class="rules-section">
<h3 class="rules-title">{{ t('activity.rulesSource') }}</h3> <h3 class="rules-title">{{ t('activity.rulesSource') }}</h3>
<a <a v-if="isResolutionSourceUrl" :href="eventDetail.resolutionSource" target="_blank"
v-if="isResolutionSourceUrl" rel="noopener noreferrer" class="rules-link">
:href="eventDetail.resolutionSource"
target="_blank"
rel="noopener noreferrer"
class="rules-link"
>
{{ eventDetail.resolutionSource }} {{ eventDetail.resolutionSource }}
<v-icon size="14">mdi-open-in-new</v-icon> <v-icon size="14">mdi-open-in-new</v-icon>
</a> </a>
@ -309,13 +218,9 @@
<p class="market-closed-pane-desc">{{ t('trade.marketClosedDesc') }}</p> <p class="market-closed-pane-desc">{{ t('trade.marketClosedDesc') }}</p>
</div> </div>
<div v-else-if="tradeMarketPayload" class="trade-sidebar"> <div v-else-if="tradeMarketPayload" class="trade-sidebar">
<TradeComponent <TradeComponent ref="tradeComponentRef" :market="tradeMarketPayload"
ref="tradeComponentRef" :positions="tradePositionsForComponent" @merge-success="onMergeSuccess"
:market="tradeMarketPayload" @split-success="onSplitSuccess" />
:positions="tradePositionsForComponent"
@merge-success="onMergeSuccess"
@split-success="onSplitSuccess"
/>
</div> </div>
</v-col> </v-col>
@ -323,37 +228,19 @@
<template v-if="isMobile && tradeMarketPayload && !currentMarketClosed"> <template v-if="isMobile && tradeMarketPayload && !currentMarketClosed">
<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">
<v-btn <v-btn class="mobile-bar-btn mobile-bar-yes" variant="flat" rounded="sm"
class="mobile-bar-btn mobile-bar-yes" @click="openSheetWithOption('yes')">
variant="flat"
rounded="sm"
@click="openSheetWithOption('yes')"
>
{{ yesLabel }} {{ yesPriceCents }}¢ {{ yesLabel }} {{ yesPriceCents }}¢
</v-btn> </v-btn>
<v-btn <v-btn class="mobile-bar-btn mobile-bar-no" variant="flat" rounded="sm"
class="mobile-bar-btn mobile-bar-no" @click="openSheetWithOption('no')">
variant="flat"
rounded="sm"
@click="openSheetWithOption('no')"
>
{{ noLabel }} {{ noPriceCents }}¢ {{ noLabel }} {{ noPriceCents }}¢
</v-btn> </v-btn>
<v-menu <v-menu v-model="mobileMenuOpen" :close-on-content-click="true" location="top"
v-model="mobileMenuOpen" transition="scale-transition">
:close-on-content-click="true"
location="top"
transition="scale-transition"
>
<template #activator="{ props: menuProps }"> <template #activator="{ props: menuProps }">
<v-btn <v-btn v-bind="menuProps" class="mobile-bar-more-btn" variant="flat" icon rounded="pill"
v-bind="menuProps" aria-label="更多操作">
class="mobile-bar-more-btn"
variant="flat"
icon
rounded="pill"
aria-label="更多操作"
>
<v-icon size="20">mdi-dots-horizontal</v-icon> <v-icon size="20">mdi-dots-horizontal</v-icon>
</v-btn> </v-btn>
</template> </template>
@ -368,51 +255,30 @@
</v-menu> </v-menu>
</div> </div>
<v-bottom-sheet v-model="tradeSheetOpen" content-class="trade-detail-trade-sheet"> <v-bottom-sheet v-model="tradeSheetOpen" content-class="trade-detail-trade-sheet">
<TradeComponent <TradeComponent v-if="tradeSheetRenderContent" ref="mobileTradeComponentRef" :market="tradeMarketPayload"
v-if="tradeSheetRenderContent" :initial-option="tradeInitialOptionFromBar" :initial-tab="tradeInitialTabFromBar"
ref="mobileTradeComponentRef" :positions="tradePositionsForComponent" embedded-in-sheet @order-success="onOrderSuccess"
:market="tradeMarketPayload" @merge-success="onMergeSuccess" @split-success="onSplitSuccess" />
:initial-option="tradeInitialOptionFromBar"
:initial-tab="tradeInitialTabFromBar"
:positions="tradePositionsForComponent"
embedded-in-sheet
@order-success="onOrderSuccess"
@merge-success="onMergeSuccess"
@split-success="onSplitSuccess"
/>
</v-bottom-sheet> </v-bottom-sheet>
</template> </template>
<!-- 从持仓点击 Sell 弹出的交易组件桌面/移动端通用 --> <!-- 从持仓点击 Sell 弹出的交易组件桌面/移动端通用 -->
<v-dialog <v-dialog v-model="sellDialogOpen" max-width="420" content-class="trade-detail-sell-dialog"
v-model="sellDialogOpen" transition="dialog-transition">
max-width="420" <TradeComponent v-if="sellDialogRenderContent" :market="tradeMarketPayload"
content-class="trade-detail-sell-dialog" :initial-option="sellInitialOption" :initial-tab="'sell'" :positions="tradePositionsForComponent"
transition="dialog-transition" @order-success="onSellOrderSuccess" @merge-success="onMergeSuccess" @split-success="onSplitSuccess" />
>
<TradeComponent
v-if="sellDialogRenderContent"
:market="tradeMarketPayload"
:initial-option="sellInitialOption"
:initial-tab="'sell'"
:positions="tradePositionsForComponent"
@order-success="onSellOrderSuccess"
@merge-success="onMergeSuccess"
@split-success="onSplitSuccess"
/>
</v-dialog> </v-dialog>
</v-row> </v-row>
</div> </div>
</v-pull-to-refresh> </v-pull-to-refresh>
<!-- 已结算结算结果固定在视口底部滚动时仍可见 --> <!-- 已结算结算结果固定在视口底部滚动时仍可见 -->
<div <div v-if="showSettledOutcomeBar" class="trade-settled-outcome-bar" role="status">
v-if="showSettledOutcomeBar"
class="trade-settled-outcome-bar"
role="status"
>
{{ t('trade.marketClosedOutcome', { outcome: settledOutcomeLabel }) }} {{ t('trade.marketClosedOutcome', { outcome: settledOutcomeLabel }) }}
</div> </div>
<AppFooter />
</v-container> </v-container>
</template> </template>
@ -1650,13 +1516,14 @@ onUnmounted(() => {
}) })
</script> </script>
<style scoped> <style scoped lang="scss">
.trade-detail-container { .trade-detail-container {
padding: 24px; padding: 24px;
padding-left: 24px;
padding-right: 24px;
min-height: 100vh; min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
max-width: 1440px;
margin: 0 auto;
margin-top: $header-height;
} }
/* 底部固定展示结算结果时,为内容留出空间(高度与 .trade-settled-outcome-bar 一致) */ /* 底部固定展示结算结果时,为内容留出空间(高度与 .trade-settled-outcome-bar 一致) */
@ -2237,6 +2104,7 @@ onUnmounted(() => {
font-size: 14px; font-size: 14px;
padding: 24px 0; padding: 24px 0;
} }
.placeholder-pane--loading { .placeholder-pane--loading {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -2476,10 +2344,12 @@ onUnmounted(() => {
} }
@keyframes live-pulse { @keyframes live-pulse {
0%, 0%,
100% { 100% {
opacity: 1; opacity: 1;
} }
50% { 50% {
opacity: 0.5; opacity: 0.5;
} }
@ -2637,16 +2507,16 @@ onUnmounted(() => {
.mobile-bar-yes { .mobile-bar-yes {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
background: #b8e0b8 !important; background: $yes-bg;
color: #006600 !important; color: $yes-text;
height: 46px; height: 46px;
} }
.mobile-bar-no { .mobile-bar-no {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
background: #f0b8b8 !important; background: $no-bg;
color: #cc0000 !important; color: $no-text;
height: 46px; height: 46px;
} }

View File

@ -1641,7 +1641,7 @@ async function submitAuthorize() {
} }
</script> </script>
<style scoped> <style scoped lang="scss">
.wallet-container { .wallet-container {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
@ -1649,6 +1649,9 @@ async function submitAuthorize() {
padding: 16px; padding: 16px;
background: #fcfcfc; background: #fcfcfc;
box-sizing: border-box; box-sizing: border-box;
margin: 0 auto;
max-width: 1440px;
margin-top: $header-height;
} }
.wallet-mobile-frame { .wallet-mobile-frame {