优化:UI调整

This commit is contained in:
ivan 2026-02-26 17:32:12 +08:00
parent 6e3981a637
commit 95d20a9bb1
4 changed files with 105 additions and 184 deletions

View File

@ -37,6 +37,7 @@ watch(
<template> <template>
<v-app> <v-app>
<v-app-bar color="primary" dark> <v-app-bar color="primary" dark>
<div class="app-bar-inner">
<v-btn <v-btn
v-if="currentRoute !== '/'" v-if="currentRoute !== '/'"
icon icon
@ -101,20 +102,38 @@ watch(
</v-list> </v-list>
</v-menu> </v-menu>
</template> </template>
</div>
</v-app-bar> </v-app-bar>
<v-main> <v-main>
<div class="main-content">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<keep-alive :include="['Home']"> <keep-alive :include="['Home']">
<component :is="Component" /> <component :is="Component" />
</keep-alive> </keep-alive>
</router-view> </router-view>
</div>
</v-main> </v-main>
<Toast /> <Toast />
</v-app> </v-app>
</template> </template>
<style scoped> <style scoped>
/* Global styles can be added here */ .app-bar-inner {
max-width: 1280px;
margin: 0 auto;
width: 100%;
display: flex;
align-items: center;
flex: 1;
padding: 0 16px;
}
.main-content {
max-width: 1280px;
margin: 0 auto;
width: 100%;
}
.active { .active {
font-weight: bold; font-weight: bold;
text-decoration: underline; text-decoration: underline;

View File

@ -14,14 +14,6 @@
</template> </template>
<template v-else-if="eventDetail"> <template v-else-if="eventDetail">
<div class="event-header">
<h1 class="event-title">{{ eventDetail.title }}</h1>
<p v-if="eventDetail.series?.length || eventDetail.tags?.length" class="event-meta">
{{ categoryText }}
</p>
<p v-if="eventVolume" class="event-volume">{{ eventVolume }}</p>
</div>
<v-row align="stretch" class="event-markets-row"> <v-row align="stretch" class="event-markets-row">
<!-- 左侧分时图 + 市场列表 --> <!-- 左侧分时图 + 市场列表 -->
<v-col cols="12" class="chart-col"> <v-col cols="12" class="chart-col">
@ -34,6 +26,9 @@
> >
<div class="chart-header"> <div class="chart-header">
<h1 class="chart-title">{{ eventDetail?.title || 'All markets' }}</h1> <h1 class="chart-title">{{ eventDetail?.title || 'All markets' }}</h1>
<p v-if="eventDetail.series?.length || eventDetail.tags?.length" class="chart-meta">
{{ categoryText }}
</p>
<div class="chart-controls-row"> <div class="chart-controls-row">
<v-btn variant="text" size="small" class="past-btn">Past </v-btn> <v-btn variant="text" size="small" class="past-btn">Past </v-btn>
<v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</v-btn> <v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</v-btn>
@ -749,6 +744,19 @@ watch(
} }
} }
/* 与列表页、单 market 页保持一致的左右间距 */
@media (max-width: 599px) {
.event-markets-container {
padding: 16px;
padding-left: 16px;
padding-right: 16px;
}
.chart-card.polymarket-chart {
padding: 16px;
}
}
/* 分时图卡片(扁平化) */ /* 分时图卡片(扁平化) */
.chart-card.polymarket-chart { .chart-card.polymarket-chart {
margin-bottom: 24px; margin-bottom: 24px;
@ -767,10 +775,17 @@ watch(
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;
color: #111827; color: #111827;
margin: 0 0 12px 0; margin: 0 0 4px 0;
line-height: 1.3; line-height: 1.3;
} }
.chart-meta {
font-size: 14px;
color: #6b7280;
margin: 0 0 8px 0;
line-height: 1.4;
}
.chart-controls-row { .chart-controls-row {
display: flex; display: flex;
align-items: center; align-items: center;
@ -872,30 +887,6 @@ watch(
color: #dc2626; color: #dc2626;
} }
.event-header {
margin-bottom: 24px;
}
.event-title {
font-size: 1.5rem;
font-weight: 600;
color: #111827;
margin: 0 0 8px 0;
line-height: 1.3;
}
.event-meta {
font-size: 14px;
color: #6b7280;
margin: 0 0 4px 0;
}
.event-volume {
font-size: 14px;
color: #6b7280;
margin: 0;
}
.markets-list-card { .markets-list-card {
border: 1px solid #e7e7e7; border: 1px solid #e7e7e7;
border-radius: 12px; border-radius: 12px;

View File

@ -1,48 +1,22 @@
<template> <template>
<v-container class="login-container"> <v-container class="login-container">
<v-row justify="center" align="center" class="login-row"> <v-row justify="center" align="center" class="login-row">
<v-col cols="12" md="6" lg="4"> <v-col cols="12" sm="10" md="6" lg="4">
<v-card class="login-card"> <v-card class="login-card" elevation="0" rounded="lg">
<v-card-title class="login-title">Sign In</v-card-title> <v-card-title class="login-title">Sign In</v-card-title>
<v-card-text> <v-card-text class="login-body">
<v-form @submit.prevent="handleLogin"> <p class="login-desc">Connect your wallet to sign in</p>
<v-text-field
v-model="email"
label="Email"
type="email"
required
:rules="[
(value) => !!value || 'Email is required',
(value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || 'Email is invalid',
]"
></v-text-field>
<v-text-field
v-model="password"
label="Password"
type="password"
required
:rules="[
(value) => !!value || 'Password is required',
(value) => value.length >= 6 || 'Password must be at least 6 characters',
]"
></v-text-field>
<v-btn class="login-btn" color="primary" type="submit" block> Sign In </v-btn>
<div class="divider">
<span>or</span>
</div>
<v-btn <v-btn
class="wallet-btn" class="wallet-btn"
color="success" color="success"
size="large"
block block
rounded="lg"
@click="connectWithWallet" @click="connectWithWallet"
:loading="isConnecting" :loading="isConnecting"
:disabled="isConnecting" :disabled="isConnecting"
> >
<span class="wallet-icon">🟡</span> <v-icon start>mdi-wallet</v-icon>
{{ isConnecting ? 'Connecting...' : 'Connect with Wallet' }} {{ isConnecting ? 'Connecting...' : 'Connect with Wallet' }}
</v-btn> </v-btn>
@ -53,7 +27,6 @@
<div class="login-links"> <div class="login-links">
<router-link to="/register">Don't have an account? Sign Up</router-link> <router-link to="/register">Don't have an account? Sign Up</router-link>
</div> </div>
</v-form>
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col> </v-col>
@ -72,32 +45,20 @@ import { post } from '@/api/request'
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const email = ref('')
const password = ref('')
const isConnecting = ref(false) const isConnecting = ref(false)
const errorMessage = ref('') const errorMessage = ref('')
const handleLogin = () => {
// Here you would typically make an API call to authenticate the user
console.log('Logging in with:', email.value, password.value)
// For demo purposes, we'll just navigate to the home page
router.push('/')
}
const connectWithWallet = async () => { const connectWithWallet = async () => {
isConnecting.value = true isConnecting.value = true
errorMessage.value = '' errorMessage.value = ''
try { try {
// Check if Ethereum wallet is installed
if (!window.ethereum) { if (!window.ethereum) {
throw new Error( throw new Error(
'Ethereum wallet is not installed. Please install MetaMask, TokenPocket or another Ethereum wallet to continue.', 'Ethereum wallet is not installed. Please install MetaMask, TokenPocket or another Ethereum wallet to continue.',
) )
} }
// Request account access
const accounts = await window.ethereum.request({ const accounts = await window.ethereum.request({
method: 'eth_requestAccounts', method: 'eth_requestAccounts',
}) })
@ -105,69 +66,40 @@ const connectWithWallet = async () => {
const walletAddress = accounts[0] const walletAddress = accounts[0]
console.log('Connected to wallet with account:', walletAddress) console.log('Connected to wallet with account:', walletAddress)
// Validate wallet address format
if (!walletAddress || !/^0x[0-9a-fA-F]{40}$/.test(walletAddress)) { if (!walletAddress || !/^0x[0-9a-fA-F]{40}$/.test(walletAddress)) {
throw new Error('Invalid wallet address format. Please check your wallet connection.') throw new Error('Invalid wallet address format. Please check your wallet connection.')
} }
// Get chain ID
const chainId = await window.ethereum.request({ const chainId = await window.ethereum.request({
method: 'eth_chainId', method: 'eth_chainId',
}) })
// Create Siwe-formatted message manually
// const scheme = window.location.protocol.slice(0, -1);
// const domain = 'polymarket.com';//window.location.host
// const uri = 'https://api.xtrader.vip/base/walletLogin';//window.location.origin
const scheme = window.location.protocol.slice(0, -1) const scheme = window.location.protocol.slice(0, -1)
const domain = window.location.host const domain = window.location.host
const origin = window.location.origin const origin = window.location.origin
const nonce = new Date().getTime().toString()
const statement = 'Sign in to PolyMarket'
const version = '1'
const issuedAt = new Date().toISOString()
const provider = new BrowserProvider(window.ethereum) const provider = new BrowserProvider(window.ethereum)
const signer = await provider.getSigner() const signer = await provider.getSigner()
const nonce = new Date().getTime().toString()
const siwe = new SiweMessage({ const siwe = new SiweMessage({
scheme, scheme,
domain, domain,
address: signer.address, address: signer.address,
statement, statement: 'Sign in to PolyMarket',
uri: origin, uri: origin,
version: '1', version: '1',
chainId: chainId, chainId: chainId,
nonce, nonce,
}) })
const message = siwe.prepareMessage() const message = siwe.prepareMessage()
// message http://
// message signer.address
// const lowerAddress = signer.address.toLowerCase()
const message1 = message.replace(/^https?:\/\//, '') const message1 = message.replace(/^https?:\/\//, '')
// console.log('Cleaned Siwe message:', cleanedMessage)
// const cleanedMessage = message1.replace(/^https?:\/\//, '')
// console.log('Cleaned Siwe message:', cleanedMessage)
// Generate nonce for security
// const nonce = siwe.nonce //Math.floor(Math.random() * 1000000).toString()
// Construct Siwe message according to EIP-4361 standard
// const message = `${domain} wants you to sign in with your Ethereum account:\n${walletAddress}\n\n${statement}\n\nURI: ${origin}\nVersion: ${version}\nChain ID: ${parseInt(chainId, 16)}\nNonce: ${nonce}\nIssued At: ${issuedAt}`
console.log('Generated Siwe-formatted message:', message1)
// Request signature
const signature = await window.ethereum.request({ const signature = await window.ethereum.request({
method: 'personal_sign', method: 'personal_sign',
params: [message1, signer.address], params: [message1, signer.address],
}) })
console.log('Signature:', signature)
// Call login API使 BASE_URL VITE_API_BASE_URL
const loginData = await post<{ code: number; data?: { token: string; user?: UserInfo } }>( const loginData = await post<{ code: number; data?: { token: string; user?: UserInfo } }>(
'/base/walletLogin', '/base/walletLogin',
{ {
@ -177,20 +109,13 @@ const connectWithWallet = async () => {
walletAddress, walletAddress,
}, },
) )
//
console.log('[walletLogin] 登录响应:', JSON.stringify(loginData, null, 2))
if (loginData.code === 0 && loginData.data) { if (loginData.code === 0 && loginData.data) {
const data = loginData.data as Record<string, unknown> const data = loginData.data as Record<string, unknown>
const token = data.token as string | undefined const token = data.token as string | undefined
// data.user data user data
const user = const user =
(data.user as UserInfo | undefined) ?? (data.user as UserInfo | undefined) ??
(data.ID != null || data.id != null || data.userName != null ? (data as unknown as UserInfo) : undefined) (data.ID != null || data.id != null || data.userName != null ? (data as unknown as UserInfo) : undefined)
if (!user) {
console.warn('[walletLogin] data 中无 user将依赖 fetchUserInfo 拉取。data 结构:', Object.keys(data))
}
console.log('[walletLogin] 存入 store 的 user:', JSON.stringify(user, null, 2))
userStore.setUser({ token, user }) userStore.setUser({ token, user })
await userStore.fetchUserInfo() await userStore.fetchUserInfo()
await userStore.fetchUsdcBalance() await userStore.fetchUsdcBalance()
@ -218,72 +143,57 @@ const connectWithWallet = async () => {
} }
.login-card { .login-card {
border-radius: 12px; border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); padding: 40px 32px;
padding: 24px; border: 1px solid rgba(0, 0, 0, 0.08);
} }
.login-title { .login-title {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: bold; font-weight: 600;
color: #0066cc; color: #111827;
margin-bottom: 24px; margin-bottom: 8px;
text-align: center; text-align: center;
} }
.login-btn { .login-body {
margin-top: 24px; padding-top: 8px;
height: 48px;
} }
.divider { .login-desc {
display: flex; font-size: 0.95rem;
align-items: center; color: #6b7280;
justify-content: center; text-align: center;
margin: 24px 0; margin: 0 0 28px 0;
} line-height: 1.5;
.divider::before,
.divider::after {
content: '';
flex: 1;
height: 1px;
background-color: #e0e0e0;
}
.divider span {
padding: 0 16px;
color: #808080;
font-size: 14px;
} }
.wallet-btn { .wallet-btn {
height: 48px; height: 48px;
display: flex; font-weight: 600;
align-items: center; text-transform: none;
justify-content: center; letter-spacing: 0.02em;
gap: 8px;
}
.wallet-icon {
font-size: 16px;
} }
.error-message { .error-message {
margin-top: 16px; margin-top: 20px;
color: #ff0000; padding: 12px;
color: #dc2626;
background: #fef2f2;
border-radius: 8px;
text-align: center; text-align: center;
font-size: 14px; font-size: 0.875rem;
} }
.login-links { .login-links {
margin-top: 16px; margin-top: 24px;
text-align: center; text-align: center;
} }
.login-links a { .login-links a {
color: #0066cc; color: #2563eb;
text-decoration: none; text-decoration: none;
font-size: 0.9rem;
} }
.login-links a:hover { .login-links a:hover {

View File

@ -45,7 +45,7 @@
</v-card> </v-card>
<!-- Order Book Section --> <!-- Order Book Section -->
<v-card class="order-book-card" elevation="0" rounded="lg" style="margin-top: 32px"> <v-card class="order-book-card" elevation="0" rounded="lg">
<OrderBook <OrderBook
:asks="orderBookAsks" :asks="orderBookAsks"
:bids="orderBookBids" :bids="orderBookBids"
@ -1097,7 +1097,7 @@ onUnmounted(() => {
/* Polymarket 样式分时图卡片(扁平化) */ /* Polymarket 样式分时图卡片(扁平化) */
.chart-card.polymarket-chart { .chart-card.polymarket-chart {
margin-top: 32px; margin-top: 0;
padding: 20px 24px 16px; padding: 20px 24px 16px;
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #e7e7e7; border: 1px solid #e7e7e7;
@ -1204,6 +1204,7 @@ onUnmounted(() => {
/* Order Book Card Styles扁平化 */ /* Order Book Card Styles扁平化 */
.order-book-card { .order-book-card {
margin-top: 16px;
padding: 0; padding: 0;
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #e7e7e7; border: 1px solid #e7e7e7;
@ -1323,13 +1324,13 @@ onUnmounted(() => {
/* Responsive order book adjustments */ /* Responsive order book adjustments */
@media (max-width: 600px) { @media (max-width: 600px) {
.order-book-card { .order-book-card {
margin-top: 24px; margin-top: 16px;
} }
} }
/* Comments / Top Holders / Activity扁平化 */ /* Comments / Top Holders / Activity扁平化 */
.activity-card { .activity-card {
margin-top: 32px; margin-top: 16px;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
overflow: hidden; overflow: hidden;
box-shadow: none; box-shadow: none;