Skip to main content

Web前端:路由

tip

本课程网站内容请仔细阅读后再进行实操。因未仔细阅读内容,出现任何错误后果自负(逃~~~逃~~~逃

所有的代码请不要复制粘贴,请手敲每一行代码。复制粘贴不会让你动脑子,而手敲每一个行代码会让你自然而然地去动脑子会想每一行代码的含义和原理

接下来,我们想要增加一个登陆注册的功能,这将是一个单独的页面,那么如何访问这个页面呢?我们将引入一个叫做路由的概念。

什么是路由

在传统的 Web 开发过程中,当需要实现多个站内页面时,要写很多个 HTML 页面,然后通过 <a /> 标签来实现互相跳转。

在如今工程化模式下的前端开发,像 Vue 工程,可以轻松实现只用一个 HTML 文件就完成多个站内页面渲染、跳转的功能,这就是路由。Vue 项目会根据用户访问的 URL 地址,将不同的页面或视图组件进行匹配和展示的机制。

在单页面应用(Single Page Application,SPA)中,页面的切换是通过路由来实现的,而不是通过传统的方式进行整个页面的刷新。这样可以提升用户体验,减少页面刷新的延迟,并允许在不同的页面之间保持一些状态。

路由的安装

npm install vue-router@4

路由的使用

一般来说路由的管理是放在 src/router 这个目录下:

src

.
.
.
├─router # 路由目录
│ # 路由配置,如果路由很多,可以再拆分模块文件
├───index.js
│ # 项目入口文件
└─main.js

增加页面组件

新增 src/views/HomeView.vue文件,输入如下代码:

src/views/HomeView.vue
<script setup>
</script>
<template>
<header class="bg-white">
<div class="mx-auto max-w-screen-xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<div class="md:flex md:items-center md:gap-12">
<a class="block text-teal-600" href="#">
<span class="sr-only">主页</span>
<svg class="h-8" viewBox="0 0 28 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M0.41 10.3847C1.14777 7.4194 2.85643 4.7861 5.2639 2.90424C7.6714 1.02234 10.6393 0 13.695 0C16.7507 0 19.7186 1.02234 22.1261 2.90424C24.5336 4.7861 26.2422 7.4194 26.98 10.3847H25.78C23.7557 10.3549 21.7729 10.9599 20.11 12.1147C20.014 12.1842 19.9138 12.2477 19.81 12.3047H19.67C19.5662 12.2477 19.466 12.1842 19.37 12.1147C17.6924 10.9866 15.7166 10.3841 13.695 10.3841C11.6734 10.3841 9.6976 10.9866 8.02 12.1147C7.924 12.1842 7.8238 12.2477 7.72 12.3047H7.58C7.4762 12.2477 7.376 12.1842 7.28 12.1147C5.6171 10.9599 3.6343 10.3549 1.61 10.3847H0.41ZM23.62 16.6547C24.236 16.175 24.9995 15.924 25.78 15.9447H27.39V12.7347H25.78C24.4052 12.7181 23.0619 13.146 21.95 13.9547C21.3243 14.416 20.5674 14.6649 19.79 14.6649C19.0126 14.6649 18.2557 14.416 17.63 13.9547C16.4899 13.1611 15.1341 12.7356 13.745 12.7356C12.3559 12.7356 11.0001 13.1611 9.86 13.9547C9.2343 14.416 8.4774 14.6649 7.7 14.6649C6.9226 14.6649 6.1657 14.416 5.54 13.9547C4.4144 13.1356 3.0518 12.7072 1.66 12.7347H0V15.9447H1.61C2.39051 15.924 3.154 16.175 3.77 16.6547C4.908 17.4489 6.2623 17.8747 7.65 17.8747C9.0377 17.8747 10.392 17.4489 11.53 16.6547C12.1468 16.1765 12.9097 15.9257 13.69 15.9447C14.4708 15.9223 15.2348 16.1735 15.85 16.6547C16.9901 17.4484 18.3459 17.8738 19.735 17.8738C21.1241 17.8738 22.4799 17.4484 23.62 16.6547ZM23.62 22.3947C24.236 21.915 24.9995 21.664 25.78 21.6847H27.39V18.4747H25.78C24.4052 18.4581 23.0619 18.886 21.95 19.6947C21.3243 20.156 20.5674 20.4049 19.79 20.4049C19.0126 20.4049 18.2557 20.156 17.63 19.6947C16.4899 18.9011 15.1341 18.4757 13.745 18.4757C12.3559 18.4757 11.0001 18.9011 9.86 19.6947C9.2343 20.156 8.4774 20.4049 7.7 20.4049C6.9226 20.4049 6.1657 20.156 5.54 19.6947C4.4144 18.8757 3.0518 18.4472 1.66 18.4747H0V21.6847H1.61C2.39051 21.664 3.154 21.915 3.77 22.3947C4.908 23.1889 6.2623 23.6147 7.65 23.6147C9.0377 23.6147 10.392 23.1889 11.53 22.3947C12.1468 21.9165 12.9097 21.6657 13.69 21.6847C14.4708 21.6623 15.2348 21.9135 15.85 22.3947C16.9901 23.1884 18.3459 23.6138 19.735 23.6138C21.1241 23.6138 22.4799 23.1884 23.62 22.3947Z"
fill="currentColor"
/>
</svg>
</a>
</div>

<div class="hidden md:block">
<nav aria-label="Global">
<ul class="flex items-center gap-6 text-sm">
<li>
<a class="text-gray-500 transition hover:text-gray-500/75" href="#"> Home </a>
</li>

<li>
<a class="text-gray-500 transition hover:text-gray-500/75" href="#"> Todos </a>
</li>
</ul>
</nav>
</div>

<div class="flex items-center gap-4">
<div class="sm:flex sm:gap-4">
<a
class="rounded-md bg-teal-600 px-5 py-2.5 text-sm font-medium text-white shadow"
href="#"
>
登录
</a>

<div class="hidden sm:flex">
<a
class="rounded-md bg-gray-100 px-5 py-2.5 text-sm font-medium text-teal-600"
href="#"
>
注册
</a>
</div>
</div>

<div class="block md:hidden">
<button class="rounded bg-gray-100 p-2 text-gray-600 transition hover:text-gray-600/75">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</div>
</div>
</div>
</header>
</template>

<style scoped></style>

新增 src/views/LoginView.vue文件,输入如下代码:

src/views/LoginView.vue
<script setup>
</script>

<template>
<div class="mx-auto max-w-screen-xl px-4 py-16 ">
<div class="mx-auto max-w-lg">
<h1 class="text-center text-2xl font-bold text-indigo-600">Todo Application</h1>

<form action="#" class="mb-0 mt-6 space-y-4 rounded-lg p-4 shadow-lg">
<p class="text-center text-lg font-medium">账号登录</p>

<div>
<label for="email" class="sr-only">邮箱</label>

<div class="relative">
<input
type="email"
class="w-full rounded-lg border-gray-200 p-4 pe-12 text-sm shadow-sm"
placeholder="Enter email"
/>

<span class="absolute inset-y-0 end-0 grid place-content-center px-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="size-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"
/>
</svg>
</span>
</div>
</div>

<div>
<label for="password" class="sr-only">密码</label>

<div class="relative">
<input
type="password"
class="w-full rounded-lg border-gray-200 p-4 pe-12 text-sm shadow-sm"
placeholder="Enter password"
/>

<span class="absolute inset-y-0 end-0 grid place-content-center px-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="size-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
</span>
</div>
</div>

<button
type="submit"
class="block w-full rounded-lg bg-indigo-600 px-5 py-3 text-sm font-medium text-white"
>
登录
</button>

<p class="text-center text-sm text-gray-500">
没有账号
<a class="underline" href="#">注册</a>
</p>
</form>
</div>
</div>
</template>

<style scoped></style>

新增 src/views/SignupView.vue文件,输入如下代码:

src/views/SignupView.vue
<script setup>
</script>

<template>
<section class="bg-white">
<div class="grid min-h-screen grid-cols-12">
<aside class="relative block h-16 lg:order-last lg:col-span-5 lg:h-full xl:col-span-6">
<img
alt=""
src="https://images.unsplash.com/photo-1605106702734-205df224ecce?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80"
class="absolute inset-0 h-full w-full object-cover"
/>
</aside>

<main
class="flex items-center justify-center px-8 py-8 lg:col-span-7 lg:px-16 lg:py-12 xl:col-span-6"
>
<div class="max-w-xl lg:max-w-3xl">
<a class="block text-blue-600" href="#">
<span class="sr-only">Home</span>
<svg
class="h-8 sm:h-10"
viewBox="0 0 28 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.41 10.3847C1.14777 7.4194 2.85643 4.7861 5.2639 2.90424C7.6714 1.02234 10.6393 0 13.695 0C16.7507 0 19.7186 1.02234 22.1261 2.90424C24.5336 4.7861 26.2422 7.4194 26.98 10.3847H25.78C23.7557 10.3549 21.7729 10.9599 20.11 12.1147C20.014 12.1842 19.9138 12.2477 19.81 12.3047H19.67C19.5662 12.2477 19.466 12.1842 19.37 12.1147C17.6924 10.9866 15.7166 10.3841 13.695 10.3841C11.6734 10.3841 9.6976 10.9866 8.02 12.1147C7.924 12.1842 7.8238 12.2477 7.72 12.3047H7.58C7.4762 12.2477 7.376 12.1842 7.28 12.1147C5.6171 10.9599 3.6343 10.3549 1.61 10.3847H0.41ZM23.62 16.6547C24.236 16.175 24.9995 15.924 25.78 15.9447H27.39V12.7347H25.78C24.4052 12.7181 23.0619 13.146 21.95 13.9547C21.3243 14.416 20.5674 14.6649 19.79 14.6649C19.0126 14.6649 18.2557 14.416 17.63 13.9547C16.4899 13.1611 15.1341 12.7356 13.745 12.7356C12.3559 12.7356 11.0001 13.1611 9.86 13.9547C9.2343 14.416 8.4774 14.6649 7.7 14.6649C6.9226 14.6649 6.1657 14.416 5.54 13.9547C4.4144 13.1356 3.0518 12.7072 1.66 12.7347H0V15.9447H1.61C2.39051 15.924 3.154 16.175 3.77 16.6547C4.908 17.4489 6.2623 17.8747 7.65 17.8747C9.0377 17.8747 10.392 17.4489 11.53 16.6547C12.1468 16.1765 12.9097 15.9257 13.69 15.9447C14.4708 15.9223 15.2348 16.1735 15.85 16.6547C16.9901 17.4484 18.3459 17.8738 19.735 17.8738C21.1241 17.8738 22.4799 17.4484 23.62 16.6547ZM23.62 22.3947C24.236 21.915 24.9995 21.664 25.78 21.6847H27.39V18.4747H25.78C24.4052 18.4581 23.0619 18.886 21.95 19.6947C21.3243 20.156 20.5674 20.4049 19.79 20.4049C19.0126 20.4049 18.2557 20.156 17.63 19.6947C16.4899 18.9011 15.1341 18.4757 13.745 18.4757C12.3559 18.4757 11.0001 18.9011 9.86 19.6947C9.2343 20.156 8.4774 20.4049 7.7 20.4049C6.9226 20.4049 6.1657 20.156 5.54 19.6947C4.4144 18.8757 3.0518 18.4472 1.66 18.4747H0V21.6847H1.61C2.39051 21.664 3.154 21.915 3.77 22.3947C4.908 23.1889 6.2623 23.6147 7.65 23.6147C9.0377 23.6147 10.392 23.1889 11.53 22.3947C12.1468 21.9165 12.9097 21.6657 13.69 21.6847C14.4708 21.6623 15.2348 21.9135 15.85 22.3947C16.9901 23.1884 18.3459 23.6138 19.735 23.6138C21.1241 23.6138 22.4799 23.1884 23.62 22.3947Z"
fill="currentColor"
/>
</svg>
</a>

<h1 class="mt-6 text-2xl font-bold text-gray-900 sm:text-3xl md:text-4xl">
欢迎来到Todo Application
</h1>

<p class="mt-4 leading-relaxed text-gray-500">
账号注册
</p>

<form action="#" class="mt-8 grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label for="FirstName" class="block text-sm font-medium text-gray-700">
Name
</label>

<input
type="text"
id="FirstName"
name="first_name"
class="mt-1 w-full h-10 rounded-md border-gray-200 bg-white text-sm text-gray-700 shadow-sm"
/>
</div>


<div class="col-span-6">
<label for="Email" class="block text-sm font-medium text-gray-700"> Email </label>

<input
type="email"
id="Email"
name="email"
class="mt-1 w-full h-10 rounded-md border-gray-200 bg-white text-sm text-gray-700 shadow-sm"
/>
</div>

<div class="col-span-6 sm:col-span-3">
<label for="Password" class="block text-sm font-medium text-gray-700"> Password </label>

<input
type="password"
id="Password"
name="password"
class="mt-1 w-full h-10 rounded-md border-gray-200 bg-white text-sm text-gray-700 shadow-sm"
/>
</div>

<div class="col-span-6 sm:col-span-3">
<label for="PasswordConfirmation" class="block text-sm font-medium text-gray-700">
Password Confirmation
</label>

<input
type="password"
id="PasswordConfirmation"
name="password_confirmation"
class="mt-1 w-full h-10 rounded-md border-gray-200 bg-white text-sm text-gray-700 shadow-sm"
/>
</div>

<div class="col-span-6 sm:flex sm:items-center sm:gap-4">
<button
class="inline-block shrink-0 rounded-md border border-blue-600 bg-blue-600 px-12 py-3 text-sm font-medium text-white transition hover:bg-transparent hover:text-blue-600 focus:outline-none focus:ring active:text-blue-500"
>
新建一个账号
</button>

<p class="mt-4 text-sm text-gray-500 sm:mt-0">
已经有账号了?
<a href="#" class="text-gray-700 underline">登录</a>.
</p>
</div>
</form>
</div>
</main>
</div>
</section>
</template>
<style scoped></style>

新增 src/views/NotFoundView.vue文件,输入如下代码:

src/views/NotFoundView.vue
<script setup>
</script>
<template>
<div class="grid h-screen place-content-center bg-white px-4">
<div class="text-center">
<h1 class="text-9xl font-black text-gray-200">404</h1>

<p class="text-2xl font-bold tracking-tight text-gray-900 sm:text-4xl">Uh-oh!</p>

<p class="mt-4 text-gray-500">We can't find that page.</p>

<a
href="#"
class="mt-6 inline-block rounded bg-indigo-600 px-5 py-3 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring"
>
Go Back Home
</a>
</div>
</div>
</template>

<style scoped></style>

配置路由

新建 src/router/index.js 文件,输入如下代码

src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import TodoView from "../views/TodoView.vue";
import LoginView from "../views/LoginView.vue";
import SignupView from "../views/SignupView.vue";
import NotFoundView from "../views/NotFoundView.vue";

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView
},
{
path: "/todo",
name: "todo",
component: TodoView
},
{
path: "/login",
name: "login",
component: LoginView
},
{
path: "/signup",
name: "signup",
component: SignupView
},
{
path: '/:pathMatch(.*)*',
name: 'not_found',
component: NotFoundView
},
]
})

