文章大纲
加载中...

待办清单项目10:使用Vue3和Vite重构前端

4/28/2025 322 阅读
待办清单项目10:使用Vue3和Vite重构前端

为什么要重构前端代码

  1. 前端代码写得太啰嗦,像讲故事绕弯子
  2. 前端代码各部分粘在一起,像一团乱麻
  3. 前端代码改起来很头疼,像修补破旧衣服
  4. 前端代码难以重复使用,像一次性筷子
  5. 前端代码难以搬到其他地方用,像搬家时的大件家具
  6. 前端代码难以部署上线,像组装复杂的玩具

Vue3简介

Vue3是什么?Vue3是一个前端框架,就像是一个乐高积木套装,帮你轻松搭建漂亮的网页。
Vue3的创造者是尤雨溪,他就像是中国的"前端钢铁侠",为全世界的程序员创造了这个好用的工具。
官方网站:https://cn.vuejs.org/

Vue3有哪些厉害的地方?

  1. 组合式API - 就像厨房里的模块化橱柜,你可以根据需要组合不同的功能,而不是所有东西都塞在一个大抽屉里

  2. 速度更快 - 就像换了一台更快的电脑,网页加载和响应的速度明显提升

  3. 工具更好用 - 就像升级了工具箱,有了更好的扳手、螺丝刀,开发起来更顺手

  4. 兼容性更好 - 就像一把万能钥匙,能适应更多的锁(浏览器和设备)

Vite简介

Vite(发音为"veet",法语中表示"快"的意思)是一个前端开发工具,就像一个超级快的"厨师",能够迅速准备好你的Vue项目。开发者同样是尤雨溪。

Vite有什么特点?

  1. 超级快的启动速度

    • 传统工具可能需要等待几十秒才能启动,而Vite只需不到1秒
    • 你修改代码后,页面几乎立即更新,不需要等待
  2. 简单易用

    • 不需要复杂的配置,安装后就能直接使用
    • 命令简单:npm create vite@latest 项目名 -- --template vue
  3. 开发体验好

    • 改了代码不用刷新页面,修改会自动显示
    • 报错提示清晰,容易找到问题

Vite和Vue的关系

Vite和Vue就像好朋友一样,它们由同一个人(尤雨溪)创造:

  1. Vite最初是为Vue设计的
    • 虽然现在也支持React等其他框架,但和Vue配合最默契
    • Vue官方推荐使用Vite开发Vue项目
  2. 使用Vite开发Vue项目的好处
    • 启动快:不用等很久就能开始写代码
    • 热更新快:改完代码立刻看到效果
    • 打包快:完成项目后,生成最终文件也很快
      简单来说,Vite就是让你开发Vue项目时体验更流畅、更愉快的工具!

重构前端项目

1.在E盘创建一个名为Vue3Projects的目录,然后进入该目录,然后在空白的位置单击鼠标右键,选择【在终端中打开】

2.在cmd中输入命令npm create vite,然后回车,初次运行这个命令会提示我们要安装create-vite软件包,我们输入y,然后回车确认,接下来我们把项目的名字Project Name设置成todoListFront,然后回车,把包的名字Package Name也设置成todoListFront,然后回车,最后,通过键盘箭头上下键选择Vue这个前端框架,回车。

3.最后我们还是通过上下按键选中JavaScript,然后回车确认

4.如果出现下面的提示信息,说明项目创建成功

5.接下来我们进入到刚才创建的todoListFront目录中,可以发现vite已经帮我们创建了一堆程序文件

6.在该文件夹中按住键盘的Shift键,然后单击鼠标右键,点击【通过Code打开】,进去以后点击信任作者

7.点击终端菜单里面的【新建终端】,然后在下面的终端窗口输入npm install,然后回车运行,该命令用于安装基础软件包,然后再运行npm install vue-router命令来安装路由软件包。

8.删除src目录里面的style.css文件

9.点击main.js,把第2行代码删除

11.再把components目录里面的HelloWord.vue删掉

12.打开App.vue,把代码改成下面这样:

<script setup>
</script>

<template>
  <div>
  </div>
</template>

<style scoped>
</style>

13.复制一下App.vue,然后在components文件夹上粘贴4份,分别改名成Header.vue, Login.vue, Regist.vueShowTodoList.vue,分别是网页头部模板页,登录页,注册页和显示待办事项页

