vue3+elementplus 后台通用管理系统

登录页面

管理首页

首页代码

<template>
  <div class="login-container">
    <el-card class="login-card">
      <template #header>
        <div class="login-header">xxx管理系统</div>
      </template>
      <el-form :model="loginForm" ref="loginFormRef"  >
        <el-form-item prop="username">
          <!-- 防止自动填充的技巧:添加隐藏输入框干扰浏览器记忆 -->
          <input type="text" style="display:none" />
          <input type="password" style="display:none" />
          <el-input 
            prefix-icon="user" 
            v-model="loginForm.username" 
            placeholder="请输入用户名"
            autocomplete="new-username"
          />
        </el-form-item>
        <el-form-item prop="password">
          <el-input 
            prefix-icon="lock" 
            v-model="loginForm.password" 
            type="password" 
            placeholder="请输入密码"
            autocomplete="new-password"
          />
        </el-form-item>
        <el-form-item>
          <el-checkbox v-model="rememberMe" class="rememberme">记住密码</el-checkbox>
        </el-form-item>
        <el-form-item style="width:100%;">
          <el-button type="primary" style="width:100%;" @click="handleLogin" :loading="logining">登录</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import request from '@/utils/request.js'

const router = useRouter()
const userStore = useUserStore()
const logining = ref(false)

const loginFormRef = ref(null)
const loginForm = reactive({
  username: '',
  password: '',
  code: ''
})
const userInfo=reactive({     
    name: '管理员',
    avatar: '',
    roles: ['admin'],
    permissions: ['*']
})

const rememberMe = ref(true)

const loginRules = reactive({
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
  ]
})

// 页面加载时检查本地存储
onMounted(() => {
  const savedLogin = localStorage.getItem('savedLogin')
  if (savedLogin) {
    try {
      const { username, password, remember } = JSON.parse(savedLogin)
    
      if (remember) {
         
        loginForm.username = username
        loginForm.password = password
        rememberMe.value = true
         
      }
    } catch (e) {
      console.error('Failed to parse saved login info', e)
    }
  }
  
  // 额外措施:延迟聚焦以防止自动填充
  // nextTick(() => {
  //   // 清空可能的自动填充值
  //   loginForm.username = ''
  //   loginForm.password = ''
  // })
})

const handleLogin = async () => {
  logining.value = true
  try {
     const response = await request.post("api/auth/login", loginForm)
     //const response = await request.post("api/login/login", loginForm)
    if (response.status) {
      // 保存登录状态
      if (rememberMe.value) {
        localStorage.setItem('savedLogin', JSON.stringify({
          username: loginForm.username,
          password: loginForm.password,
          remember: true
        }))
      } else {
        localStorage.removeItem('savedLogin')
      }
      localStorage.setItem("token",response.token)
      localStorage.setItem('userInfo', JSON.stringify(userInfo));
      ElMessage.success('登录成功')
      
      setTimeout(() => {
             router.push('/dashboard')
           }, 1000)
    } else {
      ElMessage.error(response.Message || '登录失败')
    }
  } catch (error) {
    ElMessage.error('网络错误,请稍后重试')
    console.error('Login error', error)
  } finally {
    logining.value = false
  }
}
</script>

<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 98vh;
  background: url('../assets/bg.jpg');
  background-color: #f5f7fa;
}

.login-card {
  width: 400px;
}

.login-header {
  text-align: center;
  font-size: 20px;
  color: #555;
 /* font-weight: bold; */
}

.captcha-container {
  display: flex;
  gap: 10px;
}

.captcha-image {
  width: 120px;
  height: 40px;
  cursor: pointer;
}
</style>    

后台代码

