修复路由和主题
This commit is contained in:
parent
bfba8d7ad6
commit
01c47ccbd4
@ -1,79 +1,8 @@
|
||||
// Element Plus 主题变量覆盖
|
||||
// Element Plus Message z-index
|
||||
:root {
|
||||
// 基础颜色
|
||||
--el-color-primary: #409eff;
|
||||
--el-color-success: #67c23a;
|
||||
--el-color-warning: #e6a23c;
|
||||
--el-color-danger: #f56c6c;
|
||||
--el-color-info: #909399;
|
||||
|
||||
// 背景颜色
|
||||
--el-bg-color: #ffffff;
|
||||
--el-bg-color-page: #f2f3f5;
|
||||
--el-bg-color-overlay: #ffffff;
|
||||
|
||||
// 文字颜色
|
||||
--el-text-color-primary: #303133;
|
||||
--el-text-color-regular: #606266;
|
||||
--el-text-color-secondary: #909399;
|
||||
--el-text-color-placeholder: #a8abb2;
|
||||
--el-text-color-disabled: #c0c4cc;
|
||||
|
||||
// 边框颜色
|
||||
--el-border-color: #dcdfe6;
|
||||
--el-border-color-light: #e4e7ed;
|
||||
--el-border-color-lighter: #ebeef5;
|
||||
--el-border-color-extra-light: #f2f6fc;
|
||||
--el-border-color-dark: #d4d7de;
|
||||
--el-border-color-darker: #cdd0d6;
|
||||
|
||||
// 填充颜色
|
||||
--el-fill-color: #f0f2f5;
|
||||
--el-fill-color-light: #f5f7fa;
|
||||
--el-fill-color-lighter: #fafafa;
|
||||
--el-fill-color-extra-light: #fafcff;
|
||||
--el-fill-color-dark: #ebedf0;
|
||||
--el-fill-color-darker: #e6e8eb;
|
||||
--el-fill-color-blank: #ffffff;
|
||||
|
||||
// Message z-index
|
||||
--el-message-z-index: 9999;
|
||||
}
|
||||
|
||||
// 暗色主题
|
||||
[data-theme="dark"] {
|
||||
--el-color-primary: #409eff;
|
||||
--el-color-success: #67c23a;
|
||||
--el-color-warning: #e6a23c;
|
||||
--el-color-danger: #f56c6c;
|
||||
--el-color-info: #909399;
|
||||
|
||||
--el-bg-color: #1d1e1f;
|
||||
--el-bg-color-page: #141414;
|
||||
--el-bg-color-overlay: #1d1e1f;
|
||||
|
||||
--el-text-color-primary: #e5eaf3;
|
||||
--el-text-color-regular: #cfd3dc;
|
||||
--el-text-color-secondary: #a3a6ad;
|
||||
--el-text-color-placeholder: #6c6e72;
|
||||
--el-text-color-disabled: #6c6e72;
|
||||
|
||||
--el-border-color: #4c4d4f;
|
||||
--el-border-color-light: #414243;
|
||||
--el-border-color-lighter: #363637;
|
||||
--el-border-color-extra-light: #2b2b2c;
|
||||
--el-border-color-dark: #58585b;
|
||||
--el-border-color-darker: #636466;
|
||||
|
||||
--el-fill-color: #2b2b2c;
|
||||
--el-fill-color-light: #262727;
|
||||
--el-fill-color-lighter: #1d1e1f;
|
||||
--el-fill-color-extra-light: #191919;
|
||||
--el-fill-color-dark: #39393a;
|
||||
--el-fill-color-darker: #424243;
|
||||
--el-fill-color-blank: transparent;
|
||||
}
|
||||
|
||||
// body 样式
|
||||
body {
|
||||
background-color: var(--el-bg-color-page);
|
||||
@ -91,9 +20,6 @@ body {
|
||||
transition: background-color 0.3s, border-color 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .container-box {
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.09);
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
@ -106,3 +32,12 @@ body {
|
||||
justify-content: flex-end;
|
||||
margin: 14px 0 0 0;
|
||||
}
|
||||
|
||||
// 修复 ElMessage 显示问题
|
||||
// 只修复定位,保持 Element Plus 官方样式
|
||||
.el-message {
|
||||
// 确保消息固定在页面顶部中央,不受父容器影响
|
||||
position: fixed !important;
|
||||
z-index: var(--el-message-z-index, 9999) !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
@ -9,7 +9,7 @@
|
||||
:background-color="asideBgColor"
|
||||
:text-color="asideTextColor"
|
||||
:active-text-color="activeColor"
|
||||
active-background-color="activeBgColor"
|
||||
:active-background-color="activeBgColor"
|
||||
class="el-menu-vertical-demo"
|
||||
@select="handleMenuSelect"
|
||||
:default-active="route.path"
|
||||
@ -73,31 +73,61 @@ const loading = ref(true);
|
||||
const store = useAllDataStore();
|
||||
const isCollapse = computed(() => store.state.isCollapse);
|
||||
const width = computed(() => store.state.isCollapse ? '64px' : '180px');
|
||||
const asideBgColor = ref('#0074e9');
|
||||
const asideTextColor = ref('#ffffff');
|
||||
const activeColor = ref ('#fff');
|
||||
const activeBgColor = ref ('#0074e9');
|
||||
// 使用 Element Plus 的颜色变量,主题切换时会自动适配
|
||||
// 这些值会被 el-menu 组件使用,el-menu 会自动适配主题
|
||||
// 计算是否为暗色主题(响应式)
|
||||
const isDark = ref(document.documentElement.classList.contains('dark'));
|
||||
|
||||
// 更新主题颜色
|
||||
const updateThemeColors = () => {
|
||||
try {
|
||||
const root = document.documentElement;
|
||||
const bgColor = getComputedStyle(root).getPropertyValue('--aside-bg-color').trim();
|
||||
const textColor = getComputedStyle(root).getPropertyValue('--aside-text-color').trim();
|
||||
|
||||
// 只有当获取到有效值时才更新
|
||||
if (bgColor) {
|
||||
asideBgColor.value = bgColor;
|
||||
}
|
||||
if (textColor) {
|
||||
asideTextColor.value = textColor;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('更新主题颜色失败:', e);
|
||||
// 出错时保持默认值
|
||||
}
|
||||
// 监听主题变化
|
||||
const updateTheme = () => {
|
||||
isDark.value = document.documentElement.classList.contains('dark');
|
||||
};
|
||||
|
||||
// 使用 MutationObserver 监听 html 元素的 class 变化(主题切换时更新)
|
||||
let themeObserver = null;
|
||||
|
||||
// 自定义颜色配置
|
||||
const BG_COLOR = '#062da3'; // 背景和 active 颜色
|
||||
const HOVER_COLOR = '#4f84ff'; // hover 颜色
|
||||
|
||||
// 将 hex 颜色转换为 rgba
|
||||
function hexToRgba(hex, alpha = 1) {
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
|
||||
// 亮色主题使用自定义背景色,暗色主题使用默认背景
|
||||
const asideBgColor = computed(() => {
|
||||
if (isDark.value) {
|
||||
return 'var(--el-bg-color)';
|
||||
}
|
||||
// 亮色主题使用 #062da3 背景
|
||||
return BG_COLOR;
|
||||
});
|
||||
|
||||
const asideTextColor = computed(() => {
|
||||
if (isDark.value) {
|
||||
return 'var(--el-text-color-primary)';
|
||||
}
|
||||
// 亮色主题使用白色文字
|
||||
return '#ffffff';
|
||||
});
|
||||
|
||||
const activeColor = ref('#ffffff'); // active 文字颜色为白色
|
||||
|
||||
// hover 和 active 背景色
|
||||
const activeBgColor = computed(() => {
|
||||
if (isDark.value) {
|
||||
return 'rgba(255, 255, 255, 0.1)';
|
||||
}
|
||||
// 亮色主题 active 使用 #4f84ff
|
||||
return HOVER_COLOR;
|
||||
});
|
||||
|
||||
// 不再需要手动更新主题颜色,因为使用了 Element Plus 的 CSS 变量,会自动适配
|
||||
|
||||
// 将后端数据转换为前端需要的格式
|
||||
const transformMenuData = (menus) => {
|
||||
// 首先映射字段格式
|
||||
@ -213,25 +243,25 @@ const fetchMenus = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时获取菜单
|
||||
// 组件挂载时初始化主题监听和获取菜单
|
||||
onMounted(() => {
|
||||
// 先更新主题颜色,确保组件可见
|
||||
updateThemeColors();
|
||||
// 初始化主题监听
|
||||
themeObserver = new MutationObserver(() => {
|
||||
updateTheme();
|
||||
});
|
||||
|
||||
themeObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
|
||||
// 初始化时检查一次主题
|
||||
updateTheme();
|
||||
|
||||
// 延迟一点获取菜单,确保主题已初始化
|
||||
setTimeout(() => {
|
||||
fetchMenus();
|
||||
}, 100);
|
||||
|
||||
// 监听主题变化事件
|
||||
window.addEventListener('theme-change', updateThemeColors);
|
||||
|
||||
// 监听 CSS 变量变化(MutationObserver)
|
||||
const observer = new MutationObserver(updateThemeColors);
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['data-theme']
|
||||
});
|
||||
});
|
||||
|
||||
// 计算属性:统一排序所有菜单项(不再区分有无子菜单)
|
||||
@ -304,12 +334,18 @@ const findMenuItemByPath = (menus, path) => {
|
||||
<style scoped lang="less">
|
||||
.common-aside {
|
||||
height: 100%;
|
||||
background-color: var(--aside-bg-color, #0074e9);
|
||||
// 亮色主题使用 primary 浅色,暗色主题使用默认背景
|
||||
background-color: var(--el-bg-color);
|
||||
transition: width 0.3s, background-color 0.3s ease;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
|
||||
// 亮色主题下的特殊处理
|
||||
html:not(.dark) & {
|
||||
background-color: #062da3;
|
||||
}
|
||||
}
|
||||
|
||||
.icons {
|
||||
@ -318,8 +354,10 @@ const findMenuItemByPath = (menus, path) => {
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
transition: var(--transition-fast);
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
|
||||
// 亮色主题下默认使用白色
|
||||
html:not(.dark) & {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,6 +368,121 @@ h3{
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
// 亮色主题使用白色,暗色主题使用默认文字颜色
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
html:not(.dark) & {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单项样式:优化 hover 和 active 状态的视觉效果
|
||||
:deep(.el-menu) {
|
||||
.el-menu-item {
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
// hover 状态:亮色主题使用 #4f84ff
|
||||
&:hover:not(.is-active) {
|
||||
background-color: #4f84ff !important;
|
||||
color: #ffffff !important;
|
||||
|
||||
// 确保图标在 hover 状态下也有正确的颜色
|
||||
.icons {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
|
||||
// active 状态:使用 #4f84ff 背景色,白色文字
|
||||
&.is-active {
|
||||
background-color: #4f84ff !important;
|
||||
color: #ffffff !important;
|
||||
font-weight: 600;
|
||||
|
||||
// 添加左侧边框作为指示器(使用更亮的颜色)
|
||||
border-left: 3px solid #ffffff;
|
||||
margin-left: -3px; // 使用负边距补偿,保持文字位置不变
|
||||
|
||||
// 确保图标在 active 状态下也有正确的颜色
|
||||
.icons {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 子菜单项也需要相同的效果
|
||||
.el-sub-menu {
|
||||
.el-menu-item {
|
||||
&:hover:not(.is-active) {
|
||||
background-color: #4f84ff !important;
|
||||
color: #ffffff !important;
|
||||
|
||||
.icons {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: #4f84ff !important;
|
||||
color: #ffffff !important;
|
||||
font-weight: 600;
|
||||
|
||||
// 添加左侧边框作为指示器
|
||||
border-left: 3px solid #ffffff;
|
||||
margin-left: -3px; // 使用负边距补偿,保持文字位置不变
|
||||
|
||||
.icons {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色主题下的特殊处理
|
||||
html.dark & {
|
||||
.el-menu-item {
|
||||
&:hover:not(.is-active) {
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
|
||||
.icons {
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: rgba(255, 255, 255, 0.12) !important;
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
border-left-color: var(--el-color-primary-light-3);
|
||||
|
||||
.icons {
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu {
|
||||
.el-menu-item {
|
||||
&:hover:not(.is-active) {
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
|
||||
.icons {
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: rgba(255, 255, 255, 0.12) !important;
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
border-left-color: var(--el-color-primary-light-3);
|
||||
|
||||
.icons {
|
||||
color: var(--el-color-primary-light-3) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { useAllDataStore } from "@/stores";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
@ -138,6 +138,73 @@ const handleCommand = (command) => {
|
||||
router.push('/login');
|
||||
}
|
||||
};
|
||||
|
||||
// Element Plus 主题切换
|
||||
const THEME_STORAGE_KEY = 'element-plus-theme';
|
||||
const isDark = ref(false);
|
||||
|
||||
// 初始化主题:从 localStorage 读取或检测系统偏好
|
||||
const initTheme = () => {
|
||||
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
|
||||
if (savedTheme) {
|
||||
isDark.value = savedTheme === 'dark';
|
||||
} else {
|
||||
// 检测系统偏好
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
isDark.value = prefersDark;
|
||||
}
|
||||
applyTheme();
|
||||
};
|
||||
|
||||
// 应用主题:在 html 元素上添加/移除 dark 类
|
||||
const applyTheme = () => {
|
||||
const htmlElement = document.documentElement;
|
||||
if (isDark.value) {
|
||||
htmlElement.classList.add('dark');
|
||||
localStorage.setItem(THEME_STORAGE_KEY, 'dark');
|
||||
} else {
|
||||
htmlElement.classList.remove('dark');
|
||||
localStorage.setItem(THEME_STORAGE_KEY, 'light');
|
||||
}
|
||||
};
|
||||
|
||||
// 切换主题
|
||||
const toggleTheme = () => {
|
||||
isDark.value = !isDark.value;
|
||||
applyTheme();
|
||||
};
|
||||
|
||||
// 计算当前主题
|
||||
const currentTheme = computed(() => isDark.value ? 'dark' : 'light');
|
||||
|
||||
// 计算主题图标
|
||||
const themeIcon = computed(() => isDark.value ? Sunny : Moon);
|
||||
|
||||
// 组件挂载时初始化主题
|
||||
let mediaQuery: MediaQueryList | null = null;
|
||||
let handleChange: ((e: MediaQueryListEvent) => void) | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
initTheme();
|
||||
|
||||
// 监听系统主题变化
|
||||
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
handleChange = (e: MediaQueryListEvent) => {
|
||||
// 如果用户没有手动设置主题,则跟随系统
|
||||
if (!localStorage.getItem(THEME_STORAGE_KEY)) {
|
||||
isDark.value = e.matches;
|
||||
applyTheme();
|
||||
}
|
||||
};
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (mediaQuery && handleChange) {
|
||||
mediaQuery.removeEventListener('change', handleChange);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@ -148,9 +215,17 @@ const handleCommand = (command) => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 40px;
|
||||
background-color: var(--header-bg-color, #0074e9);
|
||||
color: var(--header-text-color, #fff);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
// 使用 Element Plus 的背景色变量,暗黑模式下自动适配
|
||||
background-color: var(--el-bg-color);
|
||||
color: var(--el-text-color-primary);
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||||
|
||||
// 亮色主题下使用 #062da3 背景
|
||||
html:not(.dark) & {
|
||||
background-color: #062da3;
|
||||
border-bottom-color: rgba(79, 132, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.icons {
|
||||
@ -164,13 +239,15 @@ const handleCommand = (command) => {
|
||||
|
||||
.el-button {
|
||||
margin-right: 20px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
color: var(--header-text-color, #fff);
|
||||
// 使用 Element Plus 的填充色变量
|
||||
background-color: var(--el-fill-color-light);
|
||||
border-color: var(--el-border-color);
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
background-color: var(--el-fill-color);
|
||||
border-color: var(--el-border-color-dark);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -186,8 +263,13 @@ const handleCommand = (command) => {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
// 使用 Element Plus 的边框颜色变量
|
||||
border: 2px solid var(--el-border-color);
|
||||
transition: border-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
@ -234,16 +316,17 @@ const handleCommand = (command) => {
|
||||
}
|
||||
|
||||
:deep(.bread) {
|
||||
// 面包屑使用白色
|
||||
.el-breadcrumb__inner {
|
||||
color: var(--header-text-color, #fff) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.el-breadcrumb__inner.is-link {
|
||||
color: var(--header-text-color, #fff) !important;
|
||||
color: #ffffff !important;
|
||||
cursor: pointer !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
color: rgba(255, 255, 255, 0.8) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from '@/App.vue'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
// 导入 Element Plus 样式(必须)
|
||||
import 'element-plus/dist/index.css'
|
||||
// 导入 Element Plus 暗黑模式样式
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import '@/assets/less/index.less'
|
||||
import '@/assets/css/all.min.css'
|
||||
import router from './router'
|
||||
@ -32,9 +36,7 @@ authStore.checkAuth()
|
||||
|
||||
// 如果用户已登录,在应用启动时加载动态路由
|
||||
if (authStore.isLoggedIn) {
|
||||
loadAndAddDynamicRoutes().then(() => {
|
||||
// console.log('应用启动时已加载动态路由');
|
||||
}).catch(err => {
|
||||
loadAndAddDynamicRoutes().catch(err => {
|
||||
console.error('应用启动时加载动态路由失败:', err);
|
||||
});
|
||||
}
|
||||
|
||||
@ -47,67 +47,91 @@ const router = createRouter({
|
||||
|
||||
// 动态路由加载状态
|
||||
let dynamicRoutesAdded = false;
|
||||
// 路由加载 Promise,用于确保路由加载完成
|
||||
let routesLoadingPromise = null;
|
||||
|
||||
// 从 API 加载并添加动态路由
|
||||
export async function loadAndAddDynamicRoutes() {
|
||||
// 如果已经有加载中的 Promise,直接返回它
|
||||
if (routesLoadingPromise) {
|
||||
return routesLoadingPromise;
|
||||
}
|
||||
|
||||
// 如果已经加载过,直接返回
|
||||
if (dynamicRoutesAdded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// 创建加载 Promise
|
||||
routesLoadingPromise = (async () => {
|
||||
try {
|
||||
console.log('[路由加载] 开始从 API 加载动态路由...');
|
||||
|
||||
// 直接从 API 获取菜单数据
|
||||
const { getAllMenus } = await import("@/api/menu");
|
||||
const res = await getAllMenus();
|
||||
|
||||
if (res && res.success && res.data) {
|
||||
// 保存菜单到 localStorage
|
||||
localStorage.setItem('menus', JSON.stringify(res.data));
|
||||
console.log('[路由加载] API 返回菜单数量:', res.data.length);
|
||||
// 添加动态路由
|
||||
addDynamicRoutes(res.data);
|
||||
console.log('[路由加载] 动态路由加载完成');
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
// 如果 API 失败,尝试从 localStorage 加载
|
||||
const cachedMenus = JSON.parse(localStorage.getItem('menus') || '[]');
|
||||
if (cachedMenus.length) {
|
||||
addDynamicRoutes(cachedMenus);
|
||||
return Promise.resolve();
|
||||
}
|
||||
console.warn('[路由加载] API 返回数据格式异常:', res);
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
return Promise.resolve();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载动态路由失败:', error);
|
||||
// 如果 API 失败,尝试从 localStorage 加载
|
||||
const cachedMenus = JSON.parse(localStorage.getItem('menus') || '[]');
|
||||
if (cachedMenus.length) {
|
||||
addDynamicRoutes(cachedMenus);
|
||||
return Promise.resolve();
|
||||
}
|
||||
console.error('[路由加载] 加载动态路由失败:', error);
|
||||
// 即使出错也标记为已加载,避免无限重试
|
||||
dynamicRoutesAdded = true;
|
||||
routesLoadingPromise = null;
|
||||
return Promise.resolve();
|
||||
}
|
||||
})();
|
||||
|
||||
return routesLoadingPromise;
|
||||
}
|
||||
|
||||
// 添加动态路由到 Main 的 children 中
|
||||
function addDynamicRoutes(menus) {
|
||||
if (dynamicRoutesAdded || !menus?.length) {
|
||||
if (!menus?.length) {
|
||||
console.warn('[路由加载] 菜单数据为空,跳过添加动态路由');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[路由加载] 开始添加动态路由,菜单数量:', menus.length);
|
||||
|
||||
// 如果已经添加过,先移除旧路由(刷新时可能需要重新添加)
|
||||
if (dynamicRoutesAdded) {
|
||||
console.log('[路由加载] 检测到已添加的路由,先移除旧路由');
|
||||
// 移除 Main 路由以便重新添加
|
||||
if (router.hasRoute('Main')) {
|
||||
router.removeRoute('Main');
|
||||
}
|
||||
dynamicRoutesAdded = false;
|
||||
}
|
||||
|
||||
// 过滤掉 ID 为 1 的仪表盘菜单,因为它是静态路由
|
||||
const filteredMenus = menus.filter(menu => menu.id !== 1);
|
||||
|
||||
const dynamicRoutes = convertMenusToRoutes(filteredMenus);
|
||||
|
||||
// 打印路由树结构(只打印路径信息,不序列化组件)
|
||||
// console.log('生成的路由树:', dynamicRoutes.map(r => ({
|
||||
// path: r.path,
|
||||
// name: r.name,
|
||||
// hasComponent: !!r.component,
|
||||
// childrenCount: r.children?.length || 0
|
||||
// })));
|
||||
console.log('[路由加载] 转换后的动态路由数量:', dynamicRoutes.length);
|
||||
console.log('[路由加载] 动态路由列表:', dynamicRoutes.map(r => ({
|
||||
path: r.path,
|
||||
name: r.name,
|
||||
hasComponent: !!r.component,
|
||||
childrenCount: r.children?.length || 0
|
||||
})));
|
||||
|
||||
// 获取主路由
|
||||
const mainRoute = router.getRoutes().find(r => r.name === 'Main');
|
||||
if (!mainRoute) {
|
||||
console.error('找不到 Main 路由');
|
||||
console.error('[路由加载] 找不到 Main 路由');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -116,10 +140,12 @@ function addDynamicRoutes(menus) {
|
||||
const dashboardRoute = staticChildren.find(r => r.name === 'Dashboard');
|
||||
|
||||
// 移除旧的主路由
|
||||
if (router.hasRoute('Main')) {
|
||||
router.removeRoute('Main');
|
||||
}
|
||||
|
||||
// 重新添加主路由,仪表盘路由放在第一个,后面跟动态路由
|
||||
router.addRoute({
|
||||
const newMainRoute = {
|
||||
...mainRoute,
|
||||
redirect: "/dashboard", // 始终重定向到仪表盘
|
||||
children: [
|
||||
@ -128,7 +154,10 @@ function addDynamicRoutes(menus) {
|
||||
// 动态路由跟在后面
|
||||
...dynamicRoutes
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
router.addRoute(newMainRoute);
|
||||
console.log('[路由加载] Main 路由已更新,子路由总数:', newMainRoute.children.length);
|
||||
|
||||
// 添加知识库的子路由(详情页和编辑页)
|
||||
// 直接在 Main 路由下添加完整路径的子路由
|
||||
@ -152,30 +181,6 @@ function addDynamicRoutes(menus) {
|
||||
});
|
||||
|
||||
dynamicRoutesAdded = true;
|
||||
|
||||
// 打印路由信息用于调试
|
||||
const finalMainRoute = router.getRoutes().find(r => r.name === 'Main');
|
||||
// console.log('动态路由已添加:', {
|
||||
// redirect: "/dashboard",
|
||||
// staticRoutesCount: dashboardRoute ? 1 : 0,
|
||||
// dynamicRoutesCount: dynamicRoutes.length,
|
||||
// totalChildrenCount: (dashboardRoute ? 1 : 0) + dynamicRoutes.length,
|
||||
// childrenPaths: finalMainRoute?.children?.map(c => ({
|
||||
// path: c.path,
|
||||
// name: c.name,
|
||||
// isStatic: c.meta?.isStatic || false,
|
||||
// metaPath: c.meta?.menuPath
|
||||
// }))
|
||||
// });
|
||||
|
||||
// 测试路由解析(使用完整路径)
|
||||
const testResolve = router.resolve('/dashboard');
|
||||
// console.log('测试路由解析 /dashboard:', {
|
||||
// matched: testResolve.matched.map(m => ({ name: m.name, path: m.path })),
|
||||
// fullPath: testResolve.fullPath,
|
||||
// name: testResolve.name,
|
||||
// hasMatched: testResolve.matched.length > 0
|
||||
// });
|
||||
}
|
||||
|
||||
// 查找第一个有效的路由(有组件的路由)
|
||||
@ -222,15 +227,82 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
// 3. 已登录,确保动态路由已加载(必须在检查 404 之前)
|
||||
if (!dynamicRoutesAdded) {
|
||||
console.log('[路由守卫] 开始加载动态路由, 目标路径:', to.path);
|
||||
await loadAndAddDynamicRoutes();
|
||||
// 路由加载后,使用原始路径重新导航(确保路由表已更新)
|
||||
// 如果 to 已经是 404,使用原始路径而不是 404 路由对象
|
||||
const targetPath = to.matched.length === 0 || to.name === "NotFound"
|
||||
? to.path // 如果匹配失败或者是 404,使用原始路径
|
||||
: to.fullPath;
|
||||
next({ path: targetPath, replace: true });
|
||||
console.log('[路由守卫] 动态路由加载完成, dynamicRoutesAdded:', dynamicRoutesAdded);
|
||||
|
||||
// 如果路由加载后仍然未添加(API 失败)
|
||||
if (!dynamicRoutesAdded) {
|
||||
console.warn('[路由守卫] 路由加载失败,可能是 API 错误或 token 无效');
|
||||
// 重新检查 token,如果 token 不存在或无效,跳转到登录页
|
||||
const currentToken = localStorage.getItem('token');
|
||||
if (!currentToken) {
|
||||
console.log('[路由守卫] 未找到 token,跳转到登录页');
|
||||
next({ path: "/login", query: { redirect: to.path } });
|
||||
return;
|
||||
}
|
||||
// 如果 token 存在但路由加载失败,可能是 token 过期或无效,清除 token 并跳转登录
|
||||
console.warn('[路由守卫] Token 存在但路由加载失败,清除 token 并跳转登录页');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('userInfo');
|
||||
next({ path: "/login", query: { redirect: to.path } });
|
||||
return;
|
||||
}
|
||||
|
||||
// 路由加载完成后,等待一个tick确保Vue Router内部路由表已更新
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
// 重新解析路径检查是否能匹配
|
||||
const resolved = router.resolve(to.path);
|
||||
console.log('[路由守卫] 路由解析结果:', {
|
||||
path: to.path,
|
||||
resolved: resolved.path,
|
||||
matched: resolved.matched.length,
|
||||
name: resolved.name,
|
||||
matchedRoutes: resolved.matched.map(m => ({ name: m.name, path: m.path }))
|
||||
});
|
||||
|
||||
// 如果能匹配到路由,使用完整路径重新导航(确保Vue Router正确更新)
|
||||
if (resolved.matched.length > 0 && resolved.name !== "NotFound") {
|
||||
console.log('[路由守卫] 路由匹配成功,重新导航确保正确加载');
|
||||
next({ path: to.fullPath || to.path, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果无法匹配,使用原始路径重新导航
|
||||
console.log('[路由守卫] 路由未匹配,重新导航到:', to.path);
|
||||
next({ path: to.fullPath || to.path, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 3.5 如果路由已加载但当前路径未匹配,可能是刷新问题
|
||||
// 注意:这里需要排除根路径和已知的静态路由
|
||||
if (to.matched.length === 0 && to.name !== "NotFound" && to.path !== "/" && to.path !== "/dashboard") {
|
||||
console.log('[路由守卫] 路由已加载但未匹配,尝试重新解析:', to.path);
|
||||
// 重新解析路径,看看是否能够匹配
|
||||
const resolved = router.resolve(to.path);
|
||||
console.log('[路由守卫] 重新解析结果:', {
|
||||
matched: resolved.matched.length,
|
||||
name: resolved.name,
|
||||
matchedRoutes: resolved.matched.map(m => ({ name: m.name, path: m.path }))
|
||||
});
|
||||
|
||||
if (resolved.matched.length > 0 && resolved.name !== "NotFound") {
|
||||
// 能够匹配,说明路由表正常,使用路径重新导航
|
||||
console.log('[路由守卫] 重新解析匹配成功,重新导航');
|
||||
next({ path: to.path, replace: true });
|
||||
return;
|
||||
} else {
|
||||
console.warn('[路由守卫] 路由确实无法匹配,可能是路径错误:', to.path);
|
||||
// 打印所有已注册的路由用于调试
|
||||
const allRoutes = router.getRoutes();
|
||||
console.log('[路由守卫] 所有已注册的路由:', allRoutes.map(r => ({
|
||||
name: r.name,
|
||||
path: r.path,
|
||||
children: r.children?.map(c => ({ name: c.name, path: c.path }))
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 已登录且路由已加载,检查是否是 404
|
||||
// 注意:这里要在路由加载完成后才能正确判断是否是 404
|
||||
|
||||
@ -39,10 +39,42 @@ import { ref as vueRef } from 'vue';
|
||||
export const useTabsStore = defineTabsStore('tabs', () => {
|
||||
// 固定首页tab
|
||||
const defaultDashboardPath = '/dashboard';
|
||||
const tabList = vueRef([
|
||||
{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' },
|
||||
]);
|
||||
const activeTab = vueRef(defaultDashboardPath);
|
||||
|
||||
// 从 localStorage 恢复 tabs 状态
|
||||
function loadTabsFromStorage() {
|
||||
try {
|
||||
const savedTabs = localStorage.getItem('tabs_list');
|
||||
const savedActiveTab = localStorage.getItem('active_tab');
|
||||
if (savedTabs) {
|
||||
const tabs = JSON.parse(savedTabs);
|
||||
// 确保至少包含首页
|
||||
const hasDashboard = tabs.some(t => t.fullPath === defaultDashboardPath);
|
||||
if (!hasDashboard) {
|
||||
tabs.unshift({ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' });
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('恢复 tabs 失败:', e);
|
||||
}
|
||||
return [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }];
|
||||
}
|
||||
|
||||
// 保存 tabs 到 localStorage
|
||||
function saveTabsToStorage(tabs, active) {
|
||||
try {
|
||||
localStorage.setItem('tabs_list', JSON.stringify(tabs));
|
||||
if (active) {
|
||||
localStorage.setItem('active_tab', active);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('保存 tabs 失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
const tabList = vueRef(loadTabsFromStorage());
|
||||
const savedActiveTab = localStorage.getItem('active_tab');
|
||||
const activeTab = vueRef(savedActiveTab || defaultDashboardPath);
|
||||
|
||||
// 添加tab,若已存在则激活
|
||||
function addTab(tab) {
|
||||
@ -51,6 +83,7 @@ export const useTabsStore = defineTabsStore('tabs', () => {
|
||||
tabList.value.push(tab);
|
||||
}
|
||||
activeTab.value = tab.fullPath;
|
||||
saveTabsToStorage(tabList.value, activeTab.value);
|
||||
}
|
||||
|
||||
// 删除指定tab并切换激活tab
|
||||
@ -69,6 +102,7 @@ export const useTabsStore = defineTabsStore('tabs', () => {
|
||||
activeTab.value = defaultDashboardPath;
|
||||
}
|
||||
}
|
||||
saveTabsToStorage(tabList.value, activeTab.value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,12 +111,20 @@ export const useTabsStore = defineTabsStore('tabs', () => {
|
||||
tabList.value = tabList.value.filter(
|
||||
(t) => t.fullPath === defaultDashboardPath || t.fullPath === activeTab.value
|
||||
);
|
||||
saveTabsToStorage(tabList.value, activeTab.value);
|
||||
}
|
||||
|
||||
// 关闭全部,只留首页
|
||||
function closeAll() {
|
||||
tabList.value = tabList.value.filter((t) => t.fullPath === defaultDashboardPath);
|
||||
activeTab.value = defaultDashboardPath;
|
||||
saveTabsToStorage(tabList.value, activeTab.value);
|
||||
}
|
||||
|
||||
// 设置激活tab(不触发路由跳转,仅用于更新状态)
|
||||
function setActiveTab(fullPath) {
|
||||
activeTab.value = fullPath;
|
||||
saveTabsToStorage(tabList.value, activeTab.value);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -92,5 +134,7 @@ export const useTabsStore = defineTabsStore('tabs', () => {
|
||||
removeTab,
|
||||
closeOthers,
|
||||
closeAll,
|
||||
setActiveTab,
|
||||
saveTabsToStorage,
|
||||
};
|
||||
});
|
||||
@ -3,7 +3,7 @@ import CommonAside from '@/components/CommonAside.vue';
|
||||
import CommonHeader from '@/components/CommonHeader.vue';
|
||||
import { useTabsStore } from '@/stores';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { ref, watch, reactive, nextTick } from 'vue';
|
||||
import { ref, watch, reactive, nextTick, onMounted } from 'vue';
|
||||
import { More, DArrowRight } from '@element-plus/icons-vue'
|
||||
|
||||
const tabsStore = useTabsStore();
|
||||
@ -11,7 +11,53 @@ const router = useRouter();
|
||||
const route = useRoute();
|
||||
const defaultDashboardPath = '/dashboard';
|
||||
|
||||
// 根据当前路由恢复 tab(刷新时使用)
|
||||
function restoreTabFromRoute() {
|
||||
const currentPath = route.fullPath;
|
||||
const currentName = route.name;
|
||||
const currentMeta = route.meta || {};
|
||||
|
||||
// 如果当前路径不在 tabList 中,添加它
|
||||
const existTab = tabsStore.tabList.find(t => t.fullPath === currentPath);
|
||||
if (!existTab) {
|
||||
// 从路由 meta 获取标题,如果没有则使用路由 name 或路径
|
||||
const title = currentMeta.title || currentName || '页面';
|
||||
// 使用 addTab 会自动保存到 localStorage
|
||||
tabsStore.addTab({
|
||||
title: title,
|
||||
fullPath: currentPath,
|
||||
name: currentName || title,
|
||||
icon: currentMeta.icon
|
||||
});
|
||||
} else {
|
||||
// 如果已存在,只激活它(不触发路由跳转)
|
||||
tabsStore.setActiveTab(currentPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载后,根据当前路由恢复 tab
|
||||
onMounted(() => {
|
||||
// 等待路由完全加载后再恢复
|
||||
nextTick(() => {
|
||||
// 刷新时,localStorage 中已保存的 tabs 会在 store 初始化时恢复
|
||||
// 这里只需要确保当前路由对应的 tab 存在并激活
|
||||
restoreTabFromRoute();
|
||||
});
|
||||
});
|
||||
|
||||
// 监听路由变化,自动添加/激活对应的 tab
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
(newPath) => {
|
||||
if (newPath) {
|
||||
restoreTabFromRoute();
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
// 1. 侧栏菜单点击:加入/激活Tab并切换路由
|
||||
// 注意:路由跳转由 watch(tabsStore.activeTab) 统一处理,避免重复调用
|
||||
const handleAsideMenuClick = (menuItem) => {
|
||||
tabsStore.addTab({
|
||||
title: menuItem.label,
|
||||
@ -19,9 +65,8 @@ const handleAsideMenuClick = (menuItem) => {
|
||||
name: menuItem.label,
|
||||
icon: menuItem.icon
|
||||
});
|
||||
if (route.fullPath !== menuItem.path) {
|
||||
router.push(menuItem.path);
|
||||
}
|
||||
// 移除这里的 router.push,由下面的 watch 统一处理路由跳转
|
||||
// 这样可以避免重复跳转,确保 keep-alive 正常工作
|
||||
};
|
||||
const tabsCloseTab = (targetKey) => {
|
||||
tabsStore.removeTab(targetKey);
|
||||
@ -40,13 +85,24 @@ const closeAll = () => {
|
||||
router.push(defaultDashboardPath);
|
||||
};
|
||||
// 主动监听tab激活,保证切tab时内容区切换
|
||||
// 使用 immediate: false 避免初始化时触发,使用 flush: 'post' 确保在 DOM 更新后执行
|
||||
watch(
|
||||
() => tabsStore.activeTab,
|
||||
(newVal) => {
|
||||
if (newVal && router.currentRoute.value.fullPath !== newVal) {
|
||||
router.push(newVal);
|
||||
(newVal, oldVal) => {
|
||||
// 如果新值和当前路由路径不同,且不是初始化(oldVal 不为 undefined),才进行跳转
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{ flush: 'post' }
|
||||
);
|
||||
|
||||
// ========== 右键菜单逻辑 ========== //
|
||||
@ -173,6 +229,7 @@ function closeAllTabs() {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
// 注意:overflow-y: auto 不会影响 fixed 定位的元素(fixed 相对于 viewport)
|
||||
.multi-tabs-wrapper {
|
||||
position: relative;
|
||||
zoom: 1;
|
||||
|
||||
@ -311,9 +311,6 @@ const handleSubmit = () => {
|
||||
share: formData.share, // Added share in params
|
||||
};
|
||||
|
||||
// 调试:打印提交的数据
|
||||
// console.log("提交的数据:", JSON.stringify(submitData, null, 2));
|
||||
|
||||
if (isEdit.value && currentId !== "new") {
|
||||
// 编辑:需要添加 id(后端 JSON tag 是 "id")
|
||||
const knowledgeId = parseInt(currentId as string);
|
||||
|
||||
@ -64,7 +64,6 @@ const handleUploadImage = async (file: File, insertFn: (url: string, alt?: strin
|
||||
fullUrl = `${base}${url}`;
|
||||
}
|
||||
|
||||
console.log('图片上传成功,URL:', fullUrl);
|
||||
insertFn(fullUrl, file.name, fullUrl);
|
||||
ElMessage.success('图片上传成功');
|
||||
} else {
|
||||
@ -97,7 +96,6 @@ const handleUploadVideo = async (file: File, insertFn: (url: string, poster?: st
|
||||
fullUrl = `${base}${url}`;
|
||||
}
|
||||
|
||||
console.log('视频上传成功,URL:', fullUrl);
|
||||
insertFn(fullUrl, '');
|
||||
ElMessage.success('视频上传成功');
|
||||
} else {
|
||||
@ -130,7 +128,6 @@ const handleUploadAttachment = async (file: File, insertFn: (url: string, text?:
|
||||
fullUrl = `${base}${url}`;
|
||||
}
|
||||
|
||||
console.log('附件上传成功,URL:', fullUrl);
|
||||
insertFn(fullUrl, file.name);
|
||||
ElMessage.success('附件上传成功');
|
||||
} else {
|
||||
|
||||
@ -494,16 +494,13 @@ const copyFileUrl = (file: any) => {};
|
||||
|
||||
const downloadFile = (file: any) => {
|
||||
// 这里实现下载逻辑
|
||||
console.log("下载文件:", file.name || file.file_name);
|
||||
};
|
||||
|
||||
const deleteFile = (file: any) => {
|
||||
// 这里实现删除逻辑
|
||||
console.log("删除文件:", file.name || file.file_name);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log("文件管理模块已加载");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -87,15 +87,15 @@ const permissions = ref<Permission[]>([
|
||||
]);
|
||||
|
||||
const handleAddPermission = () => {
|
||||
console.log("添加权限");
|
||||
// 添加权限功能
|
||||
};
|
||||
|
||||
const handleEdit = (permission: Permission) => {
|
||||
console.log("编辑权限:", permission);
|
||||
// 编辑权限功能
|
||||
};
|
||||
|
||||
const handleDelete = (permission: Permission) => {
|
||||
console.log("删除权限:", permission);
|
||||
// 删除权限功能
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
<div class="header-bar">
|
||||
<h2>用户管理</h2>
|
||||
<div class="header-actions">
|
||||
<el-button type="warning" @click="testElMessage">测试ElMessage</el-button>
|
||||
<el-button type="primary" @click="handleAddUser">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加用户
|
||||
@ -433,34 +432,6 @@ const handleDelete = async (user: User) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 测试ElMessage功能
|
||||
const testElMessage = () => {
|
||||
console.log("测试 ElMessage");
|
||||
console.log("ElMessage 类型:", typeof ElMessage);
|
||||
console.log("ElMessage 对象:", ElMessage);
|
||||
console.log("ElMessage.success:", ElMessage.success);
|
||||
console.log("ElMessage.error:", ElMessage.error);
|
||||
|
||||
try {
|
||||
ElMessage("这是普通消息");
|
||||
setTimeout(() => {
|
||||
ElMessage.success("这是成功消息");
|
||||
}, 500);
|
||||
setTimeout(() => {
|
||||
ElMessage.error("这是错误消息");
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
ElMessage.warning("这是警告消息");
|
||||
}, 1500);
|
||||
setTimeout(() => {
|
||||
ElMessage.info("这是信息消息");
|
||||
}, 2000);
|
||||
} catch (e) {
|
||||
console.error("ElMessage 调用失败:", e);
|
||||
alert("ElMessage 调用失败: " + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
//修改密码
|
||||
const handleChangePassword = async (user: User) => {
|
||||
dialogTitle.value = "修改密码";
|
||||
|
||||
@ -47,7 +47,6 @@ const handleDeleteUser = async () => {
|
||||
try {
|
||||
await deleteUserApi(userId);
|
||||
userInfo.value = null; // 清空用户信息
|
||||
console.log("用户删除成功");
|
||||
} catch (error) {
|
||||
console.error("删除用户失败", error);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user