新增:登录页面

This commit is contained in:
ivan 2026-02-05 14:43:10 +08:00
parent bfe52f4db4
commit 3e0bf253be
4 changed files with 262 additions and 0 deletions

View File

@ -16,6 +16,7 @@ const currentRoute = computed(() => {
<v-spacer></v-spacer>
<v-btn text to="/" :class="{ active: currentRoute === '/' }"> Home </v-btn>
<v-btn text to="/trade" :class="{ active: currentRoute === '/trade' }"> Trade </v-btn>
<v-btn text to="/login" :class="{ active: currentRoute === '/login' }"> Login </v-btn>
</v-app-bar>
<v-main>
<router-view />

View File

@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Trade from '../views/Trade.vue'
import Login from '../views/Login.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -14,6 +15,11 @@ const router = createRouter({
path: '/trade',
name: 'trade',
component: Trade
},
{
path: '/login',
name: 'login',
component: Login
}
],
})

235
src/views/Login.vue Normal file
View File

@ -0,0 +1,235 @@
<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-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-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
class="metamask-btn"
color="success"
block
@click="connectWithMetaMask"
:loading="isConnecting"
:disabled="isConnecting"
>
<span class="metamask-icon">🟡</span>
{{ isConnecting ? 'Connecting...' : 'Connect with MetaMask' }}
</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>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
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 connectWithMetaMask = async () => {
isConnecting.value = true
errorMessage.value = ''
try {
// Check if MetaMask is installed
if (!window.ethereum) {
throw new Error('MetaMask is not installed. Please install it to continue.')
}
// Request account access
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts',
})
const walletAddress = accounts[0]
console.log('Connected to MetaMask with account:', walletAddress)
// Generate nonce for security
const nonce = Math.floor(Math.random() * 1000000).toString()
// Create message to sign
const message = `happy new year`
// Request signature
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message + nonce, walletAddress],
})
console.log('Signature:', signature)
console.log('Login data:', {
message,
nonce,
signature,
walletAddress,
})
// Call login API
const loginResponse = await fetch('https://api.xtrader.vip/base/walletLogin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
nonce,
signature,
walletAddress,
}),
})
if (!loginResponse.ok) {
throw new Error('Login failed. Please try again.')
}
const loginData = await loginResponse.json()
console.log('Login API response:', loginData)
// For demo purposes, we'll just navigate to the home page
// In a real application, you would store the token or session data here
router.push('/')
} catch (error: any) {
console.error('Error connecting to MetaMask:', error)
errorMessage.value = error.message || 'Failed to connect with MetaMask'
} finally {
isConnecting.value = false
}
}
</script>
<style scoped>
.login-container {
padding: 40px 20px;
min-height: 100vh;
}
.login-row {
min-height: calc(100vh - 80px);
}
.login-card {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 24px;
}
.login-title {
font-size: 1.5rem;
font-weight: bold;
color: #0066cc;
margin-bottom: 24px;
text-align: center;
}
.login-btn {
margin-top: 24px;
height: 48px;
}
.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;
}
.metamask-btn {
height: 48px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.metamask-icon {
font-size: 16px;
}
.error-message {
margin-top: 16px;
color: #ff0000;
text-align: center;
font-size: 14px;
}
.login-links {
margin-top: 16px;
text-align: center;
}
.login-links a {
color: #0066cc;
text-decoration: none;
}
.login-links a:hover {
text-decoration: underline;
}
</style>

20
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// Add MetaMask types
declare interface Window {
ethereum?: {
isMetaMask?: boolean
request: (args: {
method: string
params?: any[]
}) => Promise<any>
on: (event: string, callback: (...args: any[]) => void) => void
removeListener: (event: string, callback: (...args: any[]) => void) => void
}
}