新增:登录页面
This commit is contained in:
parent
bfe52f4db4
commit
3e0bf253be
@ -16,6 +16,7 @@ const currentRoute = computed(() => {
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn text to="/" :class="{ active: currentRoute === '/' }"> Home </v-btn>
|
<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="/trade" :class="{ active: currentRoute === '/trade' }"> Trade </v-btn>
|
||||||
|
<v-btn text to="/login" :class="{ active: currentRoute === '/login' }"> Login </v-btn>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<v-main>
|
<v-main>
|
||||||
<router-view />
|
<router-view />
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import Home from '../views/Home.vue'
|
import Home from '../views/Home.vue'
|
||||||
import Trade from '../views/Trade.vue'
|
import Trade from '../views/Trade.vue'
|
||||||
|
import Login from '../views/Login.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@ -14,6 +15,11 @@ const router = createRouter({
|
|||||||
path: '/trade',
|
path: '/trade',
|
||||||
name: 'trade',
|
name: 'trade',
|
||||||
component: Trade
|
component: Trade
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: Login
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
235
src/views/Login.vue
Normal file
235
src/views/Login.vue
Normal 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
20
src/vite-env.d.ts
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user