数据库实训Day005上午

1 添加导航

1.1 写一个Main.vue

让相应的的路由对应起来

java 复制代码
<template>
  <el-container class="main-container">
    <!-- 头部导航栏 -->
    <el-header class="main-header">
      <div class="header-left">
        <el-icon size="24" class="header-logo"><Shop /></el-icon>
        <span class="header-title">商品管理系统</span>
      </div>
      <div class="header-right">
        <el-dropdown :disabled="isSubmitting" @command="handleDropdownCommand">
          <span class="user-info">
            <el-icon><User /></el-icon>
            {{ userName }}
          </span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item command="profile">个人中心</el-dropdown-item>
              <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
    </el-header>

    <el-container>
      <!-- 左侧侧边导航栏 -->
      <el-aside width="200px" class="main-aside">
        <el-menu
            :default-active="activeMenu"
            class="el-menu-vertical-demo"
            router
            :disabled="isSubmitting"
            background-color="#f5f5f5"
            text-color="#333"
            active-text-color="#409eff"
            collapse-transition
        >
          <!-- 首页 -->
          <el-menu-item index="/dashboard">
            <el-icon><House /></el-icon>
            <template #title>首页</template>
          </el-menu-item>


          <!-- 管理员管理 -->
          <el-menu-item index="/adminpage">
            <el-icon><Menu /></el-icon>
            <template #title>管理员管理</template>
          </el-menu-item>

          <!-- 用户管理 -->
          <el-menu-item index="/userpage">
            <el-icon><Menu /></el-icon>
            <template #title>用户管理</template>
          </el-menu-item>

          <!-- 分类管理 -->
          <el-menu-item index="/categorypage">
            <el-icon><Menu /></el-icon>
            <template #title>分类管理</template>
          </el-menu-item>

          <!-- 分类管理 -->
          <el-menu-item index="/brandpage">
            <el-icon><Menu /></el-icon>
            <template #title>品牌管理</template>
          </el-menu-item>

          <!-- 订单管理 -->
          <el-menu-item index="/productpage">
            <el-icon><Tickets /></el-icon>
            <template #title>商品管理</template>
          </el-menu-item>
        </el-menu>
      </el-aside>

      <!-- 主内容区域(路由视图容器) -->
      <el-main class="main-content">
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute, type RouteLocationNormalized } from 'vue-router'
import {
  Shop, User, UserFilled, House, Goods, List, Menu, Plus, Tickets, Edit
} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'

// 状态管理:提交中(禁用交互)
const isSubmitting = ref<boolean>(false)
// 当前登录用户名(可从本地存储/接口获取)
const userName = ref<string>(localStorage.getItem('username') || '管理员')

// 路由实例
const router = useRouter()
const route = useRoute()

// 计算当前激活的菜单(匹配路由路径)
const activeMenu = computed<string>(() => {
  // 处理嵌套路由的菜单激活(如 /user/list 匹配 /user)
  const path = route.path
  const firstLevelPath = path.split('/')[1]
  return firstLevelPath ? `/${firstLevelPath}` : '/dashboard'
})

// 处理下拉菜单命令
const handleDropdownCommand = (command: 'profile' | 'logout') => {
  switch (command) {
    case 'profile':
      router.push('/profile')
      break
    case 'logout':
      handleLogout()
      break
  }
}

// 退出登录逻辑
const handleLogout = async () => {
  try {
    await ElMessageBox.confirm(
        '确定要退出登录吗?',
        '提示',
        {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
          closeOnClickModal: false, // 点击遮罩层不关闭弹窗
          draggable: true // 弹窗可拖拽,提升交互体验
        }
    )
    // 清除登录状态
    localStorage.removeItem('token')
    localStorage.removeItem('username')
    // 跳转登录页
    await router.push('/login')
    ElMessage.success('退出登录成功!')
  } catch {
    ElMessage.info('已取消退出登录')
  }
}

// 路由守卫:切换路由后重置提交状态
onMounted(() => {
  router.afterEach((to: RouteLocationNormalized, from: RouteLocationNormalized) => {
    isSubmitting.value = false
    // 同步用户名(防止刷新后丢失)
    userName.value = localStorage.getItem('username') || '管理员'
  })
})
</script>