export default router;
  1. 调用 createRouter 函数来创建一个路由实例。 history 属性使用 createWebHistory 函数,传入 import.meta.env.BASE_URL,这可以根据应用的环境变量配置基础 URL。

  2. routes 属性定义了路由的配置数组,其中每个对象代表一个路由规则。 每个路由规则的属性包括:

  • path: 路由路径,例如 / 对应主页。
  • name: 路由名称,用于标识路由。
  • component: 对应的组件,当路径匹配时,将渲染这个组件。
  1. 特别说明: /:pathMatch(.*)*: 这个路由模式匹配所有未定义的路径,使用 NotFoundView 组件进行处理。

Vue 应用中引入路由

src\main.js中写入

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from "pinia"

import router from "./router/index.js";
const app = createApp(App);
app.use(router);
app.use(createPinia());
app.mount('#app');

现在已经配置好了路由节点,接下来在App.vue中使用。

请将 src/App.vue 中的代码修改成如下:

src/App.vue
<script setup>
import { RouterView } from "vue-router";
</script>

<template>
<RouterView />
</template>

<style scoped></style>

RouterView 是 Vue Router 提供的组件,是用于渲染当前路由匹配组件的组件。

这种结构允许你在页面中定义多个URL, 每个URL指向不同的路由, 而 RouterView 则根据当前路由动态地显示相应的组件, 实现了 SPA 中的页面切换效果。