14.在src文件夹中新建一个router路由文件夹,然后在router文件夹中新建一个router.js文件

15.然后在router.js文件中插入下面的代码:

import { createRouter, createWebHashHistory } from 'vue-router' // 从vue-router中导入createRouter和createWebHashHistory函数
import Header from '../components/Header.vue' // 导入Header组件
import Login from '../components/Login.vue' // 导入Login组件
import Regist from '../components/Regist.vue' // 导入Regist组件
import ShowTodoList from '../components/ShowTodoList.vue' // 导入ShowTodoList组件

// 创建路由实例
const router = createRouter({
    history: createWebHashHistory(), // 使用hash模式的路由历史记录管理
    routes: [ // 定义路由配置
        { // 首页路由配置
            path: '/', // 路径为根路径
            name: 'Header', // 路由名称为'Header'
            component: ShowTodoList // 对应的组件为ShowTodoList
        },
        { // 登录页面路由配置
            path: '/login', // 路径为/login
            name: 'Login', // 路由名称为'Login'
            component: Login // 对应的组件为Login
        },
        { // 注册页面路由配置
            path: '/regist', // 路径为/regist
            name: 'Regist', // 路由名称为'Regist'
            component: Regist // 对应的组件为Regist
        },
        { // 显示待办事项列表页面路由配置
            path: '/todolist', // 路径为/todolist
            name: 'ShowTodoList', // 路由名称为'ShowTodoList'
            component: ShowTodoList // 对应的组件为ShowTodoList
        }
    ]
})

export default router // 导出路由实例

16.点开main.js文件,然后把代码改成下面这样

// 从 'vue' 库中导入 createApp 函数,该函数用于创建 Vue 应用实例
import { createApp } from 'vue'

// 导入根组件 App.vue,这是 Vue 应用的入口组件
import App from './App.vue'

// 导入路由配置,路由用于定义不同的 URL 对应不同的组件
import router from './router/router'

// 使用 createApp 函数创建一个 Vue 应用实例,并将根组件 App 作为参数传递
const app = createApp(App)

// 将路由配置应用到 Vue 实例上,使得应用可以使用路由功能
app.use(router)

// 将创建的 Vue 实例挂载到 id 为 'app' 的 DOM 元素上,这一步是将应用渲染到页面中
app.mount('#app')

17.再点开App.vue,把代码改成下面这样

/* 定义组件行为 */
<script setup>
  // 使用组合式 API 的 <script setup> 块
  // 从 ./components/ 目录下导入 Header.vue 组件,并将其命名为 Header
  import Header from './components/Header.vue';
</script>

<!-- 定义组件的结构和外观 -->
<template>
  <div>
    <!-- 使用之前导入的 Header 组件 -->
    <Header></Header>
    <!-- 使用 Vue Router 提供的 router-view 组件,用于渲染与当前路由匹配的组件 -->
    <router-view></router-view>
  </div>
</template>

/* 定义组件的样式,并且添加了 scoped 属性,使得样式只应用于当前组件 */
<style scoped>

</style>

18.打开components目录下的Header.vue,我们来设计网页头部,请用AI帮忙设计,或者粘贴下面的代码:

<script setup>
import { ref, computed } from 'vue';
// 使用ref定义响应式变量isLoggedIn来模拟用户登录状态
const isLoggedIn = ref(false);
// 使用ref定义响应式变量username来存储用户名
const username = ref('');

// 定义退出登录函数logout,该函数会将isLoggedIn设置为false,清空username,并在控制台输出退出登录成功的消息
const logout = () => {
  isLoggedIn.value = false;
  username.value = '';
  console.log('退出登录成功');
};
</script>

<template>
  <header class="header">
    <div class="logo">
      <!-- 标题 -->
      <h1>待办事项管理系统</h1>
      <!-- 副标题 -->
      <p>欢迎使用我们的待办事项管理系统</p>
    </div>
    
    <div class="actions">
      <!-- 链接到待办事项页面的按钮 -->
      <router-link to="/todolist" class="btn">待办事项</router-link>
      
      <!-- 如果用户未登录,则显示登录和注册按钮 -->
      <template v-if="!isLoggedIn">
        <router-link to="/login" class="btn btn-primary">登录</router-link>
        <router-link to="/regist" class="btn btn-outline">注册</router-link>
      </template>
      
      <!-- 如果用户已登录,则显示用户名和退出登录按钮 -->
      <template v-else>
        <span class="username">{{ username }}</span>
        <button class="btn btn-danger" @click="logout">退出登录</button>
      </template>
    </div>
  </header>
