登录页面
管理首页

首页代码
<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)
{
}
}
}
1463

被折叠的 条评论
为什么被折叠?