RouterLink的使用

RouteLink 是 Vue Router 中的一个组件,用于在应用中创建可导航的链接。 它的主要作用是渲染一个可点击的链接(通常是 <a> 标签), 点击该链接后会根据配置的路由进行导航,而不需要刷新页面。

修改 src/views/HomeView.vue 代码

src/views/HomeView.vue
<script setup>
import { RouterLink } from 'vue-router';
</script>

<template>
<header class="bg-white">
<div class="mx-auto max-w-screen-xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<div class="md:flex md:items-center md:gap-12">
<RouterLink to="/" class="block text-teal-600">
<svg class="h-8" viewBox="0 0 28 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M0.41 10.3847C1.14777 7.4194 2.85643 4.7861 5.2639 2.90424C7.6714 1.02234 10.6393 0 13.695 0C16.7507 0 19.7186 1.02234 22.1261 2.90424C24.5336 4.7861 26.2422 7.4194 26.98 10.3847H25.78C23.7557 10.3549 21.7729 10.9599 20.11 12.1147C20.014 12.1842 19.9138 12.2477 19.81 12.3047H19.67C19.5662 12.2477 19.466 12.1842 19.37 12.1147C17.6924 10.9866 15.7166 10.3841 13.695 10.3841C11.6734 10.3841 9.6976 10.9866 8.02 12.1147C7.924 12.1842 7.8238 12.2477 7.72 12.3047H7.58C7.4762 12.2477 7.376 12.1842 7.28 12.1147C5.6171 10.9599 3.6343 10.3549 1.61 10.3847H0.41ZM23.62 16.6547C24.236 16.175 24.9995 15.924 25.78 15.9447H27.39V12.7347H25.78C24.4052 12.7181 23.0619 13.146 21.95 13.9547C21.3243 14.416 20.5674 14.6649 19.79 14.6649C19.0126 14.6649 18.2557 14.416 17.63 13.9547C16.4899 13.1611 15.1341 12.7356 13.745 12.7356C12.3559 12.7356 11.0001 13.1611 9.86 13.9547C9.2343 14.416 8.4774 14.6649 7.7 14.6649C6.9226 14.6649 6.1657 14.416 5.54 13.9547C4.4144 13.1356 3.0518 12.7072 1.66 12.7347H0V15.9447H1.61C2.39051 15.924 3.154 16.175 3.77 16.6547C4.908 17.4489 6.2623 17.8747 7.65 17.8747C9.0377 17.8747 10.392 17.4489 11.53 16.6547C12.1468 16.1765 12.9097 15.9257 13.69 15.9447C14.4708 15.9223 15.2348 16.1735 15.85 16.6547C16.9901 17.4484 18.3459 17.8738 19.735 17.8738C21.1241 17.8738 22.4799 17.4484 23.62 16.6547ZM23.62 22.3947C24.236 21.915 24.9995 21.664 25.78 21.6847H27.39V18.4747H25.78C24.4052 18.4581 23.0619 18.886 21.95 19.6947C21.3243 20.156 20.5674 20.4049 19.79 20.4049C19.0126 20.4049 18.2557 20.156 17.63 19.6947C16.4899 18.9011 15.1341 18.4757 13.745 18.4757C12.3559 18.4757 11.0001 18.9011 9.86 19.6947C9.2343 20.156 8.4774 20.4049 7.7 20.4049C6.9226 20.4049 6.1657 20.156 5.54 19.6947C4.4144 18.8757 3.0518 18.4472 1.66 18.4747H0V21.6847H1.61C2.39051 21.664 3.154 21.915 3.77 22.3947C4.908 23.1889 6.2623 23.6147 7.65 23.6147C9.0377 23.6147 10.392 23.1889 11.53 22.3947C12.1468 21.9165 12.9097 21.6657 13.69 21.6847C14.4708 21.6623 15.2348 21.9135 15.85 22.3947C16.9901 23.1884 18.3459 23.6138 19.735 23.6138C21.1241 23.6138 22.4799 23.1884 23.62 22.3947Z"
fill="currentColor"
/>
</svg>
</RouterLink>
</div>