<style scoped>
/* 整体容器 - 占满视口高度 */
.main-container {
  height: 100vh;
  overflow: hidden;
}

/* 头部导航样式 */
.main-header {
  background-color: #ffffff;
  color: #333333;
  line-height: 60px;
  border-bottom: 1px solid #e6e6e6;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 20px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
  z-index: 10;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.header-logo {
  color: #409eff;
  transition: color 0.3s;
}

.header-logo:hover {
  color: #66b1ff;
}

.header-title {
  font-size: 18px;
  font-weight: 600;
  color: #1f2937;
}

.header-right {
  display: flex;
  align-items: center;
}

.user-info {
  cursor: pointer;
  color: #666666;
  padding: 8px 12px;
  border-radius: 4px;
  transition: background-color 0.3s, color 0.3s;
  display: flex;
  align-items: center;
  gap: 6px;
}

.user-info:hover {
  background-color: #f0f9ff;
  color: #409eff;
}

/* 左侧侧边导航样式 */
.main-aside {
  background-color: #f5f5f5;
  border-right: 1px solid #e6e6e6;
  transition: width 0.3s ease;
}

/* 穿透样式:Element Plus 菜单组件 */
:deep(.el-menu-vertical-demo) {
  height: 100%;
  border-right: 0;
  padding-top: 16px;
  font-size: 14px;
}

:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
  height: 48px;
  line-height: 48px;
}

:deep(.el-menu-item.is-active) {
  background-color: #e8f4ff !important;
}

/* 主内容区域样式 */
.main-content {
  background-color: #ffffff;
  padding: 24px;
  overflow-y: auto;
  height: calc(100vh - 60px);
  box-sizing: border-box;
  scroll-behavior: smooth;
}

/* 响应式适配 - 小屏幕(平板/手机) */
@media (max-width: 768px) {
  .main-aside {
    width: 64px !important;
  }

  /* 隐藏菜单文字和箭头 */
  :deep(.el-menu-vertical-demo .el-menu-item__title),
  :deep(.el-sub-menu__title span),
  :deep(.el-sub-menu__icon-arrow) {
    display: none;
  }

  /* 小屏幕下菜单图标居中 */
  :deep(.el-menu-item .el-icon),
  :deep(.el-sub-menu__title .el-icon) {
    margin: 0 auto;
  }

  /* 主内容区域自适应 */
  .main-content {
    padding: 16px;
  }
}

</style>

1.2 修改index.js

1、新增一个main的路由

java 复制代码
{
        path: '/adminlogin',
        name: 'Login',
        component: Login
    },

2、然后再在main的路由里面写一个children数组

java 复制代码
{
        path: '/adminlogin',
        name: 'Login',
        component: Login,
        children[]
    },

3、然后把之前的路由全部剪切到children数组中

java 复制代码
{
        path: '/adminlogin',
        name: 'Login',
        component: Login,
        children[剪切到这里面]
    },

2 登录功能

2.1 写后端AdminLoginController

java 复制代码
package com.easy.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.easy.bean.Admin;
import com.easy.service.AdminService;
import com.easy.util.PasswordUtil;
import com.easy.util.Result;
import com.easy.util.TokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@CrossOrigin
@RestController
@RequestMapping("adminlogin")
public class AdminLoginController {
    @Autowired
    AdminService adminService;

    @PostMapping("dologin")
    public Result dologin(@RequestBody Admin admin){
        // 根据账号查询管理员的信息
        LambdaQueryWrapper<Admin> lambdaQueryWrapper=new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(Admin::getAdminname,admin.getAdminname());
        Admin admin_db=adminService.getOne(lambdaQueryWrapper);
        if(admin_db!=null){
            //比对密码
            if(PasswordUtil.equalsPassword(admin.getPassword(),admin_db.getPassword())){
                //生成token
                String token= TokenUtil.generateAdminToken(admin_db.getId().toString());
                return Result.success("管理员登录成功",token);

            }
        }
        return Result.fail("管理员登录失败,请重新尝试");


    }
}

2.2 修改TokenUtil

User

Admin