</template>

<style scoped>
.header {
  display: flex;
  justify-content: space-between; /* 使子元素在主轴上两端对齐 */
  align-items: center; /* 使子元素在交叉轴上居中对齐 */
  padding: 1rem 2rem; /* 设置内边距 */
  background-color: #f8f9fa; /* 设置背景颜色 */
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
}

.logo h1 {
  margin: 0; /* 去除默认的外边距 */
  font-size: 1.5rem; /* 设置字体大小 */
  color: #333; /* 设置字体颜色 */
}

.logo p {
  margin: 0.25rem 0 0; /* 设置外边距 */
  font-size: 0.9rem; /* 设置字体大小 */
  color: #666; /* 设置字体颜色 */
}

.actions {
  display: flex; /* 使用Flex布局 */
  gap: 0.75rem; /* 设置子元素之间的间距 */
  align-items: center; /* 使子元素在交叉轴上居中对齐 */
}

.username {
  font-weight: 500; /* 设置字体粗细 */
  margin-right: 0.5rem; /* 设置右边距 */
  color: #333; /* 设置字体颜色 */
}

.btn {
  text-decoration: none;  /* 去除下划线 */
  padding: 0.5rem 1rem; /* 设置内边距 */
  border: none; /* 去除边框 */
  border-radius: 4px; /* 设置圆角 */
  cursor: pointer; /* 设置鼠标悬停时的光标样式 */
  font-size: 0.9rem; /* 设置字体大小 */
  transition: all 0.2s; /* 设置过渡效果 */
}

.btn:hover {
  opacity: 0.9; /* 设置鼠标悬停时的透明度 */
}

.btn-primary {
  background-color: #4361ee; /* 设置按钮背景颜色 */
  color: white; /* 设置按钮字体颜色 */
}

.btn-outline {
  background-color: transparent; /* 设置按钮背景颜色为透明 */
  border: 1px solid #4361ee; /* 设置按钮边框 */
  color: #4361ee; /* 设置按钮字体颜色 */
}

.btn-danger {
  background-color: #ef476f; /* 设置按钮背景颜色 */
  color: white; /* 设置按钮字体颜色 */
}
</style>

19.在终端中运行命令npm run dev,然后安装Ctrl键,点击下面的网址

20.应该可以在浏览器中看到我们设计的网页头部

21.接下来设计登录页,把Login.vue的代码改成下面这样:

<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';

const router = useRouter();
const username = ref('');
const password = ref('');

const handleLogin = () => {
  // 这里添加登录逻辑
  console.log('登录信息:', {
    username: username.value,
    password: password.value
  });
  // 登录成功后跳转到待办事项页面
  // router.push('/todolist');
};
</script>

<template>
  <div class="login-wrapper">
    <div class="login-card">
      <div class="login-header">
        <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-square"><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>
        <h2>用户登录</h2>
        <p>欢迎回来!请登录您的账户。</p>
      </div>
      <form @submit.prevent="handleLogin" class="login-form">
        <div class="input-group">
          <label for="username">用户名</label>
          <input
            type="text"
            id="username"
            v-model="username"
            placeholder="请输入用户名"
            required
          />
        </div>
        <div class="input-group">
          <label for="password">密码</label>
          <input
            type="password"
            id="password"
            v-model="password"
            placeholder="请输入密码"
            required
          />
        </div>
        <button type="submit" class="btn-login">登 录</button>
      </form>
      <div class="login-footer">
        <router-link to="/regist">还没有账号?立即注册</router-link>
      </div>
    </div>
  </div>
</template>

<style scoped>
.login-wrapper {
  /* min-height: 100vh; */ /* 移除 min-height */
  flex-grow: 1; /* 让 wrapper 在 main-content 内填充可用空间 */
  display: flex;
  justify-content: center;
  align-items: center;
  /* background-color: #fff; */ /* 背景色由 main-content 或 body 控制,或者按需保留 */
  padding: 2rem; /* 保留内边距 */
}