<div class="hidden md:block">
<nav aria-label="Global">
<ul class="flex items-center gap-6 text-sm">
<li>
<RouterLink to="/" class="text-gray-500 transition hover:text-gray-500/75"> Home </RouterLink>
</li>

<li>
<RouterLink to="/todo" class="text-gray-500 transition hover:text-gray-500/75"> Todo </RouterLink>
</li>
</ul>
</nav>
</div>

<div class="flex items-center gap-4">
<div class="sm:flex sm:gap-4">
<RouterLink to="/login"
class="rounded-md bg-teal-600 px-5 py-2.5 text-sm font-medium text-white shadow"
>
登录
</RouterLink>

<div class="hidden sm:flex">
<RouterLink to="/signup"
class="rounded-md bg-gray-100 px-5 py-2.5 text-sm font-medium text-teal-600"
>
注册
</RouterLink>
</div>
</div>

<div class="block md:hidden">
<button class="rounded bg-gray-100 p-2 text-gray-600 transition hover:text-gray-600/75">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</div>
</div>
</div>
</header>
</template>

<style scoped></style>

修改 src/views/LoginView.vue 代码

src/views/LoginView.vue
<script setup>
import { RouterLink } from 'vue-router';
</script>

<template>
<div class="mx-auto max-w-screen-xl px-4 py-16 ">
<div class="mx-auto max-w-lg">
<h1 class="text-center text-2xl font-bold text-indigo-600">Todo Application</h1>