generateToken改为generateAdminToken和generateUserToken

getLoginID改为getLoginAdminID和getLoginUserID

相应的内容也要做出修改

3 生成登录的前端页面

3.1 修改admin_api,在最后添加

3.2 创建Login.vue文件

java 复制代码
<template>
  <div class="login-container">
    <!-- 背景装饰区 -->
    <div class="login-bg"></div>

    <!-- 登录卡片 -->
    <div class="login-card">
      <!-- 登录标题 -->
      <div class="login-header">
        <h2 class="login-title">系统登录</h2>
        <p class="login-desc">欢迎登录管理系统,请输入您的账号信息</p>
      </div>

      <!-- 登录表单 -->
      <el-form
          ref="loginFormRef"
          :model="loginForm"
          :rules="loginRules"
          class="login-form"
          label-width="0px"
      >
        <!-- 账号输入框 -->
        <el-form-item prop="adminname">
          <el-input
              v-model="loginForm.adminname"
              placeholder="请输入账号"
              prefix-icon="User"
              size="large"
              autocomplete="adminname"
          />
        </el-form-item>

        <!-- 密码输入框 -->
        <el-form-item prop="password">
          <el-input
              v-model="loginForm.password"
              type="password"
              placeholder="请输入密码"
              prefix-icon="Lock"
              size="large"
              show-password
              autocomplete="current-password"
          />
        </el-form-item>

        <!-- 记住密码 & 忘记密码 -->
        <el-form-item class="login-form-options">
          <el-checkbox v-model="loginForm.remember">记住密码</el-checkbox>
          <span class="forget-password">忘记密码?</span>
        </el-form-item>

        <!-- 登录按钮 -->
        <el-form-item class="login-form-submit">
          <el-button
              type="primary"
              size="large"
              class="login-btn"
              @click="handleLogin"
              :loading="isLoading"
          >
            登录
          </el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import admin_api from '@/api/admin_api.js'
import {useRoute,useRouter} from 'vue-router'

const router=useRouter();
const route=useRoute();

// 表单引用
const loginFormRef = ref(null)

// 加载状态
const isLoading = ref(false)

// 登录表单数据
const loginForm = reactive({
  adminname: '',
  password: '',
  remember: false
})

// 表单验证规则
const loginRules = reactive({
  adminname: [
    { required: true, message: '请输入账号', trigger: 'blur' },
    { min: 3, max: 20, message: '账号长度在 3 到 20 个字符', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 6, message: '密码长度不少于 6 个字符', trigger: 'blur' }
  ]
})

// 登录处理函数
const handleLogin = async () => {
  try {
    // 表单验证
    await loginFormRef.value.validate()

    // 模拟登录加载
    isLoading.value = true
    const result=await admin_api.adminLogin(loginForm);
    isLoading.value = false
    if(result.data){
      // 登录成功提示
      ElMessage.success(result.message)
      localStorage.setItem("token",result.data);
      setTimeout(function(){
        router.push('/userPage')
      },1500);
      // 这里可添加路由跳转逻辑,例如:router.push('/home')
    }else{
      ElMessage.error('请检查账号或密码格式!')
    }

  } catch (error) {
    // 表单验证失败
    ElMessage.error('请检查账号或密码格式!')
    console.error('登录验证失败:', error)
  }
}
</script>

<style scoped>
/* 全局登录容器 */
.login-container {
  width: 100vw;
  height: 100vh;
  background-color: #f5f7fa;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  overflow: hidden;
}

/* 背景装饰 */
.login-bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(135deg, #409eff 0%, #67c23a 100%);
  opacity: 0.1;
  z-index: 1;
}

/* 登录卡片 */
.login-card {
  width: 420px;
  padding: 40px;
  background-color: #fff;
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  position: relative;
  z-index: 2;
}

/* 登录标题区 */
.login-header {
  text-align: center;
  margin-bottom: 30px;
}

.login-title {
  font-size: 24px;
  font-weight: 600;
  color: #1f2937;
  margin: 0 0 8px 0;
}

.login-desc {
  font-size: 14px;
  color: #6b7280;
  margin: 0;
}

/* 登录表单 */
.login-form {
  width: 100%;
}