.login-card {
  background: white;
  padding: 2.5rem 3rem;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 420px;
  text-align: center;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.login-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}

.login-header {
  margin-bottom: 2rem;
}

.login-header svg {
  color: #4361ee;
  margin-bottom: 0.5rem;
}

.login-header h2 {
  font-size: 1.8rem;
  color: #333;
  margin-bottom: 0.5rem;
}

.login-header p {
  color: #777;
  font-size: 0.95rem;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 1.5rem; /* 增加输入框间距 */
}

.input-group {
  text-align: left;
}

.input-group label {
  display: block;
  margin-bottom: 0.5rem;
  color: #555;
  font-weight: 500;
  font-size: 0.9rem;
}

.input-group input {
  width: 100%;
  padding: 0.8rem 1rem; /* 增加内边距 */
  border: 1px solid #ddd;
  border-radius: 6px; /* 更圆润的边角 */
  font-size: 1rem;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
  box-sizing: border-box; /* 确保 padding 不会撑大元素 */
}

.input-group input:focus {
  outline: none;
  border-color: #4361ee;
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1); /* 添加聚焦光晕效果 */
}

.btn-login {
  padding: 0.9rem; /* 增加按钮内边距 */
  background: linear-gradient(90deg, #4361ee 0%, #3a53c0 100%); /* 渐变背景 */
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 1.1rem; /* 增大字体 */
  font-weight: 600; /* 加粗字体 */
  cursor: pointer;
  transition: all 0.3s ease;
  margin-top: 1rem; /* 与上方输入框的间距 */
  letter-spacing: 1px; /* 增加字间距 */
}

.btn-login:hover {
  background: linear-gradient(90deg, #3a53c0 0%, #4361ee 100%);
  box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
  transform: translateY(-2px);
}

.login-footer {
  margin-top: 1.5rem;
  font-size: 0.9rem;
}

.login-footer a {
  color: #4361ee;
  text-decoration: none;
  transition: color 0.3s ease;
}

.login-footer a:hover {
  color: #3451db;
  text-decoration: underline;
}
</style>

22.然后设计注册页,把Regist.vue文件的代码改成下面这样:

<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';

const router = useRouter();
const username = ref('');
const password = ref('');
const confirmPassword = ref('');
const usernameError = ref(''); // 用户名错误信息
const passwordError = ref(''); // 密码格式错误信息
const confirmPasswordError = ref(''); // 确认密码不匹配错误信息

// --- 验证函数 ---
const validateUsername = (value) => {
  if (!value) return '用户名不能为空';
  if (value.length < 3 || value.length > 20) return '用户名长度必须在 3 到 20 个字符之间';
  if (!/^[a-zA-Z0-9_]+$/.test(value)) return '用户名只能包含字母、数字和下划线';
  return ''; // 无错误
};

const validatePassword = (value) => {
  if (!value) return '密码不能为空';
  if (value.length < 6) return '密码长度至少为 6 个字符';
  // 可以根据需要添加更复杂的密码规则
  return ''; // 无错误
};
// --- 结束:验证函数 ---

const handleRegister = () => {
  // --- 执行验证 ---
  usernameError.value = validateUsername(username.value);
  passwordError.value = validatePassword(password.value);
  confirmPasswordError.value = ''; // 先清空确认密码错误

  // 检查两次输入的密码是否一致 (仅在密码格式有效时检查)
  if (!passwordError.value && password.value !== confirmPassword.value) {
    confirmPasswordError.value = '两次输入的密码不一致';
  }

  // 如果有任何错误,则阻止提交
  if (usernameError.value || passwordError.value || confirmPasswordError.value) {
    return;
  }
  // --- 结束:执行验证 ---

  // 清除所有错误信息
  usernameError.value = '';
  passwordError.value = '';
  confirmPasswordError.value = '';

  // 这里添加实际的注册逻辑,例如调用 API
  console.log('注册信息:', {
    username: username.value,
    password: password.value // 注意:实际应用中不应明文传输密码
  });
  alert('注册成功(模拟)!'); // 模拟提示
  // 注册成功后可以跳转到登录页面
  // router.push('/login');
};

// --- 输入框失焦时触发验证 ---
const triggerUsernameValidation = () => {
  usernameError.value = validateUsername(username.value);
};
const triggerPasswordValidation = () => {
  passwordError.value = validatePassword(password.value);
  // 当密码框失焦时,如果确认密码框已有内容,也检查一致性
  if (confirmPassword.value && password.value !== confirmPassword.value) {
    confirmPasswordError.value = '两次输入的密码不一致';
  } else if (confirmPassword.value) {
    confirmPasswordError.value = ''; // 如果一致了,清除错误
  }
};
const triggerConfirmPasswordValidation = () => {
  if (password.value !== confirmPassword.value) {
    confirmPasswordError.value = '两次输入的密码不一致';
  } else {
    confirmPasswordError.value = '';
  }
};

</script>

<template>
  <div class="register-wrapper">
    <div class="register-card">
      <div class="register-header">
        <!-- 使用不同的 SVG 图标 -->
        <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user-plus"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>
        <h2>创建账户</h2>
        <p>加入我们,开始管理您的待办事项!</p>
      </div>
      <form @submit.prevent="handleRegister" class="register-form">
        <!-- 用户名输入组 -->
        <div class="input-group">
          <label for="username">用户名</label>
          <input
            type="text"
            id="username"
            v-model="username"
            placeholder="3-20位, 字母/数字/_"
            required
            @blur="triggerUsernameValidation"
            :class="{ 'input-error': usernameError }"
            aria-describedby="username-error"
          />
          <p v-if="usernameError" id="username-error" class="error-message">{{ usernameError }}</p>
        </div>
        <!-- 密码输入组 -->
        <div class="input-group">
          <label for="password">密码</label>
          <input
            type="password"
            id="password"
            v-model="password"
            placeholder="至少6位"
            required
            @blur="triggerPasswordValidation"
            :class="{ 'input-error': passwordError }"
            aria-describedby="password-error"
          />
          <p v-if="passwordError" id="password-error" class="error-message">{{ passwordError }}</p>
        </div>
        <!-- 确认密码输入组 -->
        <div class="input-group">
          <label for="confirmPassword">确认密码</label>
          <input
            type="password"
            id="confirmPassword"
            v-model="confirmPassword"
            placeholder="请再次输入密码"
            required
            @blur="triggerConfirmPasswordValidation"
            :class="{ 'input-error': confirmPasswordError }"
            aria-describedby="confirm-password-error"
          />
          <p v-if="confirmPasswordError" id="confirm-password-error" class="error-message">{{ confirmPasswordError }}</p>
        </div>
        <!-- 注册按钮 -->
        <button type="submit" class="btn-register">注 册</button>
      </form>
      <!-- 页脚链接 -->
      <div class="register-footer">
        <router-link to="/login">已有账号?返回登录</router-link>
      </div>
    </div>
  </div>
</template>

<style scoped>
/* 整体布局和卡片样式 (与 Login.vue 类似) */
.register-wrapper {
  /* min-height: 100vh; */ /* 移除 min-height */
  flex-grow: 1; /* 让 wrapper 在 main-content 内填充可用空间 */
  display: flex;
  justify-content: center;
  align-items: center;
  /* background-color: #fff; */ /* 背景色由 main-content 或 body 控制,或者按需保留 */
  padding: 2rem; /* 保留内边距 */
}

.register-card {
  background: white;
  padding: 2.5rem 3rem;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 420px;
  text-align: center;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.register-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}

/* 头部样式 */
.register-header {
  margin-bottom: 2rem;
}

.register-header svg {
  color: #4361ee; /* 主题色 */
  margin-bottom: 0.5rem;
}

.register-header h2 {
  font-size: 1.8rem;
  color: #333;
  margin-bottom: 0.5rem;
}

.register-header p {
  color: #777;
  font-size: 0.95rem;
}

/* 表单样式 */
.register-form {
  display: flex;
  flex-direction: column;
  gap: 0.8rem; /* 调整输入框组之间的基础间距 */
}

/* 输入框组样式 */
.input-group {
  text-align: left;
  margin-bottom: 0.7rem; /* 为错误信息预留空间 */
  position: relative; /* 使得错误信息可以定位 */
}

.input-group label {
  display: block;
  margin-bottom: 0.5rem;
  color: #555;
  font-weight: 500;
  font-size: 0.9rem;
}

.input-group input {
  width: 100%;
  padding: 0.8rem 1rem;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 1rem;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
  box-sizing: border-box;
}

.input-group input:focus {
  outline: none;
  border-color: #4361ee;
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
}

/* 错误状态的输入框 */
.input-group input.input-error {
  border-color: #ef476f; /* 红色边框 */
}

.input-group input.input-error:focus {
  border-color: #ef476f;
  box-shadow: 0 0 0 3px rgba(239, 71, 111, 0.1); /* 红色光晕 */
}

/* 错误信息样式 */
.error-message {
  color: #ef476f;
  font-size: 0.8rem;
  margin-top: 0.3rem;
  min-height: 1.1rem; /* 占据固定高度防止跳动 */
  /* position: absolute; */ /* 可以取消绝对定位,让其自然流动 */
  /* bottom: -1.2rem; */
  /* left: 0; */
  width: 100%; /* 确保宽度 */
  text-align: left;
}

/* 注册按钮样式 (与 Login.vue 类似,但类名不同) */
.btn-register {
  padding: 0.9rem;
  background: linear-gradient(90deg, #4361ee 0%, #3a53c0 100%);
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 1.1rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  margin-top: 1rem; /* 与上方元素的间距 */
  letter-spacing: 1px;
}

.btn-register:hover {
  background: linear-gradient(90deg, #3a53c0 0%, #4361ee 100%);
  box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
  transform: translateY(-2px);
}

/* 页脚链接样式 (与 Login.vue 类似) */
.register-footer {
  margin-top: 1.5rem;
  font-size: 0.9rem;
}

.register-footer a {
  color: #4361ee;
  text-decoration: none;
  transition: color 0.3s ease;
}

.register-footer a:hover {
  color: #3451db;
  text-decoration: underline;
}
</style>


23.设计待办事项列表页,打开ShowTodoList.vue文件,把代码改成这样

<script setup>
import { ref } from 'vue';

// 使用 ref 创建响应式的待办事项列表,初始包含一些示例数据
const todos = ref([
  { id: 1, text: '学习 Vue 3', completed: false },
  { id: 2, text: '完成项目报告', completed: true },
  { id: 3, text: '去超市购物', completed: false }
]);

// 用于绑定新待办事项输入框的 ref
const newTodoText = ref('');

// 用于生成唯一 ID 的计数器 (简单实现)
let nextTodoId = ref(todos.value.length + 1);

// 添加新的待办事项
const addTodo = () => {
  const text = newTodoText.value.trim();
  if (text) {
    todos.value.push({
      id: nextTodoId.value++,
      text: text,
      completed: false
    });
    newTodoText.value = ''; // 清空输入框
  }
};

// 切换待办事项的完成状态
const toggleComplete = (id) => {
  const todo = todos.value.find(t => t.id === id);
  if (todo) {
    todo.completed = !todo.completed;
  }
};

// 删除待办事项
const removeTodo = (id) => {
  todos.value = todos.value.filter(t => t.id !== id);
};
</script>

<template>
  <div class="todolist-container">
    <h2>我的待办事项</h2>

    <!-- 添加新待办事项的表单 -->
    <form @submit.prevent="addTodo" class="add-todo-form">
      <input
        type="text"
        v-model="newTodoText"
        placeholder="添加新的待办事项..."
        class="todo-input"
      />
      <button type="submit" class="btn-add">添加</button>
    </form>

    <!-- 待办事项列表 -->
    <ul class="todo-list">
      <li
        v-for="todo in todos"
        :key="todo.id"
        :class="{ completed: todo.completed }"
        class="todo-item"
      >
        <input
          type="checkbox"
          :checked="todo.completed"
          @change="toggleComplete(todo.id)"
          class="todo-checkbox"
        />
        <span class="todo-text">{{ todo.text }}</span>
        <button @click="removeTodo(todo.id)" class="btn-remove">删除</button>
      </li>
    </ul>
     <p v-if="todos.length === 0" class="empty-message">当前没有待办事项。</p>
  </div>
</template>

<style scoped>
.todolist-container {
  max-width: 600px;
  margin: 2rem auto;
  padding: 2rem;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}

h2 {
  text-align: center;
  color: #333;
  margin-bottom: 1.5rem;
}

.add-todo-form {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.todo-input {
  flex-grow: 1;
  padding: 0.75rem 1rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
  transition: border-color 0.3s ease;
}

.todo-input:focus {
  outline: none;
  border-color: #4361ee; /* 主题色 */
}

.btn-add {
  padding: 0.75rem 1.5rem;
  background-color: #4361ee; /* 主题色 */
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.btn-add:hover {
  background-color: #3a53c0; /* 主题色加深 */
}

.todo-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.todo-item {
  display: flex;
  align-items: center;
  padding: 0.8rem 0.5rem;
  border-bottom: 1px solid #eee;
  transition: background-color 0.2s ease;
}

.todo-item:last-child {
  border-bottom: none;
}

.todo-item:hover {
    background-color: #f9f9f9;
}

.todo-item.completed .todo-text {
  text-decoration: line-through;
  color: #aaa;
}

.todo-checkbox {
  margin-right: 0.75rem;
  cursor: pointer;
  /* 放大复选框,使其更容易点击 */
  width: 18px;
  height: 18px;
}

.todo-text {
  flex-grow: 1;
  color: #444;
  word-break: break-all; /* 防止长文本溢出 */
}

.btn-remove {
  background-color: #ef476f; /* 危险色 */
  color: white;
  border: none;
  border-radius: 4px;
  padding: 0.3rem 0.7rem;
  font-size: 0.8rem;
  cursor: pointer;
  margin-left: 0.5rem;
  opacity: 0; /* 默认隐藏 */
  transition: opacity 0.2s ease, background-color 0.3s ease;
}

.todo-item:hover .btn-remove {
  opacity: 1; /* 悬停时显示删除按钮 */
}

.btn-remove:hover {
  background-color: #d6345b; /* 危险色加深 */
}

.empty-message {
    text-align: center;
    color: #888;
    margin-top: 2rem;
    font-style: italic;
}
</style>


24.如果前端服务器没有运行,在终端输入npm run dev 运行,然后访问下面的网址,如果已经运行服务器了,可以Ctrl+C停止服务器,然后再运行。

25.打开浏览器后,可以看到简介美观的前端。

前后端互联测试

1.打开注册组件,在找到validateUsername函数,在if之后,return之前,插入下面的代码

// 创建一个新的XMLHttpRequest对象,用于在浏览器和服务器之间进行异步通信
var xhr = new XMLHttpRequest();
// 设置xhr对象的状态改变时的回调函数
// 该函数会在xhr对象的状态(readyState)改变时被调用
xhr.onreadystatechange = function() {
    // 当readyState为4(请求已完成且响应已就绪)且status为200(HTTP状态码200表示请求成功)
    // 进行以下操作
    if (xhr.readyState === 4 && xhr.status === 200) {
        // 解析服务器返回的JSON数据
        var result = JSON.parse(xhr.responseText);
        if (result.code != 200) {
            //把用户名错误的提示信息设置成用户名已经存在
            usernameError.value = "用户名已存在";
        }
    }
};
// 访问后台的检测用户名控件
xhr.open("GET", "http://localhost:8080/TodoList/user/checkUsername?username=" + value, true);
// 发送请求
xhr.send();

2.打开IDEA软件,在filter包里面创建一个Java类,取名为CROSFilter

  1. CROSFilter中的代码如下:
package com.youngshu.todolist.filter;

import com.youngshu.todolist.pojo.SysUser;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;

//允许CROS
@WebFilter(urlPatterns = {"/*"})
public class CROSFilter implements Filter {

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            HttpServletResponse httpResponse = (HttpServletResponse) response;

            // 允许来自Vue前端的请求
            httpResponse.setHeader("Access-Control-Allow-Origin", "*");
            httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
            httpResponse.setHeader("Access-Control-Max-Age", "3600");

            chain.doFilter(request, response);
        }

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // 初始化方法
        }

        @Override
        public void destroy() {
            // 销毁方法
        }
}

4.在IDEA中重启服务器,在VSCODE中重启前端项目,试试注册页是否可以显示用户名已经存在。


评论 (0)

暂无评论,来发表第一条评论吧!