<form action="#" class="mb-0 mt-6 space-y-4 rounded-lg p-4 shadow-lg">
<p class="text-center text-lg font-medium">账号登录</p>

<div>
<label for="email" class="sr-only">邮箱</label>

<div class="relative">
<input
type="email"
class="w-full rounded-lg border-gray-200 p-4 pe-12 text-sm shadow-sm"
placeholder="Enter email"
/>

<span class="absolute inset-y-0 end-0 grid place-content-center px-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="size-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"
/>
</svg>
</span>
</div>
</div>

<div>
<label for="password" class="sr-only">密码</label>

<div class="relative">
<input
type="password"
class="w-full rounded-lg border-gray-200 p-4 pe-12 text-sm shadow-sm"
placeholder="Enter password"
/>

<span class="absolute inset-y-0 end-0 grid place-content-center px-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="size-4 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
</span>
</div>
</div>

<button
type="submit"
class="block w-full rounded-lg bg-indigo-600 px-5 py-3 text-sm font-medium text-white"
>
登录
</button>

<p class="text-center text-sm text-gray-500">
没有账号
<RouterLink to="/signup" class="underline">注册</RouterLink>
</p>
</form>
</div>
</div>
</template>

<style scoped></style>

修改 src/views/SignupView.vue代码:

src/views/SignupView.vue
<script setup>
import { RouterLink } from 'vue-router';
</script>