/* 表单项间距 */
.el-form-item {
  margin-bottom: 20px;
}

/* 记住密码 & 忘记密码 */
.login-form-options {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30px;
}

.forget-password {
  cursor: pointer;
  font-size: 14px;
  color: #6b7280;
}

.forget-password:hover {
  color: #3590f3;
}

/* 登录按钮 */
.login-form-submit {
  margin-bottom: 0;
}

.login-btn {
  width: 100%;
  height: 48px;
  font-size: 16px;
  background-color: #409eff;
  border: none;
  border-radius: 8px;
}

.login-btn:hover {
  background-color: #3590f3;
}

/* 适配小屏幕 */
@media (max-width: 500px) {
  .login-card {
    width: 90%;
    padding: 30px 20px;
  }
}
</style>

3.3 数据库文件

在admin的数据库中新增一条数据,密码为

84551d67acace512f1251495b28e0403f6

这一串字符为123123的密文形式

3.4 启动登录

手动跳转到

/adminlogin

然后输入数据库中存好的信息,密码为明文形式(123123),然后点击登录即可

显示登录成功后,会跳转到商品系统管理页面

4 商品Product页面没有匹配数据问题

修改后端ProductController.java文件

步骤1:

1、在ProductController.java中引入品牌(brand)和分类(category)的Service

步骤2:

2、在getPage方法中新写入一个for循环

内容为两个方法的set方法

步骤3:

重新启动程序即可

5 守卫

java 复制代码
meta: {
            requiresAuth: true
        },

index.js

javascript 复制代码
// 1. 导入必要的依赖
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import { ElMessage } from 'element-plus'

// 导入路由组件(推荐使用懒加载,优化首屏加载速度)
// 方式1:普通导入(不推荐,会一次性加载所有组件)
import User from '@/views/User.vue'
import UserAdd from '@/views/UserAdd.vue'
import UserEdit from '@/views/UserEdit.vue'

import Brand from '@/views/Brand.vue'
import BrandAdd from '@/views/BrandAdd.vue'
import BrandEdit from '@/views/BrandEdit.vue'

import Category from '@/views/Category.vue'
import CategoryAdd from '@/views/CategoryAdd.vue'
import CategoryEdit from '@/views/CategoryEdit.vue'

import Admin from '@/views/Admin.vue'
import AdminAdd from '@/views/AdminAdd.vue'
import AdminEdit from '@/views/AdminEdit.vue'

import Product from '@/views/Product.vue'
import ProductAdd from '@/views/ProductAdd.vue'
import ProductEdit from '@/views/ProductEdit.vue'

import Main from '@/views/Main.vue'
import Login from '@/views/Login.vue'




// import About from '@/views/About.vue'

// 方式2:懒加载(推荐,按需加载组件)
//const User = () => import('@/views/User.vue')

// 2. 定义路由规则
const routes = [
  // 重定向:访问根路径时跳转到首页
  {
    path: '/',
    redirect: '/home'
  },

    {
        path: '/adminlogin',
        name: 'Login',
        component: Login
    },

  // 动态路由(带参数)

    // Main
    {
        path: '/main',
        name: 'Main',
        component: Main,
        meta: {
            requiresAuth: true
        },
        children:[
            {
                path: '/userpage', // :id 是动态参数
                name: 'User',
                component: User,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/useradd', // :id 是动态参数
                name: 'UserAdd',
                component: UserAdd,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/useredit', // :id 是动态参数
                name: 'UserEdit',
                component: UserEdit,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/brandpage', // :id 是动态参数
                name: 'Brand',
                component: Brand,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/brandadd', // :id 是动态参数
                name: 'BrandAdd',
                component: BrandAdd,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/brandedit', // :id 是动态参数
                name: 'BrandEdit',
                component: BrandEdit,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/categorypage', // :id 是动态参数
                name: 'Category',
                component: Category,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/categoryadd', // :id 是动态参数
                name: 'CategoryAdd',
                component: CategoryAdd,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/categoryedit', // :id 是动态参数
                name: 'CategoryEdit',
                component: CategoryEdit,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/adminpage', // :id 是动态参数
                name: 'Admin',
                component: Admin,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/adminadd', // :id 是动态参数
                name: 'AdminAdd',
                component: AdminAdd,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/adminedit', // :id 是动态参数
                name: 'AdminEdit',
                component: AdminEdit,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/productpage', // :id 是动态参数
                name: 'Product',
                component: Product,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/productadd', // :id 是动态参数
                name: 'ProductAdd',
                component: ProductAdd,
                meta: {
                    requiresAuth: true
                },
            },
            {
                path: '/productedit', // :id 是动态参数
                name: 'ProductEdit',
                component: ProductEdit,
                meta: {
                    requiresAuth: true
                },
            },

        ]
    },
  // 404 页面(必须放在最后)
//  {
//    path: '/:pathMatch(.*)*', // 匹配所有未定义的路由
//    name: 'NotFound',
//    component: NotFound,
//    meta: {
//      title: '页面不存在'
//    }
//  }
]

