You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue项目需求:点击Header登录按钮弹出模态框且不跳转页面

实现Home页面点击Header的Login按钮显示模态框(不跳转路由)

一、修改Login组件,让外部能控制模态框显示/隐藏

原Login组件的模态框状态是内部私有变量,外部无法控制。需要改为通过props接收状态、emit事件通知关闭的方式,实现父组件对模态框的控制:

<script setup>
import { ref, onMounted } from 'vue'
import VueCookies from 'vue-cookies'
import axios from 'axios';
import { useRouter } from 'vue-router'

// 定义props接收外部传递的显示状态
const props = defineProps({
  visible: {
    type: Boolean,
    default: false
  }
})
// 定义emit事件,通知父组件更新状态
const emit = defineEmits(['update:visible'])

const router = useRouter()
const baseURL = 'http://localhost:3000/'
const userURL = baseURL + "users";

const users = ref(null)
const email = ref(null)
const password = ref(null)
const emailError = ref(null)
const passwordError = ref(null)
const passwordValidation = ref(false)


onMounted(async () => {
    try {
            const res = await axios.get(baseURL + 'users');
            users.value = res.data;
        } catch (e) {
            console.error(e)
        }
})

async function loginSubmit() {
    // 修复拼写错误:lenght → length,添加空值判断
    if(email.value?.length === 0) {
        emailError.value = "Field is empty"
    } else {
        emailError.value = null
    }

    if(password.value?.length === 0) {
        passwordError.value = "Field is empty!";
    } else if(password.value.length > 0 && password.value.length < 8) { 
        passwordError.value = "Password is too short!"
    } else {
        passwordError.value = null
    }

    passwordValidation.value = false;
    // 遍历用户列表验证登录
    for (let i = 0; i < users.value?.length; i++) {
        if (users.value[i].email === email.value) {
            if (users.value[i].password === password.value) {
                passwordValidation.value = true;
                VueCookies.set('username', users.value[i].username, "120min");
                VueCookies.set('email', email.value, "120min");
                VueCookies.set('password', password.value, "120min");
                VueCookies.set('id', users.value[i].id, "120min");

                // 用vue-router跳转代替页面刷新
                router.push('/')
                alert("Login successful");
                // 关闭模态框
                emit('update:visible', false)
                break;
            }
        }
    }

    if (!passwordValidation.value) { 
        passwordError.value = "Incorrect password or username!"
    }
}
</script>

<template>
<!-- 通过v-model:visible实现双向绑定 -->
<el-dialog v-model:visible="visible" title="LogIn" width="50%" height="50%" center>
    <el-form label-position='top' status-icon :label-width="80">
        <el-form-item label="Email">
            <el-input type="email" id='email' placeholder="Enter Email" v-model="email" />
            <div class="input-message" v-if="emailError"><h6>{{emailError}}</h6></div>
        </el-form-item>

        <el-form-item label="Password">
            <el-input type="password" id='password' placeholder="Enter Password" v-model="password" />
            <div class="input-message" v-if="passwordError"><h6>{{passwordError}}</h6></div>
        </el-form-item>
    </el-form>
    <template #footer>
        <span class="dialog-footer">
            <el-button @click="emit('update:visible', false)">Cancel</el-button>
            <el-button type="primary" @click="loginSubmit()">
                Confirm
            </el-button>
        </span>
    </template>
</el-dialog>
</template>

<style>
</style>

二、修改Header组件,引入Login组件并管理模态框状态

移除跳转/login路由的按钮,新增模态框状态变量,引入并渲染Login组件:

<script setup>
import { Sunny, Moon } from '@element-plus/icons-vue'
import { ref, onMounted } from 'vue'
import { RouterLink } from 'vue-router'
import { pokeStore } from '../store/store'
import axios from 'axios';
import Pokedex from './Pokedex.vue'
// 引入Login组件
import Login from './Login.vue'

const pokedexVisible = ref(false)
// 新增控制登录模态框的状态
const loginModalVisible = ref(false)
const PokemonStore = pokeStore();

const allpokemons = ref([])

async function GetAllPokemons() {
  try {
    let response = await PokemonStore.getPokemonData();
    allpokemons.value = response.data.results;
    const randomPokemon = allpokemons.value[Math.floor(Math.random() * 151) + 1]
    console.log(randomPokemon)
  } catch (error) {
    throw error;
  }
}

GetAllPokemons()
</script>

<template>
    <el-header class="navbar">
        <div class="navbar-content">
            <div>
                <router-link to="/" custom v-slot="{ navigate }">
                    <img @click="navigate" role="link" class="logo" src="/src/assets/images/logo.png" />
                </router-link>
            </div>
            <el-space size="large">
                <div>
                    <input class="search" type="text" placeholder="Search pokemon" />
                </div>
                <div>
                    <el-button link><el-icon :size="20">
                            <Sunny />
                        </el-icon></el-button>
                    <el-button link><el-icon :size="20">
                            <Moon />
                        </el-icon></el-button>
                </div>
                <div>
                    <el-button @click="pokedexVisible = true" class="pokedexBtn">Pokedex</el-button>
                </div>
                <div>
                    <!-- 保留控制模态框的按钮,移除跳转路由的按钮 -->
                    <el-button @click="loginModalVisible = true" text>LogIn</el-button>
                </div>
            </el-space>
        </div>
    </el-header>

    <!-- 渲染Login组件,双向绑定显示状态 -->
    <Login v-model:visible="loginModalVisible" />
</template>

<style scoped>
.navbar {
  background-color: whitesmoke;
  padding: 5px 30px;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  opacity: 0.9;
}
.navbar-content{
    align-items: center;
    justify-content: space-between;
    display: flex;
}

.logo{
    width: 100%;
    max-width: 50px;
    display: block;
}
.search{
    display: block;
    padding: 5px;
    border-radius: 4px;
    font-size: 14px;
    width: 100%;
    background-color: transparent;
    float: right;
}

.search::placeholder {
  opacity: 0.5;
}

.navbar-right{
    align-items: center;
    justify-content: space-between;
}

.pokedexBtn{
    background-color: black;
    color: whitesmoke;
}
</style>

三、可选:调整路由配置(移除/login路由)

如果不需要单独的登录页面,可以从路由配置中删除Login相关路由:

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Pokedex from '../components/Pokedex.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/pokedex',
      name: 'Pokedex',
      component: Pokedex
    },
    // 移除Login路由项
    // {
    //   path: '/login',
    //   name: 'Login',
    //   component: Login
    // },
  ]
})

export default router

核心修改说明

  1. Login组件:将模态框状态改为外部可控,修复<script setup>中this的错误使用、拼写错误,用Vue Router跳转代替页面刷新,符合Vue响应式和单向数据流原则。
  2. Header组件:移除路由跳转按钮,新增模态框状态变量,引入Login组件并绑定状态,点击按钮直接触发模态框显示。
  3. 路由配置:移除/login路由,避免用户直接访问该路径,确保登录仅通过模态框完成。

内容的提问来源于stack exchange,提问作者Trpimir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 00:25:26