优化: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>
<v-app>
<v-app-bar color="primary" dark>
<div class="app-bar-inner">
<v-btn
v-if="currentRoute !== '/'"
icon
@ -101,20 +102,38 @@ watch(
</v-list>
</v-menu>
</template>
</div>
</v-app-bar>
<v-main>
<router-view v-slot="{ Component }">
<keep-alive :include="['Home']">
<component :is="Component" />
</keep-alive>
</router-view>
<div class="main-content">
<router-view v-slot="{ Component }">
<keep-alive :include="['Home']">
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</v-main>
<Toast />
</v-app>
</template>
<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 {
font-weight: bold;
text-decoration: underline;

View File

@ -14,14 +14,6 @@
</template>
<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-col cols="12" class="chart-col">
@ -34,6 +26,9 @@
>
<div class="chart-header">
<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">
<v-btn variant="text" size="small" class="past-btn">Past </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 {
margin-bottom: 24px;
@ -767,10 +775,17 @@ watch(
font-size: 1.25rem;
font-weight: 600;
color: #111827;
margin: 0 0 12px 0;
margin: 0 0 4px 0;
line-height: 1.3;
}
.chart-meta {
font-size: 14px;
color: #6b7280;
margin: 0 0 8px 0;
line-height: 1.4;
}
.chart-controls-row {
display: flex;
align-items: center;
@ -872,30 +887,6 @@ watch(
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 {
border: 1px solid #e7e7e7;
border-radius: 12px;

View File

@ -1,59 +1,32 @@
<template>
<v-container class="login-container">
<v-row justify="center" align="center" class="login-row">
<v-col cols="12" md="6" lg="4">
<v-card class="login-card">
<v-col cols="12" sm="10" md="6" lg="4">
<v-card class="login-card" elevation="0" rounded="lg">
<v-card-title class="login-title">Sign In</v-card-title>
<v-card-text>
<v-form @submit.prevent="handleLogin">
<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-card-text class="login-body">
<p class="login-desc">Connect your wallet to sign in</p>
<v-btn
class="wallet-btn"
color="success"
size="large"
block
rounded="lg"
@click="connectWithWallet"
:loading="isConnecting"
:disabled="isConnecting"
>
<v-icon start>mdi-wallet</v-icon>
{{ isConnecting ? 'Connecting...' : 'Connect with Wallet' }}
</v-btn>
<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>
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
<v-btn class="login-btn" color="primary" type="submit" block> Sign In </v-btn>
<div class="divider">
<span>or</span>
</div>
<v-btn
class="wallet-btn"
color="success"
block
@click="connectWithWallet"
:loading="isConnecting"
:disabled="isConnecting"
>
<span class="wallet-icon">🟡</span>
{{ isConnecting ? 'Connecting...' : 'Connect with Wallet' }}
</v-btn>
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
<div class="login-links">
<router-link to="/register">Don't have an account? Sign Up</router-link>
</div>
</v-form>
<div class="login-links">
<router-link to="/register">Don't have an account? Sign Up</router-link>
</div>
</v-card-text>
</v-card>
</v-col>
@ -72,32 +45,20 @@ import { post } from '@/api/request'
const router = useRouter()
const userStore = useUserStore()
const email = ref('')
const password = ref('')
const isConnecting = ref(false)
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 () => {
isConnecting.value = true
errorMessage.value = ''
try {
// Check if Ethereum wallet is installed
if (!window.ethereum) {
throw new Error(
'Ethereum wallet is not installed. Please install MetaMask, TokenPocket or another Ethereum wallet to continue.',
)
}
// Request account access
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts',
})
@ -105,69 +66,40 @@ const connectWithWallet = async () => {
const walletAddress = accounts[0]
console.log('Connected to wallet with account:', walletAddress)
// Validate wallet address format
if (!walletAddress || !/^0x[0-9a-fA-F]{40}$/.test(walletAddress)) {
throw new Error('Invalid wallet address format. Please check your wallet connection.')
}
// Get chain ID
const chainId = await window.ethereum.request({
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 domain = window.location.host
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 signer = await provider.getSigner()
const nonce = new Date().getTime().toString()
const siwe = new SiweMessage({
scheme,
domain,
address: signer.address,
statement,
statement: 'Sign in to PolyMarket',
uri: origin,
version: '1',
chainId: chainId,
nonce,
})
const message = siwe.prepareMessage()
// message http://
// message signer.address
// const lowerAddress = signer.address.toLowerCase()
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({
method: 'personal_sign',
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 } }>(
'/base/walletLogin',
{
@ -177,20 +109,13 @@ const connectWithWallet = async () => {
walletAddress,
},
)
//
console.log('[walletLogin] 登录响应:', JSON.stringify(loginData, null, 2))
if (loginData.code === 0 && loginData.data) {
const data = loginData.data as Record<string, unknown>
const token = data.token as string | undefined
// data.user data user data
const user =
(data.user 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 })
await userStore.fetchUserInfo()
await userStore.fetchUsdcBalance()
@ -218,72 +143,57 @@ const connectWithWallet = async () => {
}
.login-card {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 24px;
border-radius: 16px;
padding: 40px 32px;
border: 1px solid rgba(0, 0, 0, 0.08);
}
.login-title {
font-size: 1.5rem;
font-weight: bold;
color: #0066cc;
margin-bottom: 24px;
font-weight: 600;
color: #111827;
margin-bottom: 8px;
text-align: center;
}
.login-btn {
margin-top: 24px;
height: 48px;
.login-body {
padding-top: 8px;
}
.divider {
display: flex;
align-items: center;
justify-content: center;
margin: 24px 0;
}
.divider::before,
.divider::after {
content: '';
flex: 1;
height: 1px;
background-color: #e0e0e0;
}
.divider span {
padding: 0 16px;
color: #808080;
font-size: 14px;
.login-desc {
font-size: 0.95rem;
color: #6b7280;
text-align: center;
margin: 0 0 28px 0;
line-height: 1.5;
}
.wallet-btn {
height: 48px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.wallet-icon {
font-size: 16px;
font-weight: 600;
text-transform: none;
letter-spacing: 0.02em;
}
.error-message {
margin-top: 16px;
color: #ff0000;
margin-top: 20px;
padding: 12px;
color: #dc2626;
background: #fef2f2;
border-radius: 8px;
text-align: center;
font-size: 14px;
font-size: 0.875rem;
}
.login-links {
margin-top: 16px;
margin-top: 24px;
text-align: center;
}
.login-links a {
color: #0066cc;
color: #2563eb;
text-decoration: none;
font-size: 0.9rem;
}
.login-links a:hover {

View File

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