12月30号管理端界面优化,前后端配置

This commit is contained in:
cxytw 2025-12-30 17:27:55 +08:00
parent ce0e27f9e7
commit 2c73d0c552
5 changed files with 136 additions and 10 deletions

View File

@ -0,0 +1,47 @@
-- ============================================
-- 首页菜单修复脚本 v2
-- 用于确保侧边栏"首页"菜单能正确跳转到主页
-- ============================================
-- 1. 查看当前一级菜单pid=0
SELECT id, pid, name, icon, component, menu_type, sort, is_show, is_delte
FROM eb_system_menu
WHERE pid = 0
ORDER BY sort DESC, id ASC;
-- 2. 查看是否已存在首页相关菜单
SELECT * FROM eb_system_menu
WHERE name IN ('首页', '控制台', '主页')
OR component IN ('/dashboard', '/home', '/home/index');
-- ============================================
-- 修复方案更新现有首页菜单的component为/dashboard
-- ============================================
-- 方案A如果首页菜单存在但component不正确更新它
UPDATE eb_system_menu
SET component = '/dashboard'
WHERE name IN ('首页', '控制台', '主页')
AND (component IS NULL OR component = '' OR component = '/home' OR component = '/home/index');
-- 方案B如果首页菜单不存在插入新记录
-- 注意:先执行上面的查询,确认首页菜单是否存在
-- 检查是否需要插入(如果不存在首页菜单)
-- 如果上面的查询结果为空,执行以下插入语句:
-- 获取当前最大的菜单ID
SELECT MAX(id) as max_id FROM eb_system_menu;
-- 插入首页菜单sort=300 确保排在最前面)
-- 注意请根据实际情况修改id值确保不与现有记录冲突
/*
INSERT INTO `eb_system_menu` (`id`, `pid`, `name`, `icon`, `perms`, `component`, `menu_type`, `sort`, `is_show`, `is_delte`, `create_time`, `update_time`)
VALUES (700, 0, '首页', 'el-icon-s-home', 'admin:dashboard:index', '/dashboard', 'C', 300, 1, 0, NOW(), NOW());
*/
-- 3. 验证修复结果
SELECT id, pid, name, icon, component, menu_type, sort, is_show
FROM eb_system_menu
WHERE name IN ('首页', '控制台', '主页')
OR component = '/dashboard';

View File

@ -159,6 +159,21 @@ export const constantRoutes = [
}, },
], ],
}, },
// 首页路由别名 - 兼容数据库菜单配置的 /home 路径
{
path: '/home',
component: Layout,
redirect: '/home/index',
hidden: true,
children: [
{
path: 'index',
component: () => import('@/views/dashboard/index'),
name: 'Home',
meta: { title: '首页', icon: 'el-icon-s-home', isAffix: true },
},
],
},
{ {
path: '/setting/uploadPicture', path: '/setting/uploadPicture',
component: () => import('@/components/uploadPicture/index.vue'), component: () => import('@/components/uploadPicture/index.vue'),

View File

@ -253,7 +253,15 @@ function replaceChildListWithChildren(data) {
const children = replaceChildListWithChildren(item.childList); const children = replaceChildListWithChildren(item.childList);
// 创建一个新的对象,将 childList 替换为 children // 创建一个新的对象,将 childList 替换为 children
const title = item.name; const title = item.name;
const path = item.component; let path = item.component;
// 特殊处理:如果是首页相关菜单,确保路径正确
if (item.name === '首页' || item.name === '控制台' || item.name === '主页') {
if (!path || path === '' || path === '/home' || path === '/home/index') {
path = '/dashboard';
}
}
return { return {
...item, ...item,
children, children,
@ -265,8 +273,24 @@ function replaceChildListWithChildren(data) {
component: undefined, component: undefined,
}; };
} }
// 如果不存在 childList 字段,直接返回原对象
return item; // 如果不存在 childList 字段,处理叶子节点
let path = item.component;
// 特殊处理:如果是首页相关菜单,确保路径正确
if (item.name === '首页' || item.name === '控制台' || item.name === '主页') {
if (!path || path === '' || path === '/home' || path === '/home/index') {
path = '/dashboard';
}
}
return {
...item,
title: item.name,
path: path,
name: undefined,
component: undefined,
};
}); });
} }

View File