// 3. 创建路由实例
const router = createRouter({
  // 路由模式:
  // - createWebHistory:history 模式(无 # 号,需要后端配置)
  // - createWebHashHistory:hash 模式(有 # 号,无需后端配置)
  history: createWebHistory(import.meta.env.BASE_URL), // import.meta.env.BASE_URL 是 Vue 项目的基础路径
  // history: createWebHashHistory(), // 如需使用 hash 模式,替换上面这行
  routes // 传入路由规则
})

// 4. 全局路由守卫(可选,用于全局权限控制、页面标题设置等)
// 全局前置守卫:路由跳转前执行
// 路由前置守卫(每次跳转路由前触发)
router.beforeEach((to, from, next) => {
    // 1. 设置页面标题
    // if (to.meta.title) {
    //     document.title = to.meta.title + ' - 管理系统'
    // }

    // 2. 获取本地存储的token(登录成功后存储)
    const token = localStorage.getItem('token')

    // 3. 登录状态验证逻辑
    if (to.meta.requiresAuth) {
        // 目标路由需要登录权限
        if (token) {
            // 有token,视为已登录,放行
            next()
        } else {
            // 无token,未登录,跳转到登录页
            ElMessage.warning('请先登录!')
            next({
                path: '/adminlogin',
                query: { redirect: to.fullPath } // 记录跳转前的页面,登录后可返回
            })
        }
    } else {
        // 目标路由是公开路由(白名单)
        if (token && to.path === '/adminlogin') {
            // 已登录用户访问登录页,自动跳转到首页
            ElMessage.info('您已登录,无需重复登录')
            next({ path: '/mainpage' })
        } else {
            // 未登录用户访问公开路由,放行
            next()
        }
    }
})

// 5. 导出路由实例(供 main.js 引入)
export default router
相关推荐
Moresweet猫甜3 分钟前
Ubuntu LVM引导丢失紧急救援:完整恢复指南
linux·运维·数据库·ubuntu
yumgpkpm7 分钟前
Cloudera CDH5、CDH6、CDP7现状及替代方案
数据库·人工智能·hive·hadoop·elasticsearch·数据挖掘·kafka
松涛和鸣10 分钟前
48、MQTT 3.1.1
linux·前端·网络·数据库·tcp/ip·html
晓时谷雨12 分钟前
达梦数据库适配方案及总结
数据库·达梦·数据迁移
LaLaLa_OvO14 分钟前
spring boot2.0 里的 javax.validation.Constraint 加入 service
java·数据库·spring boot
地球资源数据云26 分钟前
MODIS(MCD19A2)中国2000-2024年度平均气溶胶光学深度数据集
大数据·服务器·数据库·人工智能·均值算法
数据大魔方27 分钟前
【期货量化实战】威廉指标(WR)策略:精准捕捉超买超卖信号(Python源码)
开发语言·数据库·python·算法·github·程序员创富
忘记92629 分钟前
mysql一条sql语句是如何运行的
数据库·mysql
Tan_Ying_Y1 小时前
为什么InnoDB存储引擎默认使用B+树作为索引结构?B+树索引工作原理?
数据库·b树·oracle
lalala_lulu1 小时前
MySQL数据库存储引擎的数据结构(超详细版)
数据结构·数据库·mysql