<template>
<section class="bg-white">
<div class="grid min-h-screen grid-cols-12">
<aside class="relative block h-16 lg:order-last lg:col-span-5 lg:h-full xl:col-span-6">
<img
alt=""
src="https://images.unsplash.com/photo-1605106702734-205df224ecce?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80"
class="absolute inset-0 h-full w-full object-cover"
/>
</aside>

<main
class="flex items-center justify-center px-8 py-8 lg:col-span-7 lg:px-16 lg:py-12 xl:col-span-6"
>
<div class="max-w-xl lg:max-w-3xl">
<a class="block text-blue-600" href="#">
<span class="sr-only">Home</span>
<svg
class="h-8 sm:h-10"
viewBox="0 0 28 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.41 10.3847C1.14777 7.4194 2.85643 4.7861 5.2639 2.90424C7.6714 1.02234 10.6393 0 13.695 0C16.7507 0 19.7186 1.02234 22.1261 2.90424C24.5336 4.7861 26.2422 7.4194 26.98 10.3847H25.78C23.7557 10.3549 21.7729 10.9599 20.11 12.1147C20.014 12.1842 19.9138 12.2477 19.81 12.3047H19.67C19.5662 12.2477 19.466 12.1842 19.37 12.1147C17.6924 10.9866 15.7166 10.3841 13.695 10.3841C11.6734 10.3841 9.6976 10.9866 8.02 12.1147C7.924 12.1842 7.8238 12.2477 7.72 12.3047H7.58C7.4762 12.2477 7.376 12.1842 7.28 12.1147C5.6171 10.9599 3.6343 10.3549 1.61 10.3847H0.41ZM23.62 16.6547C24.236 16.175 24.9995 15.924 25.78 15.9447H27.39V12.7347H25.78C24.4052 12.7181 23.0619 13.146 21.95 13.9547C21.3243 14.416 20.5674 14.6649 19.79 14.6649C19.0126 14.6649 18.2557 14.416 17.63 13.9547C16.4899 13.1611 15.1341 12.7356 13.745 12.7356C12.3559 12.7356 11.0001 13.1611 9.86 13.9547C9.2343 14.416 8.4774 14.6649 7.7 14.6649C6.9226 14.6649 6.1657 14.416 5.54 13.9547C4.4144 13.1356 3.0518 12.7072 1.66 12.7347H0V15.9447H1.61C2.39051 15.924 3.154 16.175 3.77 16.6547C4.908 17.4489 6.2623 17.8747 7.65 17.8747C9.0377 17.8747 10.392 17.4489 11.53 16.6547C12.1468 16.1765 12.9097 15.9257 13.69 15.9447C14.4708 15.9223 15.2348 16.1735 15.85 16.6547C16.9901 17.4484 18.3459 17.8738 19.735 17.8738C21.1241 17.8738 22.4799 17.4484 23.62 16.6547ZM23.62 22.3947C24.236 21.915 24.9995 21.664 25.78 21.6847H27.39V18.4747H25.78C24.4052 18.4581 23.0619 18.886 21.95 19.6947C21.3243 20.156 20.5674 20.4049 19.79 20.4049C19.0126 20.4049 18.2557 20.156 17.63 19.6947C16.4899 18.9011 15.1341 18.4757 13.745 18.4757C12.3559 18.4757 11.0001 18.9011 9.86 19.6947C9.2343 20.156 8.4774 20.4049 7.7 20.4049C6.9226 20.4049 6.1657 20.156 5.54 19.6947C4.4144 18.8757 3.0518 18.4472 1.66 18.4747H0V21.6847H1.61C2.39051 21.664 3.154 21.915 3.77 22.3947C4.908 23.1889 6.2623 23.6147 7.65 23.6147C9.0377 23.6147 10.392 23.1889 11.53 22.3947C12.1468 21.9165 12.9097 21.6657 13.69 21.6847C14.4708 21.6623 15.2348 21.9135 15.85 22.3947C16.9901 23.1884 18.3459 23.6138 19.735 23.6138C21.1241 23.6138 22.4799 23.1884 23.62 22.3947Z"
fill="currentColor"
/>
</svg>
</a>

