数据库实训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
相关推荐
迦南的迦 亚索的索4 分钟前
PYTHON_DAY20_数据库
数据库·oracle
数厘14 分钟前
2.14 sql数据删除(DELETE、TRUNCATE)
数据库·oracle
XDHCOM26 分钟前
MySQL ER_ERROR_ENABLING_KEYS报错修复,远程处理索引启用失败故障,解决数据表锁定与性能瓶颈问题
数据库·mysql
高梦轩29 分钟前
Python 操作 MySQL 数据库
数据库·oracle
Arva .30 分钟前
Redis 数据类型
数据库·redis·缓存
CDN36036 分钟前
高防切换后网站打不开?DNS 解析与回源路径故障排查
前端·网络·数据库
笑我归无处1 小时前
Redis和数据库的数据一致性问题研究
数据库·redis·缓存
水痕011 小时前
使用sqlSugar来操作mysql数据库
数据库·mysql
zandy10111 小时前
衡石科技 HENGSHI SENSE:一站式智能分析平台,让企业数据价值“所见即所得”
大数据·数据库·科技
fly spider1 小时前
MySQL日志篇
数据库·mysql