<template>
  <el-container class="admin-container">
    <!-- 侧边栏 -->
    <el-aside 
      :width="sidebarCollapse ? '64px' : '230px'" 
      class="admin-sidebar"
      :class="{ 'is-collapse': sidebarCollapse }"
    >
      <div class="logo" @click="toggleSidebar">
        <span v-if="!sidebarCollapse">后台管理系统</span>
        <span v-else></span>
      </div>
      <div class="menu-container">
      <el-menu
        router
        :default-active="$route.path"
        class="admin-menu"
        background-color="#2b2f3a"
        text-color="#bfcbd9"
        active-text-color="#409EFF"
        :collapse="sidebarCollapse"
        :collapse-transition="false"
      >
        <template v-for="menu in menus" :key="menu.id">
          <!-- 有子菜单的项 -->
          <el-sub-menu 
            v-if="menu.children && menu.children.length" 
            :index="menu.path"
          >
            <template #title>
              <el-icon v-if="menu.icon">
                <component :is="menu.icon"></component>
              </el-icon>
              <span>{{ menu.title }}</span>
            </template>
            <el-menu-item 
              v-for="child in menu.children" 
              :key="child.id" 
              :index="child.path"
            >
              <el-icon v-if="child.icon">
                <component :is="child.icon"></component>
              </el-icon>
              <span>{{ child.title }}</span>
            </el-menu-item>
          </el-sub-menu>

          <!-- 没有子菜单的项 -->
          <el-menu-item 
            v-else 
            :index="menu.path"
          >
            <el-icon v-if="menu.icon">
              <component :is="menu.icon"></component>
            </el-icon>
            <span>{{ menu.title }}</span>
          </el-menu-item>
        </template>
      </el-menu>
       </div>
    </el-aside>

    <el-container>
      <!-- 顶部导航 -->
      <el-header class="admin-header">
        <div class="header-left">
          <el-icon class="collapse-icon" @click="toggleSidebar" style="cursor: pointer;">
            <component :is="sidebarCollapse ? 'Expand' : 'Fold'"></component>
          </el-icon>
          <el-breadcrumb separator="/" class="breadcrumb">
            <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
            <el-breadcrumb-item>{{ currentRouteName }}</el-breadcrumb-item>
          </el-breadcrumb>
        </div>
        <div class="header-right">
          <el-dropdown>
            <div class="user-info">
              <el-avatar size="small"
                src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"></el-avatar>
              <span class="user-name">管理员</span>
            </div>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item   icon="user" @click="my">个人中心</el-dropdown-item>
                <el-dropdown-item   icon="setting">设置</el-dropdown-item>
                <el-dropdown-item   icon="SwitchButton" @click="logout">退出登录</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
          <el-tooltip content="全屏" placement="bottom">
            <el-button type="text" @click="toggleFullScreen">
              <el-icon><FullScreen /></el-icon>
            </el-button>
          </el-tooltip>
        </div>
      </el-header>

      <!-- 主内容区 -->
      <el-main class="admin-main">
        <router-view />
      </el-main>

      <!-- 页脚 -->
      <el-footer class="admin-footer"  height="30rpx">
        © 2023 通用后台管理系统 - Powered by Vue & Element Plus
      </el-footer>
    </el-container>
  </el-container>
</template>

<script>
    import {
        ref,
        computed,
        onMounted
    } from 'vue'
    import {
        useRouter,
        useRoute
    } from 'vue-router'
    import {
        ElMessage
    } from 'element-plus'
    import {
        useMenuStore
    } from '@/stores/menu'
    export default {
        name: 'AdminLayout',
        setup() {
            const router = useRouter()
            const route = useRoute()
            const sidebarCollapse = ref(
                  localStorage.getItem('sidebarCollapse') === 'true' || false
                )

            // 获取当前路由名称用于面包屑
            const currentRouteName = computed(() => {
                return route.meta.title || route.name
            })

            // 切换侧边栏折叠
            const toggleSidebar = () => {
                sidebarCollapse.value = !sidebarCollapse.value
                 localStorage.setItem('sidebarCollapse', sidebarCollapse.value.toString())
            }

            // 全屏切换
            const toggleFullScreen = () => {
                if (!document.fullscreenElement) {
                    document.documentElement.requestFullscreen()
                } else {
                    if (document.exitFullscreen) {
                        document.exitFullscreen()
                    }
                }
            }
            onMounted(async () => {
                await menuStore.fetchMenus()
                 
            })
            const menuItems = ref([])
            const menuStore = useMenuStore()

            const menus = computed(() => menuStore.menus)
            const my=()=>{
                router.push('/profile') 
                // router.push('/profile/profile')
            }

            // 退出登录
            const logout = () => {
                ElMessage.success('退出登录成功')
                localStorage.removeItem("token")
                router.push('/login')
            }

            return {
                sidebarCollapse,
                currentRouteName,
                toggleSidebar,
                toggleFullScreen,
                logout,
                menus,
                my
            }
        }
    }
