优化样式调整
This commit is contained in:
parent
a21869d989
commit
e4ecea471c
80
src/App.vue
80
src/App.vue
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
@ -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> & <a href="#">Privacy Policy</a>.
|
<a href="/doc/tos">Terms of Service</a> & <a href="/doc/privacy">Privacy Policy</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@ -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 重叠 */
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
|
||||||
|
|
||||||
/** 按市场依次请求 getPmPriceHistoryPublic,market 传 clobTokenIds[0](YES token) */
|
/** 按市场依次请求 getPmPriceHistoryPublic,market 传 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
|
|
||||||
}) {
|
|
||||||
// 可在此调用下单 API,payload 含 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,41 +22,53 @@
|
|||||||
</v-tabs>
|
</v-tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 可滚动容器作为 v-pull-to-refresh 的父元素,组件据此判断 scrollTop 仅在顶部时才响应下拉 -->
|
|
||||||
<div ref="scrollRef" class="home-list-scroll">
|
<div class="flex-row home-left-menu2-container">
|
||||||
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
|
<div class="left-menu2" :class="{ 'position-fixed': isFixed, 'position-absolute': isAbsolute }">
|
||||||
<div class="pull-to-refresh-inner">
|
<div v-for="item in categoryLayers[1]" :key="item.id" @click="menuStore.onCategorySelect(1, item.id)"
|
||||||
<div ref="listRef" class="home-list" :style="gridListStyle">
|
class="left-menu2-item" :class="{ 'active': layerActiveValues[1] === item.id }">
|
||||||
<MarketCard v-for="card in eventList" :key="card.id" :id="card.id" :slug="card.slug"
|
<div class="item-icon"></div>
|
||||||
:market-title="card.marketTitle" :chance-value="card.chanceValue" :market-info="card.marketInfo"
|
<div class="item-label">{{ item.label }}</div>
|
||||||
:image-url="card.imageUrl" :category="card.category" :expires-at="card.expiresAt"
|
<div class="item-count">{{ 99 }}</div>
|
||||||
:display-type="card.displayType" :outcomes="card.outcomes" :yes-label="card.yesLabel"
|
|
||||||
:no-label="card.noLabel" :is-new="card.isNew" :market-id="card.marketId"
|
|
||||||
:clob-token-ids="card.clobTokenIds" :yes-price="card.yesPrice" :no-price="card.noPrice"
|
|
||||||
@open-trade="onCardOpenTrade" />
|
|
||||||
<div v-if="eventListLoading" class="home-list-empty home-list-loading">
|
|
||||||
<v-progress-circular indeterminate size="40" width="2" />
|
|
||||||
<span>{{ t('common.loading') }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="eventList.length === 0 && !loadingMore" class="home-list-empty">
|
|
||||||
{{ t('common.noData') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="eventList.length > 0" class="load-more-footer">
|
|
||||||
<div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" />
|
|
||||||
<div v-if="loadingMore" class="load-more-indicator">
|
|
||||||
<v-progress-circular indeterminate size="24" width="2" />
|
|
||||||
<span>{{ t('common.loading') }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="noMoreEvents" class="no-more-tip">{{ t('home.noMore') }}</div>
|
|
||||||
<v-btn v-else class="load-more-btn" variant="outlined" color="primary" :disabled="loadingMore"
|
|
||||||
@click="loadMore">
|
|
||||||
{{ t('home.loadMore') }}
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</v-pull-to-refresh>
|
</div>
|
||||||
|
<!-- 可滚动容器作为 v-pull-to-refresh 的父元素,组件据此判断 scrollTop 仅在顶部时才响应下拉 -->
|
||||||
|
<div ref="scrollRef" class="home-list-scroll">
|
||||||
|
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
|
||||||
|
<div class="pull-to-refresh-inner">
|
||||||
|
<div ref="listRef" class="home-list" :style="gridListStyle">
|
||||||
|
<MarketCard v-for="card in eventList" :key="card.id" :id="card.id" :slug="card.slug"
|
||||||
|
:market-title="card.marketTitle" :chance-value="card.chanceValue" :market-info="card.marketInfo"
|
||||||
|
:image-url="card.imageUrl" :category="card.category" :expires-at="card.expiresAt"
|
||||||
|
:display-type="card.displayType" :outcomes="card.outcomes" :yes-label="card.yesLabel"
|
||||||
|
:no-label="card.noLabel" :is-new="card.isNew" :market-id="card.marketId"
|
||||||
|
:clob-token-ids="card.clobTokenIds" :yes-price="card.yesPrice" :no-price="card.noPrice"
|
||||||
|
@open-trade="onCardOpenTrade" />
|
||||||
|
<div v-if="eventListLoading" class="home-list-empty home-list-loading">
|
||||||
|
<v-progress-circular indeterminate size="40" width="2" />
|
||||||
|
<span>{{ t('common.loading') }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="eventList.length === 0 && !loadingMore" class="home-list-empty">
|
||||||
|
{{ t('common.noData') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="eventList.length > 0" class="load-more-footer">
|
||||||
|
<div ref="sentinelRef" class="load-more-sentinel" aria-hidden="true" />
|
||||||
|
<div v-if="loadingMore" class="load-more-indicator">
|
||||||
|
<v-progress-circular indeterminate size="24" width="2" />
|
||||||
|
<span>{{ t('common.loading') }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="noMoreEvents" class="no-more-tip">{{ t('home.noMore') }}</div>
|
||||||
|
<v-btn v-else class="load-more-btn" variant="outlined" color="primary" :disabled="loadingMore"
|
||||||
|
@click="loadMore">
|
||||||
|
{{ t('home.loadMore') }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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,18 +58,12 @@
|
|||||||
|
|
||||||
<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">{{
|
||||||
walletAddressShort
|
walletAddressShort
|
||||||
}}</span>
|
}}</span>
|
||||||
<span v-else class="menu-arrow">></span>
|
<span v-else class="menu-arrow">></span>
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -1,27 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container
|
<v-container fluid class="trade-detail-container" :class="{
|
||||||
fluid
|
'trade-detail-container--settled-bar': showSettledOutcomeBar,
|
||||||
class="trade-detail-container"
|
}">
|
||||||
:class="{
|
|
||||||
'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"
|
!(pos.availableSharesNum != null && pos.availableSharesNum > 0)
|
||||||
size="small"
|
" @click="openSellFromPosition(pos)">
|
||||||
color="primary"
|
|
||||||
class="position-sell-btn"
|
|
||||||
:disabled="
|
|
||||||
currentMarketClosed ||
|
|
||||||
!(pos.availableSharesNum != null && pos.availableSharesNum > 0)
|
|
||||||
"
|
|
||||||
@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"
|
'rules-text--multi-collapsed':
|
||||||
:class="{
|
rulesDescriptionCollapsible && !rulesDescriptionExpanded,
|
||||||
'rules-text--multi-collapsed':
|
}">
|
||||||
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>
|
||||||
|
|
||||||
@ -571,10 +437,10 @@ async function loadEventDetail() {
|
|||||||
marketsCount: ev?.markets?.length ?? 0,
|
marketsCount: ev?.markets?.length ?? 0,
|
||||||
firstMarket: ev?.markets?.[0]
|
firstMarket: ev?.markets?.[0]
|
||||||
? {
|
? {
|
||||||
question: ev.markets[0].question,
|
question: ev.markets[0].question,
|
||||||
outcomePrices: ev.markets[0].outcomePrices,
|
outcomePrices: ev.markets[0].outcomePrices,
|
||||||
marketId: ev.markets[0].marketId,
|
marketId: ev.markets[0].marketId,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -1303,10 +1169,10 @@ function ensureChartSeries() {
|
|||||||
lastValueVisible: true,
|
lastValueVisible: true,
|
||||||
priceFormat: isCrypto
|
priceFormat: isCrypto
|
||||||
? {
|
? {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
minMove: 1e-10,
|
minMove: 1e-10,
|
||||||
formatter: (priceValue: BarPrice) => formatCryptoChartPrice(priceValue as number),
|
formatter: (priceValue: BarPrice) => formatCryptoChartPrice(priceValue as number),
|
||||||
}
|
}
|
||||||
: { type: 'percent', precision: 1 },
|
: { type: 'percent', precision: 1 },
|
||||||
priceScaleId: CHART_OVERLAY_PRICE_SCALE_ID,
|
priceScaleId: CHART_OVERLAY_PRICE_SCALE_ID,
|
||||||
})
|
})
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user