优化:UI调整
This commit is contained in:
parent
6e3981a637
commit
95d20a9bb1
31
src/App.vue
31
src/App.vue
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user