</script>
<style scoped>
    .admin-container {
        height: 98vh;
    }

    .admin-sidebar {
        background-color: #2b2f3a;
        transition: width 0.3s;
    }

    .admin-sidebar.collapse {
        width: 64px;
    }

    .logo {
        height: 60px;
        line-height: 60px;
        text-align: center;
        color: #fff;
        font-size: 20px;
        font-weight: bold;
        background-color: #2b2f3a;
    }

    .admin-menu {
        border-right: none;
    }

    .admin-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0 20px;
        background-color: #fff;
        box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
    }

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

    .collapse-btn {
        font-size: 20px;
        margin-right: 20px;
    }

    .breadcrumb {
        margin-left: 10px;
    }

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

    .user-info {
        display: flex;
        align-items: center;
        cursor: pointer;
        padding: 0 10px;
    }

    .user-name {
        margin-left: 10px;
    }

    .admin-main {
        background-color: #f0f2f5;
        padding: 5px 1px;
        min-height: calc(100vh - 200px);
    }

    .admin-footer {
        text-align: center;
        padding: 2px 0;
        color: #888;
        font-size: 12px;
        background-color: #fff;
    }
    /* 菜单容器(可滚动) */
    .menu-container {
      /* 减去 logo 的高度,避免被 logo 遮挡 */
      height: calc(100% - 60px); 
      overflow-y: auto; /* 菜单超出高度时显示滚动条 */
      /* 隐藏滚动条(可选,美化样式) */
      scrollbar-width: none; /* Firefox */
    }
    .menu-container::-webkit-scrollbar {
      display: none; /* Chrome/Safari */
    }
    /* 在原有的 style 标签中添加 */
    .collapse-icon {
      /* 初始样式(可选) */
      padding: 4px; /* 增加点击区域,优化体验 */
      border-radius: 4px; /* 圆角,使背景色更美观 */
      transition: background-color 0.2s; /* 背景色变化过渡,更平滑 */
    }
    
    /* 鼠标滑过时的背景色 */
    .collapse-icon:hover {
      background-color: rgba(0, 0, 0, 0.1); /* 浅灰色背景(可根据主题调整) */
    }
</style>

api接口 登录验证 

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using NetCoreWebAPI.HttpModel;
using NetCoreWebAPI.Models;
using NetCoreWebAPI.Services;

namespace NetCoreWebAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly IConfiguration _configuration;

        public AuthController(IConfiguration configuration)
        {
            _configuration = configuration;
        }
        [AllowAnonymous]
        [HttpPost("login")]
        public IActionResult Login([FromBody] LoginModel model)
        {
            Models.HttpResult result = new Models.HttpResult();
            // 实际项目中应从数据库验证用户
            UserService userService = new UserService();
            model.Password=Utils.HashHelper.Md5(model.Password);
            var q=userService.IQueryable(u => u.UserName == model.Username && u.Password == model.Password).First();
            if (q != null)
            {
                result.Status = true;
                result.Message = "登录成功";
                result.Data = GenerateJwtToken(q);
                var data = new {code=0, Status = true, Message = "登录成功", Token = GenerateJwtToken(q) };
                return Ok(data);
            }
            else
            {
                result.Status = false;
                result.Message = "错误的账号或密码";
                return Ok(result);
            } 
        }
        private string GenerateJwtToken(User userInfo)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var k = _configuration["Jwt:Key"];
            var e = _configuration["Jwt:DurationInMinutes"];
            var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name,userInfo.UserName),          // 用户名
                new Claim(ClaimTypes.NameIdentifier, userInfo.Id.ToString()), // 用户ID
                new Claim("NickName", userInfo.NickName),               // 自定义昵称声明
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // JWT唯一标识
            };
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                //Subject = new ClaimsIdentity(new[]
                //{
                //new Claim(ClaimTypes.Name, username),
             
                //     }),
                Subject =new ClaimsIdentity(claims),
                
                Expires = DateTime.UtcNow.AddMinutes(int.Parse(_configuration["Jwt:DurationInMinutes"] ?? "600")),
                Issuer = _configuration["Jwt:Issuer"],
                Audience = _configuration["Jwt:Audience"],
                SigningCredentials = new SigningCredentials(
                    new SymmetricSecurityKey(key),
                    SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }
    public class LoginModel
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
}
菜单接口