@ -8,11 +8,19 @@
</div> </div>
<el-form :inline="true" :model="queryForm" size="small" class="mb20"> <el-form :inline="true" :model="queryForm" size="small" class="mb20">
<el-form-item label="状态">
<el-select v-model="queryForm.status" placeholder="全部" clearable class="selWidth120">
<el-option label="全部用户" value="" />
<el-option label="仅在线" value="online" />
<el-option label="仅离线" value="offline" />
</el-select>
</el-form-item>
<el-form-item label="搜索"> <el-form-item label="搜索">
<el-input v-model="queryForm.keyword" placeholder="昵称/手机号" clearable class="selWidth" /> <el-input v-model="queryForm.keyword" placeholder="昵称/手机号" clearable class="selWidth" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button> <el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -80,7 +88,7 @@ export default {
loading: false, loading: false,
tableData: [], tableData: [],
total: 0, total: 0,
queryForm: { keyword: '', page: 1, limit: 20 } queryForm: { keyword: '', status: '', page: 1, limit: 20 }
}; };
}, },
mounted() { mounted() {
@ -95,6 +103,7 @@ export default {
this.total = res.total || 0; this.total = res.total || 0;
} catch (error) { } catch (error) {
console.error('获取在线用户失败:', error); console.error('获取在线用户失败:', error);
this.$message.error('获取用户列表失败');
} finally { } finally {
this.loading = false; this.loading = false;
} }
@ -103,6 +112,10 @@ export default {
this.queryForm.page = 1; this.queryForm.page = 1;
this.getList(); this.getList();
}, },
handleReset() {
this.queryForm = { keyword: '', status: '', page: 1, limit: 20 };
this.getList();
},
handleSizeChange(val) { handleSizeChange(val) {
this.queryForm.limit = val; this.queryForm.limit = val;
this.getList(); this.getList();
@ -129,6 +142,7 @@ export default {
.header-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .header-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.page-title { font-size: 18px; color: #303133; margin: 0; } .page-title { font-size: 18px; color: #303133; margin: 0; }
.selWidth { width: 200px; } .selWidth { width: 200px; }
.selWidth120 { width: 120px; }
.mb20 { margin-bottom: 20px; } .mb20 { margin-bottom: 20px; }
.mt20 { margin-top: 20px; } .mt20 { margin-top: 20px; }
.user-info { display: flex; align-items: center; gap: 12px; } .user-info { display: flex; align-items: center; gap: 12px; }

View File

@ -96,27 +96,39 @@ public class MonitorController {
/** /**
* 在线用户列表 * 在线用户列表
* 显示所有用户按最后登录时间排序标记在线状态5分钟内活跃为在线
*/ */
@ApiOperation(value = "在线用户列表") @ApiOperation(value = "在线用户列表")
@GetMapping("/users") @GetMapping("/users")
public CommonResult<CommonPage<Map<String, Object>>> getOnlineUsers( public CommonResult<CommonPage<Map<String, Object>>> getOnlineUsers(
@RequestParam(value = "keyword", required = false) String keyword, @RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "status", required = false) String status,
@RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "limit", defaultValue = "20") Integer limit) { @RequestParam(value = "limit", defaultValue = "20") Integer limit) {
// 获取最近活跃的用户5分钟内有登录记录 // 获取用户列表按最后登录时间排序
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append("SELECT u.uid as id, u.nickname, u.avatar, u.phone, "); sql.append("SELECT u.uid as id, u.nickname, u.avatar, u.phone, ");
sql.append("u.last_login_time, u.create_time, "); sql.append("u.last_login_time, u.create_time, ");
sql.append("CASE WHEN u.last_login_time >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) THEN 1 ELSE 0 END as is_online, ");
sql.append("TIMESTAMPDIFF(MINUTE, u.last_login_time, NOW()) as inactive_minutes "); sql.append("TIMESTAMPDIFF(MINUTE, u.last_login_time, NOW()) as inactive_minutes ");
sql.append("FROM eb_user u "); sql.append("FROM eb_user u ");
sql.append("WHERE u.status = 1 "); sql.append("WHERE u.status = 1 ");
sql.append("AND u.last_login_time >= DATE_SUB(NOW(), INTERVAL 30 MINUTE) ");
StringBuilder countSql = new StringBuilder(); StringBuilder countSql = new StringBuilder();
countSql.append("SELECT COUNT(*) FROM eb_user u "); countSql.append("SELECT COUNT(*) FROM eb_user u ");
countSql.append("WHERE u.status = 1 "); countSql.append("WHERE u.status = 1 ");
countSql.append("AND u.last_login_time >= DATE_SUB(NOW(), INTERVAL 30 MINUTE) ");
// 根据状态筛选online=仅在线offline=仅离线=全部
if ("online".equals(status)) {
String condition = " AND u.last_login_time >= DATE_SUB(NOW(), INTERVAL 5 MINUTE) ";
sql.append(condition);
countSql.append(condition);
} else if ("offline".equals(status)) {
String condition = " AND (u.last_login_time IS NULL OR u.last_login_time < DATE_SUB(NOW(), INTERVAL 5 MINUTE)) ";
sql.append(condition);
countSql.append(condition);
}
if (keyword != null && !keyword.isEmpty()) { if (keyword != null && !keyword.isEmpty()) {
String condition = " AND (u.nickname LIKE '%" + keyword + "%' OR u.phone LIKE '%" + keyword + "%') "; String condition = " AND (u.nickname LIKE '%" + keyword + "%' OR u.phone LIKE '%" + keyword + "%') ";
@ -124,7 +136,8 @@ public class MonitorController {
countSql.append(condition); countSql.append(condition);
} }
sql.append(" ORDER BY u.last_login_time DESC "); // 优先显示在线用户然后按最后登录时间排序
sql.append(" ORDER BY is_online DESC, u.last_login_time DESC ");
Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class); Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class);
@ -135,10 +148,23 @@ public class MonitorController {
// 添加在线状态 // 添加在线状态
list.forEach(item -> { list.forEach(item -> {
Integer inactiveMinutes = (Integer) item.get("inactive_minutes"); Object inactiveObj = item.get("inactive_minutes");
item.put("isOnline", inactiveMinutes != null && inactiveMinutes < 5); Long inactiveMinutes = null;
if (inactiveObj instanceof Long) {
inactiveMinutes = (Long) inactiveObj;
} else if (inactiveObj instanceof Integer) {
inactiveMinutes = ((Integer) inactiveObj).longValue();
}
// 5分钟内活跃视为在线
boolean isOnline = inactiveMinutes != null && inactiveMinutes < 5;
item.put("isOnline", isOnline);
item.put("lastLoginTime", item.get("last_login_time")); item.put("lastLoginTime", item.get("last_login_time"));
item.put("createTime", item.get("create_time")); item.put("createTime", item.get("create_time"));
// 移除不需要的字段
item.remove("is_online");
item.remove("inactive_minutes");
item.remove("last_login_time");
item.remove("create_time");
}); });
CommonPage<Map<String, Object>> result = new CommonPage<>(); CommonPage<Map<String, Object>> result = new CommonPage<>();