优化菜单加载
This commit is contained in:
parent
ca1d265e34
commit
34035fb007
7
pc/package-lock.json
generated
7
pc/package-lock.json
generated
@ -16,6 +16,7 @@
|
||||
"element-plus": "^2.11.7",
|
||||
"less": "^4.4.2",
|
||||
"marked": "^16.4.1",
|
||||
"os": "^0.1.2",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.6.3"
|
||||
@ -3743,6 +3744,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/os": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/os/-/os-0.1.2.tgz",
|
||||
"integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/own-keys": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"element-plus": "^2.11.7",
|
||||
"less": "^4.4.2",
|
||||
"marked": "^16.4.1",
|
||||
"os": "^0.1.2",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.6.3"
|
||||
|
||||
@ -49,3 +49,6 @@ body {
|
||||
border-bottom: 1px solid #dcdfe6 !important;
|
||||
}
|
||||
}
|
||||
.el-form-item__label{
|
||||
min-width: 60px;
|
||||
}
|
||||
@ -58,7 +58,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useAllDataStore } from '@/stores';
|
||||
import { getAllMenus } from '@/api/menu';
|
||||
@ -245,6 +245,17 @@ onMounted(() => {
|
||||
setTimeout(() => {
|
||||
fetchMenus();
|
||||
}, 100);
|
||||
|
||||
// 监听菜单缓存刷新事件
|
||||
window.addEventListener('menu-cache-refreshed', fetchMenus);
|
||||
});
|
||||
|
||||
// 组件卸载时清理事件监听
|
||||
onUnmounted(() => {
|
||||
if (themeObserver) {
|
||||
themeObserver.disconnect();
|
||||
}
|
||||
window.removeEventListener('menu-cache-refreshed', fetchMenus);
|
||||
});
|
||||
|
||||
// 计算属性:统一排序所有菜单项(不再区分有无子菜单)
|
||||
@ -294,8 +305,14 @@ const handleMenuSelect = (index) => {
|
||||
}
|
||||
|
||||
const menuItem = findMenuItemByPath(list.value, index);
|
||||
if (menuItem && menuItem.route) {
|
||||
emit('menu-click', menuItem);
|
||||
if (menuItem && menuItem.path) {
|
||||
// 使用 path 而不是 route,确保菜单项有路径
|
||||
const menuToEmit = {
|
||||
...menuItem,
|
||||
route: menuItem.path, // 兼容性:添加 route 字段
|
||||
label: menuItem.name || menuItem.label
|
||||
};
|
||||
emit('menu-click', menuToEmit);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -16,6 +16,16 @@
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
<div class="r-content">
|
||||
<!-- 更新缓存按钮 -->
|
||||
<el-button
|
||||
circle
|
||||
:icon="Refresh"
|
||||
@click="refreshCache"
|
||||
class="refresh-cache-btn"
|
||||
:loading="cacheLoading"
|
||||
title="更新菜单缓存"
|
||||
/>
|
||||
|
||||
<!-- 主题切换按钮 -->
|
||||
<el-button
|
||||
circle
|
||||
@ -50,8 +60,9 @@ import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { useAllDataStore } from "@/stores";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { User, SwitchButton, Sunny, Moon } from '@element-plus/icons-vue';
|
||||
import { getAllMenus } from '@/api/menu';
|
||||
import { User, SwitchButton, Sunny, Moon, Refresh } from '@element-plus/icons-vue';
|
||||
import { getAllMenus } from '@/api/menu';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@ -69,14 +80,95 @@ interface Breadcrumb {
|
||||
}
|
||||
|
||||
const menuList = ref<Menu[]>([]);
|
||||
const cacheLoading = ref(false);
|
||||
const MENU_CACHE_KEY = 'menu_cache';
|
||||
|
||||
async function loadMenu() {
|
||||
// 从缓存加载菜单
|
||||
function loadMenuFromCache(): Menu[] | null {
|
||||
try {
|
||||
const cached = localStorage.getItem(MENU_CACHE_KEY);
|
||||
if (cached) {
|
||||
const menuData = JSON.parse(cached);
|
||||
// 检查缓存是否过期(可选:设置过期时间,这里暂时不设置)
|
||||
return menuData;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load menu from cache', error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 保存菜单到缓存
|
||||
function saveMenuToCache(menus: Menu[]) {
|
||||
try {
|
||||
localStorage.setItem(MENU_CACHE_KEY, JSON.stringify(menus));
|
||||
} catch (error) {
|
||||
console.error('Failed to save menu to cache', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 从API加载菜单
|
||||
async function loadMenuFromAPI(updateCache = true) {
|
||||
try {
|
||||
const res = await getAllMenus();
|
||||
menuList.value = res.data || [];
|
||||
const menus = res.data || [];
|
||||
if (updateCache && menus.length > 0) {
|
||||
saveMenuToCache(menus);
|
||||
}
|
||||
return menus;
|
||||
} catch (error) {
|
||||
console.error('Failed to load menu', error);
|
||||
menuList.value = [];
|
||||
console.error('Failed to load menu from API', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 加载菜单(优先从缓存)
|
||||
async function loadMenu() {
|
||||
// 先尝试从缓存加载
|
||||
const cachedMenus = loadMenuFromCache();
|
||||
if (cachedMenus && cachedMenus.length > 0) {
|
||||
menuList.value = cachedMenus;
|
||||
// 异步更新缓存(后台更新,不阻塞UI)
|
||||
loadMenuFromAPI(true).then(menus => {
|
||||
if (menus.length > 0) {
|
||||
menuList.value = menus;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 缓存不存在,从API加载
|
||||
menuList.value = await loadMenuFromAPI(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新缓存(手动刷新)
|
||||
async function refreshCache() {
|
||||
cacheLoading.value = true;
|
||||
try {
|
||||
const menus = await loadMenuFromAPI(true);
|
||||
if (menus.length > 0) {
|
||||
menuList.value = menus;
|
||||
|
||||
// 重新加载动态路由
|
||||
const { loadAndAddDynamicRoutes, resetDynamicRoutes } = await import('@/router/index');
|
||||
// 重置路由加载状态,强制重新加载
|
||||
resetDynamicRoutes();
|
||||
await loadAndAddDynamicRoutes();
|
||||
|
||||
// 等待路由完全加载(给Vue Router一些时间更新路由表)
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// 触发菜单刷新事件,通知CommonAside组件刷新菜单
|
||||
window.dispatchEvent(new CustomEvent('menu-cache-refreshed'));
|
||||
|
||||
ElMessage.success('菜单缓存和路由更新成功');
|
||||
} else {
|
||||
ElMessage.warning('未获取到菜单数据');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh cache', error);
|
||||
ElMessage.error('更新缓存失败,请检查网络连接');
|
||||
} finally {
|
||||
cacheLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +211,7 @@ const handleCollapse = () => {
|
||||
store.state.isCollapse = !store.state.isCollapse;
|
||||
};
|
||||
|
||||
const handleCommand = (command) => {
|
||||
const handleCommand = async (command) => {
|
||||
if (command === 'profile') {
|
||||
router.push('/user/userProfile');
|
||||
} else if (command === 'logout') {
|
||||
@ -130,6 +222,18 @@ const handleCommand = (command) => {
|
||||
//清除租户数据
|
||||
localStorage.removeItem('tenant');
|
||||
sessionStorage.removeItem('tenant');
|
||||
//清除tabs_list缓存
|
||||
localStorage.removeItem('tabs_list');
|
||||
localStorage.removeItem('active_tab');
|
||||
sessionStorage.removeItem('tabs_list');
|
||||
// 清除菜单缓存
|
||||
localStorage.removeItem(MENU_CACHE_KEY);
|
||||
|
||||
// 重置 tabs store 状态
|
||||
const { useTabsStore } = await import('@/stores');
|
||||
const tabsStore = useTabsStore();
|
||||
tabsStore.resetTabs();
|
||||
|
||||
router.push('/login');
|
||||
}
|
||||
};
|
||||
@ -254,6 +358,21 @@ onUnmounted(() => {
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.refresh-cache-btn,
|
||||
.theme-toggle-btn {
|
||||
// 使用 Element Plus 的填充色变量
|
||||
background-color: var(--el-fill-color-light);
|
||||
border-color: var(--el-border-color);
|
||||
color: var(--el-text-color-primary);
|
||||
margin-left: 0 !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color);
|
||||
border-color: var(--el-border-color-dark);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
@ -50,6 +50,12 @@ let dynamicRoutesAdded = false;
|
||||
// 路由加载 Promise,用于确保路由加载完成
|
||||
let routesLoadingPromise = null;
|
||||
|
||||
// 重置动态路由状态,允许重新加载
|
||||
export function resetDynamicRoutes() {
|
||||
dynamicRoutesAdded = false;
|
||||
routesLoadingPromise = null;
|
||||
}
|
||||
|
||||
// 从 API 加载并添加动态路由
|
||||
export async function loadAndAddDynamicRoutes() {
|
||||
// 如果已经有加载中的 Promise,直接返回它
|
||||
|
||||
@ -169,6 +169,15 @@ export const useTabsStore = defineTabsStore('tabs', () => {
|
||||
saveTabsToStorage(tabList.value, activeTab.value);
|
||||
}
|
||||
|
||||
// 重置 tabs store 到初始状态(登出时使用)
|
||||
function resetTabs() {
|
||||
tabList.value = [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }];
|
||||
activeTab.value = defaultDashboardPath;
|
||||
// 清除 localStorage 中的 tabs 数据
|
||||
localStorage.removeItem('tabs_list');
|
||||
localStorage.removeItem('active_tab');
|
||||
}
|
||||
|
||||
return {
|
||||
tabList,
|
||||
activeTab,
|
||||
@ -180,5 +189,6 @@ export const useTabsStore = defineTabsStore('tabs', () => {
|
||||
closeAll,
|
||||
setActiveTab,
|
||||
saveTabsToStorage,
|
||||
resetTabs,
|
||||
};
|
||||
});
|
||||
@ -4,7 +4,8 @@ import CommonHeader from '@/components/CommonHeader.vue';
|
||||
import { useTabsStore } from '@/stores';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { ref, watch, reactive, nextTick, onMounted, computed } from 'vue';
|
||||
import { More, Close, CircleClose, ArrowUp } from '@element-plus/icons-vue'
|
||||
import { More, Close, CircleClose, ArrowUp } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const tabsStore = useTabsStore();
|
||||
const router = useRouter();
|
||||
@ -96,16 +97,47 @@ watch(
|
||||
);
|
||||
|
||||
// 1. 侧栏菜单点击:加入/激活Tab并切换路由
|
||||
// 注意:路由跳转由 watch(tabsStore.activeTab) 统一处理,避免重复调用
|
||||
const handleAsideMenuClick = (menuItem) => {
|
||||
const handleAsideMenuClick = async (menuItem) => {
|
||||
const targetPath = menuItem.path;
|
||||
|
||||
// 先添加tab
|
||||
tabsStore.addTab({
|
||||
title: menuItem.label,
|
||||
fullPath: menuItem.path,
|
||||
fullPath: targetPath,
|
||||
name: menuItem.label,
|
||||
icon: menuItem.icon
|
||||
});
|
||||
// 移除这里的 router.push,由下面的 watch 统一处理路由跳转
|
||||
// 这样可以避免重复跳转,确保 keep-alive 正常工作
|
||||
|
||||
// 如果当前路由已经是目标路由,不需要跳转
|
||||
if (router.currentRoute.value.fullPath === targetPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查路由是否存在,如果不存在等待一下再尝试
|
||||
let routeExists = router.resolve(targetPath).matched.length > 0;
|
||||
|
||||
if (!routeExists) {
|
||||
// 等待路由加载(最多等待500ms)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
routeExists = router.resolve(targetPath).matched.length > 0;
|
||||
if (routeExists) break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果路由存在,直接跳转
|
||||
if (routeExists) {
|
||||
router.push(targetPath).catch(err => {
|
||||
if (err.name !== 'NavigationDuplicated') {
|
||||
console.error('路由跳转失败:', err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 如果路由不存在,尝试刷新页面(最后的手段)
|
||||
console.warn('路由不存在,尝试刷新页面:', targetPath);
|
||||
// 不刷新页面,而是显示错误提示
|
||||
ElMessage.warning(`路由 ${targetPath} 不存在,请刷新缓存后重试`);
|
||||
}
|
||||
};
|
||||
const tabsCloseTab = (targetKey) => {
|
||||
tabsStore.removeTab(targetKey);
|
||||
@ -123,22 +155,39 @@ const closeAll = () => {
|
||||
tabsStore.closeAll();
|
||||
router.push(defaultDashboardPath);
|
||||
};
|
||||
// 主动监听tab激活,保证切tab时内容区切换
|
||||
// 主动监听tab激活,保证切tab时内容区切换(仅用于tab点击切换,菜单点击由handleAsideMenuClick处理)
|
||||
// 使用 immediate: false 避免初始化时触发,使用 flush: 'post' 确保在 DOM 更新后执行
|
||||
watch(
|
||||
() => tabsStore.activeTab,
|
||||
(newVal, oldVal) => {
|
||||
// 如果新值和当前路由路径不同,且不是初始化(oldVal 不为 undefined),才进行跳转
|
||||
// 注意:这个watch主要用于处理tab点击切换,菜单点击由handleAsideMenuClick直接处理
|
||||
if (newVal && oldVal !== undefined && router.currentRoute.value.fullPath !== newVal) {
|
||||
// 使用 nextTick 确保路由更新完成后再跳转,避免 keep-alive 缓存问题
|
||||
nextTick(() => {
|
||||
router.push(newVal).catch(err => {
|
||||
// 忽略导航重复的错误(用户快速点击时可能出现)
|
||||
if (err.name !== 'NavigationDuplicated') {
|
||||
console.error('路由跳转失败:', err);
|
||||
}
|
||||
// 检查路由是否存在
|
||||
const routeExists = router.resolve(newVal).matched.length > 0;
|
||||
|
||||
if (routeExists) {
|
||||
// 路由存在,直接跳转
|
||||
nextTick(() => {
|
||||
router.push(newVal).catch(err => {
|
||||
if (err.name !== 'NavigationDuplicated') {
|
||||
console.error('路由跳转失败:', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// 如果路由不存在,等待一下再重试(可能是路由还在加载中)
|
||||
setTimeout(() => {
|
||||
const retryRouteExists = router.resolve(newVal).matched.length > 0;
|
||||
if (retryRouteExists && router.currentRoute.value.fullPath !== newVal) {
|
||||
router.push(newVal).catch(err => {
|
||||
if (err.name !== 'NavigationDuplicated') {
|
||||
console.error('路由跳转失败:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ flush: 'post' }
|
||||
|
||||
@ -3,6 +3,7 @@ import { ref, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { login } from "@/api/login";
|
||||
import { getAllMenus } from "@/api/menu";
|
||||
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
@ -68,6 +69,23 @@ const handleLogin = async () => {
|
||||
authStore.setLoginInfo(res.data);
|
||||
// 登录如果有返回租户信息
|
||||
cacheTenant(tenantName);
|
||||
|
||||
// 登录成功后重置 tabs store 为初始状态
|
||||
const { useTabsStore } = await import('@/stores');
|
||||
const tabsStore = useTabsStore();
|
||||
tabsStore.resetTabs();
|
||||
|
||||
// 登录成功后缓存菜单
|
||||
try {
|
||||
const menuRes = await getAllMenus();
|
||||
if (menuRes && menuRes.data && menuRes.data.length > 0) {
|
||||
localStorage.setItem('menu_cache', JSON.stringify(menuRes.data));
|
||||
}
|
||||
} catch (menuError) {
|
||||
console.error('Failed to cache menu on login', menuError);
|
||||
// 菜单缓存失败不影响登录流程
|
||||
}
|
||||
|
||||
router.push({ path: "/dashboard" });
|
||||
} else {
|
||||
errorMsg.value = res.message || "登录失败";
|
||||
|
||||
@ -44,8 +44,8 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">
|
||||
{{ scope.row.status === "active" ? "启用" : "禁用" }}
|
||||
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
|
||||
{{ scope.row.status === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -55,6 +55,16 @@
|
||||
width="180"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="lastLoginIp"
|
||||
label="最后登录IP"
|
||||
width="150"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.lastLoginIp || '未知' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleEdit(scope.row)"
|
||||
@ -63,7 +73,11 @@
|
||||
<el-button size="small" type="warning" @click="handleChangePassword(scope.row)">
|
||||
修改密码
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row)"
|
||||
<el-button
|
||||
v-if="scope.row.username !== 'admin'"
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
@ -297,16 +311,21 @@ const fetchUsers = async () => {
|
||||
roleName = roleInfo ? roleInfo.roleName : '';
|
||||
}
|
||||
|
||||
// 处理最后登录时间,后端返回的是 last_login_time
|
||||
const lastLoginTime = item.last_login_time || item.lastLoginTime || null;
|
||||
// 处理最后登录IP,后端返回的是 last_login_ip
|
||||
const lastLoginIp = item.last_login_ip || item.lastLoginIp || null;
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
username: item.username,
|
||||
nickname: item.nickname,
|
||||
email: item.email,
|
||||
role: roleValue, // role 字段存储角色ID
|
||||
role: roleValue,
|
||||
roleName: roleName,
|
||||
status: item.status || "active",
|
||||
lastLoginTime: item.lastLoginTime
|
||||
? new Date(item.lastLoginTime).toLocaleString("zh-CN", {
|
||||
status: item.status,
|
||||
lastLoginTime: lastLoginTime
|
||||
? new Date(lastLoginTime).toLocaleString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
@ -315,7 +334,8 @@ const fetchUsers = async () => {
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
})
|
||||
: "",
|
||||
: "从未登录",
|
||||
lastLoginIp: lastLoginIp || null,
|
||||
};
|
||||
});
|
||||
total.value = users.value.length;
|
||||
|
||||
@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"server/models"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
@ -81,10 +82,50 @@ func (c *AuthController) Login() {
|
||||
"data": nil,
|
||||
}
|
||||
} else {
|
||||
// 登录成功,写当前时间到last_login_time,并增加login_count
|
||||
// 登录成功,写当前时间到last_login_time,获取IP写入last_login_ip,并增加login_count
|
||||
loginTime := time.Now()
|
||||
// 获取客户端IP地址
|
||||
clientIP := c.Ctx.Input.IP()
|
||||
|
||||
// 优先从X-Forwarded-For获取真实IP(适用于代理环境)
|
||||
forwardedFor := c.Ctx.Input.Header("X-Forwarded-For")
|
||||
if forwardedFor != "" {
|
||||
// X-Forwarded-For可能包含多个IP,取第一个
|
||||
ips := strings.Split(forwardedFor, ",")
|
||||
if len(ips) > 0 {
|
||||
ip := strings.TrimSpace(ips[0])
|
||||
// 过滤掉本地地址
|
||||
if ip != "" && ip != "::1" && ip != "127.0.0.1" && !strings.HasPrefix(ip, "192.168.") && !strings.HasPrefix(ip, "10.") && !strings.HasPrefix(ip, "172.16.") {
|
||||
clientIP = ip
|
||||
} else if ip != "" {
|
||||
clientIP = ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果X-Forwarded-For没有有效IP,尝试从X-Real-IP获取
|
||||
if clientIP == "" || clientIP == "::1" || clientIP == "127.0.0.1" {
|
||||
realIP := c.Ctx.Input.Header("X-Real-IP")
|
||||
if realIP != "" {
|
||||
ip := strings.TrimSpace(realIP)
|
||||
if ip != "::1" && ip != "127.0.0.1" {
|
||||
clientIP = ip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果获取到的是IPv6的localhost,转换为IPv4格式显示
|
||||
if clientIP == "::1" {
|
||||
clientIP = "127.0.0.1"
|
||||
}
|
||||
|
||||
// 如果仍然没有获取到IP,使用默认值
|
||||
if clientIP == "" {
|
||||
clientIP = "unknown"
|
||||
}
|
||||
|
||||
o := orm.NewOrm()
|
||||
_, _ = o.Raw("UPDATE yz_users SET last_login_time = ?, login_count = IFNULL(login_count,0)+1 WHERE id = ?", loginTime, user.Id).Exec()
|
||||
_, _ = o.Raw("UPDATE yz_users SET last_login_time = ?, last_login_ip = ?, login_count = IFNULL(login_count,0)+1 WHERE id = ?", loginTime, clientIP, user.Id).Exec()
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 0,
|
||||
|
||||
@ -78,6 +78,7 @@ func (c *UserController) GetTenantUsers() {
|
||||
"status": user.Status,
|
||||
"role": user.Role,
|
||||
"last_login_time": user.LastLoginTime,
|
||||
"last_login_ip": user.LastLoginIp,
|
||||
})
|
||||
}
|
||||
|
||||
@ -364,6 +365,29 @@ func (c *UserController) DeleteUser() {
|
||||
return
|
||||
}
|
||||
|
||||
// 先查询用户信息,检查是否为admin账号
|
||||
user, err := models.GetUserInfo(userId, "", 0)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "查询用户失败: " + err.Error(),
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 禁止删除admin账号
|
||||
if user.Username == "admin" {
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 1,
|
||||
"message": "admin账号不允许删除",
|
||||
"data": nil,
|
||||
}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 调用模型层方法删除用户
|
||||
err = models.DeleteUser(userId)
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ type User struct {
|
||||
Role int `orm:"column(role);default(0)" json:"role"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);null;type(datetime)" json:"delete_time"`
|
||||
LastLoginTime *time.Time `orm:"column(last_login_time);null;type(datetime)" json:"last_login_time"`
|
||||
LastLoginIp string `orm:"column(last_login_ip);null;size(50)" json:"last_login_ip"`
|
||||
}
|
||||
|
||||
// TableName 设置表名,默认为yz_users
|
||||
|
||||
Loading…
Reference in New Issue
Block a user