完善后台布局
This commit is contained in:
@@ -17,6 +17,11 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
// 定义组件名称(多词命名)
|
||||
defineOptions({
|
||||
name: 'LayoutBreadcrumb'
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const breadcrumbList = ref([])
|
||||
|
||||
@@ -42,7 +42,7 @@ const router = useRouter()
|
||||
|
||||
// 获取图标组件
|
||||
const getIconComponent = (iconName) => {
|
||||
return icons[iconName] || icons.MenuOutlined
|
||||
return icons[iconName] || icons.FileTextOutlined
|
||||
}
|
||||
|
||||
// 处理菜单点击
|
||||
|
||||
@@ -4,11 +4,16 @@
|
||||
<div class="setting-item">
|
||||
<div class="setting-title">布局模式</div>
|
||||
<div class="layout-mode-list">
|
||||
<div v-for="mode in layoutModes" :key="mode.value" class="layout-mode-item"
|
||||
<div
|
||||
v-for="mode in layoutModes"
|
||||
:key="mode.value"
|
||||
class="layout-mode-item"
|
||||
:class="{ active: layoutStore.layoutMode === mode.value }"
|
||||
@click="handleLayoutChange(mode.value)">
|
||||
@click="handleLayoutChange(mode.value)"
|
||||
>
|
||||
<div class="layout-preview" :class="`preview-${mode.value}`">
|
||||
<div class="preview-sidebar"></div>
|
||||
<div v-if="mode.value === 'default'" class="preview-sidebar-2"></div>
|
||||
<div class="preview-content">
|
||||
<div class="preview-header"></div>
|
||||
<div class="preview-body"></div>
|
||||
@@ -23,9 +28,14 @@
|
||||
<div class="setting-item">
|
||||
<div class="setting-title">主题颜色</div>
|
||||
<div class="color-list">
|
||||
<div v-for="color in themeColors" :key="color" class="color-item"
|
||||
:class="{ active: themeColor === color }" :style="{ backgroundColor: color }"
|
||||
@click="changeThemeColor(color)">
|
||||
<div
|
||||
v-for="color in themeColors"
|
||||
:key="color"
|
||||
class="color-item"
|
||||
:class="{ active: themeColor === color }"
|
||||
:style="{ backgroundColor: color }"
|
||||
@click="changeThemeColor(color)"
|
||||
>
|
||||
<CheckOutlined v-if="themeColor === color" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,6 +48,20 @@
|
||||
<span>显示标签栏</span>
|
||||
<a-switch v-model:checked="showTags" @change="handleShowTagsChange" />
|
||||
</div>
|
||||
<div class="toggle-item">
|
||||
<span>显示面包屑</span>
|
||||
<a-switch v-model:checked="showBreadcrumb" @change="handleShowBreadcrumbChange" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-title">其他设置</div>
|
||||
<div class="action-buttons">
|
||||
<a-button type="primary" block @click="handleResetSettings">
|
||||
<ReloadOutlined />
|
||||
重置设置
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,16 +69,22 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose } from 'vue'
|
||||
import { ref, defineExpose, defineOptions, watch, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useLayoutStore } from '@/stores/modules/layout'
|
||||
import { CheckOutlined } from '@ant-design/icons-vue'
|
||||
import { CheckOutlined, ReloadOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// 定义组件名称(多词命名)
|
||||
defineOptions({
|
||||
name: 'LayoutSetting'
|
||||
})
|
||||
|
||||
const layoutStore = useLayoutStore()
|
||||
|
||||
const open = ref(false)
|
||||
const themeColor = ref('#1890ff')
|
||||
const showTags = ref(true)
|
||||
const showBreadcrumb = ref(true)
|
||||
|
||||
const layoutModes = [
|
||||
{ value: 'default', label: '默认布局' },
|
||||
@@ -96,15 +126,60 @@ const handleLayoutChange = (mode) => {
|
||||
// 切换主题颜色
|
||||
const changeThemeColor = (color) => {
|
||||
themeColor.value = color
|
||||
// 这里可以实现主题切换逻辑
|
||||
// 更新 CSS 变量
|
||||
document.documentElement.style.setProperty('--primary-color', color)
|
||||
message.success('主题颜色已更新')
|
||||
}
|
||||
|
||||
// 切换标签栏显示
|
||||
const handleShowTagsChange = (checked) => {
|
||||
// 这里可以实现标签栏显示/隐藏逻辑
|
||||
console.log('showTags:', checked)
|
||||
showTags.value = checked
|
||||
// 触发自定义事件或更新状态
|
||||
document.documentElement.style.setProperty('--show-tags', checked ? 'block' : 'none')
|
||||
message.success(checked ? '标签栏已显示' : '标签栏已隐藏')
|
||||
}
|
||||
|
||||
// 切换面包屑显示
|
||||
const handleShowBreadcrumbChange = (checked) => {
|
||||
showBreadcrumb.value = checked
|
||||
message.success(checked ? '面包屑已显示' : '面包屑已隐藏')
|
||||
}
|
||||
|
||||
// 重置设置
|
||||
const handleResetSettings = () => {
|
||||
themeColor.value = '#1890ff'
|
||||
showTags.value = true
|
||||
showBreadcrumb.value = true
|
||||
layoutStore.setLayoutMode('default')
|
||||
document.documentElement.style.setProperty('--primary-color', '#1890ff')
|
||||
document.documentElement.style.setProperty('--show-tags', 'block')
|
||||
message.success('设置已重置')
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
// 从本地存储或其他地方恢复设置
|
||||
const savedThemeColor = localStorage.getItem('themeColor')
|
||||
if (savedThemeColor) {
|
||||
themeColor.value = savedThemeColor
|
||||
document.documentElement.style.setProperty('--primary-color', savedThemeColor)
|
||||
}
|
||||
|
||||
const savedShowTags = localStorage.getItem('showTags')
|
||||
if (savedShowTags !== null) {
|
||||
showTags.value = savedShowTags === 'true'
|
||||
document.documentElement.style.setProperty('--show-tags', savedShowTags === 'true' ? 'block' : 'none')
|
||||
}
|
||||
})
|
||||
|
||||
// 监听设置变化并保存到本地存储
|
||||
watch(themeColor, (newVal) => {
|
||||
localStorage.setItem('themeColor', newVal)
|
||||
})
|
||||
|
||||
watch(showTags, (newVal) => {
|
||||
localStorage.setItem('showTags', String(newVal))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -133,11 +208,14 @@ const handleShowTagsChange = (checked) => {
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
border-color: var(--primary-color, #1890ff);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #1890ff;
|
||||
border-color: var(--primary-color, #1890ff);
|
||||
background-color: rgba(24, 144, 255, 0.05);
|
||||
}
|
||||
|
||||
.layout-preview {
|
||||
@@ -149,14 +227,11 @@ const handleShowTagsChange = (checked) => {
|
||||
display: flex;
|
||||
background-color: #f0f2f5;
|
||||
|
||||
&.preview-default {
|
||||
.preview-sidebar {
|
||||
width: 20px;
|
||||
background-color: #001529;
|
||||
}
|
||||
|
||||
.preview-sidebar-2 {
|
||||
width: 24px;
|
||||
background-color: #fff;
|
||||
border-left: 1px solid #e8e8e8;
|
||||
}
|
||||
@@ -176,6 +251,15 @@ const handleShowTagsChange = (checked) => {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
}
|
||||
|
||||
&.preview-default {
|
||||
.preview-sidebar {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.preview-sidebar-2 {
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&.preview-menu {
|
||||
@@ -184,22 +268,6 @@ const handleShowTagsChange = (checked) => {
|
||||
background-color: #fff;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
flex: 1;
|
||||
padding: 4px;
|
||||
|
||||
.preview-header {
|
||||
height: 8px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.preview-body {
|
||||
height: calc(100% - 12px);
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.preview-top {
|
||||
@@ -212,16 +280,12 @@ const handleShowTagsChange = (checked) => {
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
flex: 1;
|
||||
padding: 4px;
|
||||
|
||||
.preview-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.preview-body {
|
||||
height: 100%;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,7 +301,7 @@ const handleShowTagsChange = (checked) => {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
color: #1890ff;
|
||||
color: var(--primary-color, #1890ff);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
@@ -256,13 +320,17 @@ const handleShowTagsChange = (checked) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 0 2px var(--primary-color, #1890ff);
|
||||
|
||||
.anticon {
|
||||
color: #fff;
|
||||
}
|
||||
@@ -288,6 +356,13 @@ const handleShowTagsChange = (checked) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
:deep(.ant-btn) {
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,9 +41,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useLayoutStore } from '@/stores/modules/layout'
|
||||
import { getUserMenu } from '@/api/menu'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -59,7 +58,6 @@ const props = defineProps({
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const layoutStore = useLayoutStore()
|
||||
|
||||
const menuList = ref([])
|
||||
const selectedKeys = ref([])
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div class="tags-view" v-show="showTags">
|
||||
<div v-show="showTags" class="tags-view">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<div class="tags-wrapper">
|
||||
<a-space :size="4">
|
||||
<a-tag v-for="tag in visitedViews" :key="tag.fullPath" :closable="!tag.meta?.affix"
|
||||
@close="closeSelectedTag(tag)" @click="clickTag(tag)"
|
||||
@contextmenu.prevent="handleContextMenu($event, tag)" class="tag-item"
|
||||
:class="{ active: isActive(tag) }">
|
||||
<a-tag v-for="tag in visitedViews" :key="tag.fullPath" :closable="!tag.meta?.affix" class="tag-item"
|
||||
:class="{ active: isActive(tag) }" @click="clickTag(tag)" @close="closeSelectedTag(tag)"
|
||||
@contextmenu.prevent="handleContextMenu($event, tag)">
|
||||
{{ tag.meta?.title || tag.name }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
@@ -17,7 +16,7 @@
|
||||
<ReloadOutlined />
|
||||
<span>刷新</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="close" v-if="!selectedTag.meta?.affix">
|
||||
<a-menu-item v-if="!selectedTag.meta?.affix" key="close">
|
||||
<CloseOutlined />
|
||||
<span>关闭</span>
|
||||
</a-menu-item>
|
||||
@@ -35,17 +34,17 @@
|
||||
|
||||
<div class="tags-actions">
|
||||
<a-tooltip title="刷新当前页">
|
||||
<a-button type="text" size="small" @click="refreshSelectedTag">
|
||||
<a-button size="small" type="text" @click="refreshSelectedTag">
|
||||
<ReloadOutlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="关闭其他">
|
||||
<a-button type="text" size="small" @click="closeOthersTags">
|
||||
<a-button size="small" type="text" @click="closeOthersTags">
|
||||
<ColumnWidthOutlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="关闭所有">
|
||||
<a-button type="text" size="small" @click="closeAllTags">
|
||||
<a-button size="small" type="text" @click="closeAllTags">
|
||||
<CloseCircleOutlined />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
@@ -54,10 +53,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'
|
||||
import { ref, computed, watch, onMounted, defineOptions } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useLayoutStore } from '@/stores/modules/layout'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
CloseOutlined,
|
||||
@@ -65,6 +63,11 @@ import {
|
||||
CloseCircleOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
// 定义组件名称(多词命名)
|
||||
defineOptions({
|
||||
name: 'TagsView'
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const layoutStore = useLayoutStore()
|
||||
@@ -91,55 +94,75 @@ const addTags = () => {
|
||||
meta: route.meta
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 移除标签
|
||||
const closeSelectedTag = (view) => {
|
||||
// 如果是固定标签,不允许关闭
|
||||
if (view.meta?.affix) {
|
||||
return
|
||||
}
|
||||
|
||||
layoutStore.removeViewTags(view.fullPath)
|
||||
|
||||
// 如果关闭的是当前激活的标签,需要跳转
|
||||
if (isActive(view)) {
|
||||
toLastView(visitedViews.value)
|
||||
const nextTag = visitedViews.value.find(tag => tag.fullPath !== view.fullPath)
|
||||
if (nextTag) {
|
||||
router.push(nextTag.fullPath)
|
||||
} else {
|
||||
// 如果没有其他标签,跳转到首页
|
||||
router.push('/home')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭其他标签
|
||||
const closeOthersTags = () => {
|
||||
router.push(selectedTag.value)
|
||||
layoutStore.viewTags = visitedViews.value.filter(tag => tag.meta?.affix || tag.fullPath === selectedTag.value.fullPath)
|
||||
if (!selectedTag.value || !selectedTag.value.fullPath) {
|
||||
return
|
||||
}
|
||||
|
||||
// 保留固定标签和当前选中的标签
|
||||
const tagsToKeep = visitedViews.value.filter(tag =>
|
||||
tag.meta?.affix || tag.fullPath === selectedTag.value.fullPath
|
||||
)
|
||||
|
||||
// 更新标签列表
|
||||
layoutStore.viewTags = tagsToKeep
|
||||
|
||||
// 如果当前不在选中的标签页,跳转到选中的标签
|
||||
if (!isActive(selectedTag.value)) {
|
||||
router.push(selectedTag.value.fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭所有标签
|
||||
const closeAllTags = () => {
|
||||
// 只保留固定标签
|
||||
const affixTags = visitedViews.value.filter(tag => tag.meta?.affix)
|
||||
layoutStore.viewTags = affixTags
|
||||
|
||||
// 如果还有固定标签,跳转到第一个固定标签
|
||||
if (affixTags.length > 0) {
|
||||
router.push(affixTags[0].fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到最后一个标签
|
||||
const toLastView = (visitedViews) => {
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
if (latestView) {
|
||||
router.push(latestView.fullPath)
|
||||
} else {
|
||||
// 如果没有标签,跳转到首页
|
||||
router.push('/')
|
||||
// 如果没有固定标签,跳转到首页
|
||||
router.push('/home')
|
||||
}
|
||||
}
|
||||
|
||||
// 点击标签
|
||||
const clickTag = (tag) => {
|
||||
selectedTag.value = tag
|
||||
if (!isActive(tag)) {
|
||||
router.push(tag.fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新当前标签
|
||||
const refreshSelectedTag = () => {
|
||||
const { fullPath } = route
|
||||
router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
// 使用 router.go(0) 刷新页面
|
||||
router.go(0)
|
||||
}
|
||||
|
||||
// 右键菜单处理
|
||||
@@ -155,7 +178,7 @@ const handleMenuClick = ({ key }) => {
|
||||
refreshSelectedTag()
|
||||
break
|
||||
case 'close':
|
||||
if (!selectedTag.value.meta?.affix) {
|
||||
if (selectedTag.value && !selectedTag.value.meta?.affix) {
|
||||
closeSelectedTag(selectedTag.value)
|
||||
}
|
||||
break
|
||||
@@ -168,16 +191,13 @@ const handleMenuClick = ({ key }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 监听路由变化
|
||||
watch(() => route.path, () => {
|
||||
// 监听路由变化,自动添加标签
|
||||
watch(() => route.fullPath, () => {
|
||||
addTags()
|
||||
// 更新当前选中的标签
|
||||
selectedTag.value = visitedViews.value.find(tag => isActive(tag)) || {}
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听路由更新
|
||||
onBeforeRouteUpdate((to) => {
|
||||
addTags()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
addTags()
|
||||
// 初始化选中的标签
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div class="userbar">
|
||||
<!-- 菜单搜索 -->
|
||||
<a-input-search v-model:value="searchKeyword" :placeholder="$t('common.searchMenu')" enter-button
|
||||
@search="handleSearch" class="search-input" allow-clear>
|
||||
<template #prefix>
|
||||
<a-tooltip :title="$t('common.search')">
|
||||
<a-button type="text" @click="showSearch" class="action-btn">
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-input-search>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- 消息通知 -->
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||
@@ -112,7 +111,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, defineOptions } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
@@ -131,13 +130,17 @@ import {
|
||||
} from '@ant-design/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// 定义组件名称(多词命名)
|
||||
defineOptions({
|
||||
name: 'UserBar'
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const i18nStore = useI18nStore()
|
||||
|
||||
const isFullscreen = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 消息数据
|
||||
const messages = ref([
|
||||
@@ -182,15 +185,9 @@ onUnmounted(() => {
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
||||
})
|
||||
|
||||
// 菜单搜索
|
||||
const handleSearch = (value) => {
|
||||
if (!value.trim()) {
|
||||
message.warning(t('common.searchEmpty'))
|
||||
return
|
||||
}
|
||||
// 这里可以实现实际的搜索逻辑,比如跳转到搜索页面或显示搜索结果
|
||||
message.info(t('common.searching') + value)
|
||||
searchKeyword.value = ''
|
||||
// 显示搜索功能
|
||||
const showSearch = () => {
|
||||
message.info('搜索功能开发中')
|
||||
}
|
||||
|
||||
// 清除消息
|
||||
@@ -244,7 +241,7 @@ const handleLogout = () => {
|
||||
await userStore.logout()
|
||||
message.success(t('common.logoutSuccess'))
|
||||
router.push('/login')
|
||||
} catch (error) {
|
||||
} catch {
|
||||
message.error(t('common.logoutFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
v-if="selectedParentMenu && selectedParentMenu.children && selectedParentMenu.children.length > 0"
|
||||
theme="light" :collapsed="sidebarCollapsed" :collapsible="true" @collapse="handleCollapse" width="200"
|
||||
:collapsed-width="64" class="right-sidebar">
|
||||
<div class="parent-title">{{ selectedParentMenu.meta?.title }}</div>
|
||||
<div class="parent-title">
|
||||
<component :is="getIconComponent(selectedParentMenu.meta?.icon)" />
|
||||
<span v-if="!sidebarCollapsed">{{ selectedParentMenu.meta?.title }}</span>
|
||||
</div>
|
||||
<a-menu v-model:openKeys="openKeys" v-model:selectedKeys="selectedKeys" mode="inline"
|
||||
:selected-keys="[route.path]">
|
||||
<navMenu :menu-items="selectedParentMenu.children" :active-path="route.path" />
|
||||
@@ -169,7 +172,7 @@ const menuList = computed(() => {
|
||||
|
||||
// 获取图标组件
|
||||
const getIconComponent = (iconName) => {
|
||||
return icons[iconName] || icons.MenuOutlined
|
||||
return icons[iconName] || icons.FileTextOutlined
|
||||
}
|
||||
|
||||
// 处理父菜单点击(默认布局的第一级菜单)
|
||||
@@ -211,7 +214,6 @@ const updateMenuState = () => {
|
||||
if (layoutMode.value === 'default') {
|
||||
// 默认布局:找到当前路由对应的父菜单
|
||||
const currentMenu = findMenuByPath(menuList.value, route.path)
|
||||
console.log(currentMenu)
|
||||
if (currentMenu) {
|
||||
// 如果当前菜单有子菜单,设置为选中的父菜单
|
||||
if (currentMenu.children && currentMenu.children.length > 0) {
|
||||
@@ -276,7 +278,6 @@ watch(() => route.path, (newPath) => {
|
||||
|
||||
// 监听布局模式变化,确保菜单状态正确
|
||||
watch(() => layoutMode.value, () => {
|
||||
console.log('布局模式变化:', layoutMode.value)
|
||||
updateMenuState()
|
||||
})
|
||||
|
||||
@@ -315,7 +316,6 @@ onMounted(() => {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
height: 60px;
|
||||
z-index: 9;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
@@ -398,17 +398,6 @@ onMounted(() => {
|
||||
color: #ffffff;
|
||||
background-color: #1890ff;
|
||||
border-left-color: #ffffff;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border-width: 6px;
|
||||
border-style: solid;
|
||||
border-color: transparent #ffffff transparent transparent;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.anticon) {
|
||||
@@ -436,12 +425,13 @@ onMounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
gap: 5px;
|
||||
height: 60px;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.ant-menu) {
|
||||
|
||||
@@ -79,9 +79,10 @@ const handleLogin = async () => {
|
||||
if (userInfo.code == 1) {
|
||||
userStore.setUserInfo(userInfo.data)
|
||||
}
|
||||
let menu = await getMyMenu()
|
||||
if (menu.code == 1){
|
||||
userStore.setMenu(menu.data)
|
||||
let authData = await getMyMenu()
|
||||
if (authData.code == 1){
|
||||
userStore.setMenu(authData.data.menu)
|
||||
userStore.setPermissions(authData.data.permissions)
|
||||
}
|
||||
|
||||
message.success('登录成功');
|
||||
|
||||
@@ -11,6 +11,7 @@ export const useUserStore = defineStore(
|
||||
const refreshToken = ref('')
|
||||
const userInfo = ref(null)
|
||||
const menu = ref([])
|
||||
const permissions = ref([])
|
||||
|
||||
// 设置 token
|
||||
function setToken(newToken) {
|
||||
@@ -70,6 +71,11 @@ export const useUserStore = defineStore(
|
||||
menu.value = []
|
||||
}
|
||||
|
||||
// 设置权限
|
||||
function setPermissions(data){
|
||||
permissions.value = data
|
||||
}
|
||||
|
||||
// 登出
|
||||
function logout() {
|
||||
token.value = ''
|
||||
@@ -97,6 +103,7 @@ export const useUserStore = defineStore(
|
||||
setMenu,
|
||||
getMenu,
|
||||
clearMenu,
|
||||
setPermissions,
|
||||
logout,
|
||||
isLoggedIn,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user