using System.Security.Claims;
using FyhGrain.Base.HttpModel;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NetCoreWebAPI.Models;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace NetCoreWebAPI.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class MenuController : ControllerBase
    {
       
        [HttpGet]
        public IActionResult Get()
        {
            Models.HttpResult result = new Models.HttpResult();
            var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "0");
            


            var menus = new List<MenuItem>
             {
                    new MenuItem
                    {
                        Id = 1,
                        Title = "系统管理",
                        Icon = "Setting",
                        Path="system",
                        ParentId = 0,
                        Order = 1,
                        Children = new List<MenuItem>
                        {
                            new MenuItem { Id = 2, Title = "用户管理", Path = "/system/users",Icon="Document", ParentId = 1, Order = 1 },
                            new MenuItem { Id = 3, Title = "角色管理", Path = "/system/roles", Icon="Document" , ParentId = 1, Order = 2 },
                            new MenuItem { Id = 3, Title = "权限管理", Path = "/system/Permissions", Icon="Document" , ParentId = 1, Order = 3 },
                            new MenuItem { Id = 3, Title = "系统日志", Path = "/system/log", Icon="Document" , ParentId = 1, Order = 4 },
                            new MenuItem { Id = 3, Title = "个人中心", Path = "/profile", Icon="Document" , ParentId = 1, Order = 5 }
                        }
                    },
                    new MenuItem
                    {
                        Id = 4,
                        Title = "基础设置",
                        Path = "/dashboard",
                        Icon = "Setting",
                        ParentId = 0,
                        Order = 2,
                        Children = new List<MenuItem>
                        {
                            new MenuItem { Id = 2, Title = "粮食管理", Path = "/basic/grains",Icon="Document", ParentId = 1, Order = 1 },
                            new MenuItem { Id = 3, Title = "仓库管理", Path = "/basic/warehouses", Icon="Document" , ParentId = 1, Order = 2 },
                            new MenuItem { Id = 3, Title = "客户管理", Path = "/basic/clients", Icon="Document" , ParentId = 1, Order = 3 },
                            new MenuItem { Id = 3, Title = "系统日志", Path = "/basic/log", Icon="Document" , ParentId = 1, Order = 4 },
                            new MenuItem { Id = 3, Title = "支付方式", Path = "/basic/clients", Icon="Document" , ParentId = 1, Order = 3 },
                            new MenuItem { Id = 3, Title = "打印设计", Path = "/basic/log", Icon="Document" , ParentId = 1, Order = 4 }
                        }
                    },
                    new MenuItem
                    {
                        Id = 4,
                        Title = "费用结算",
                        Path = "/fee",
                        Icon = "Setting",
                        ParentId = 0,
                        Order = 2,
                        Children = new List<MenuItem>
                        {
                            new MenuItem { Id = 2, Title = "过磅结算", Path = "/basic/settlements",Icon="Document", ParentId = 1, Order = 1 },
                            new MenuItem { Id = 3, Title = "运费结算", Path = "/basic/fee", Icon="Document" , ParentId = 1, Order = 2 },
                            new MenuItem { Id = 3, Title = "其他费用结算", Path = "/basic/otherfee", Icon="Document" , ParentId = 1, Order = 3 },
                            //new MenuItem { Id = 3, Title = "系统日志", Path = "/basic/log", Icon="Ticket" , ParentId = 1, Order = 4 }
                        }
                    },
                   
                      new MenuItem
                    {
                        Id = 7,
                        Title = "预收预付",
                        Path = "/settlements",
                        Icon = "Setting",
                        ParentId = 0,
                        Order = 2,
                    },
                      new MenuItem
                    {
                        Id = 7,
                        Title = "其他费用收支",
                        Path = "/settlements",
                        Icon = "Setting",
                        ParentId = 0,
                        Order = 2,
                    }
                      ,
                    new MenuItem
                    {
                        Id = 4,
                        Title = "报表",
                        Path = "/reports",
                        Icon = "Setting",
                        ParentId = 0,
                        Order = 2,
                        Children = new List<MenuItem>
                        {
                            new MenuItem { Id = 2, Title = "地磅日报表", Path = "/basic/settlements",Icon="Document", ParentId = 1, Order = 1 },
                            new MenuItem { Id = 3, Title = "粮贸一览报表", Path = "/basic/fee", Icon="Document" , ParentId = 1, Order = 2 },
                            new MenuItem { Id = 3, Title = "老板报表", Path = "/basic/otherfee", Icon="Document" , ParentId = 1, Order = 3 },
                            new MenuItem { Id = 3, Title = "财务流水报表", Path = "/basic/fee", Icon="Document" , ParentId = 1, Order = 2 },
                            new MenuItem { Id = 3, Title = "财务日报表", Path = "/basic/otherfee", Icon="Document" , ParentId = 1, Order = 3 },
                            new MenuItem { Id = 3, Title = "入库均价表", Path = "/basic/fee", Icon="Document" , ParentId = 1, Order = 2 },
                            new MenuItem { Id = 3, Title = "出库均价表", Path = "/basic/otherfee", Icon="Document" , ParentId = 1, Order = 3 },
                            new MenuItem { Id = 3, Title = "结算记录报表", Path = "/basic/fee", Icon="Document" , ParentId = 1, Order = 2 },
                            new MenuItem { Id = 3, Title = "财务月度汇总", Path = "/basic/otherfee", Icon="Document" , ParentId = 1, Order = 3 },
                            new MenuItem { Id = 3, Title = "应收应付报表", Path = "/basic/log", Icon="Document" , ParentId = 1, Order = 4 },
                            new MenuItem { Id = 3, Title = "质检地磅报表", Path = "/basic/log", Icon="Document" , ParentId = 1, Order = 4 }
                        }
                    },
                };
            result.Data = menus;

            return Ok(result);
        }

       

        // POST api/<MenuController>
        [HttpPost]
        public void Post([FromBody] string value)
        {
        }

        // PUT api/<MenuController>/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE api/<MenuController>/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}
 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sunjay117

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值