<h1 class="mt-6 text-2xl font-bold text-gray-900 sm:text-3xl md:text-4xl">
欢迎来到Todo Application
</h1>

<p class="mt-4 leading-relaxed text-gray-500">
账号注册
</p>

<form action="#" class="mt-8 grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label for="FirstName" class="block text-sm font-medium text-gray-700">
Name
</label>

<input
type="text"
id="FirstName"
name="first_name"
class="mt-1 w-full h-10 rounded-md border-gray-200 bg-white text-sm text-gray-700 shadow-sm"
/>
</div>


<div class="col-span-6">
<label for="Email" class="block text-sm font-medium text-gray-700"> Email </label>

<input
type="email"
id="Email"
name="email"
class="mt-1 w-full h-10 rounded-md border-gray-200 bg-white text-sm text-gray-700 shadow-sm"
/>
</div>

<div class="col-span-6 sm:col-span-3">
<label for="Password" class="block text-sm font-medium text-gray-700"> Password </label>

<input
type="password"
id="Password"
name="password"
class="mt-1 w-full h-10 rounded-md border-gray-200 bg-white text-sm text-gray-700 shadow-sm"
/>
</div>

<div class="col-span-6 sm:col-span-3">
<label for="PasswordConfirmation" class="block text-sm font-medium text-gray-700">
Password Confirmation
</label>

<input
type="password"
id="PasswordConfirmation"
name="password_confirmation"
class="mt-1 w-full h-10 rounded-md border-gray-200 bg-white text-sm text-gray-700 shadow-sm"
/>
</div>

<div class="col-span-6 sm:flex sm:items-center sm:gap-4">
<button
class="inline-block shrink-0 rounded-md border border-blue-600 bg-blue-600 px-12 py-3 text-sm font-medium text-white transition hover:bg-transparent hover:text-blue-600 focus:outline-none focus:ring active:text-blue-500"
>
新建一个账号
</button>

<p class="mt-4 text-sm text-gray-500 sm:mt-0">
已经有账号了?
<RouterLink to="/login" class="text-gray-700 underline">登录</RouterLink>.
</p>
</div>
</form>
</div>
</main>
</div>
</section>
</template>
<style scoped></style>