完善后台布局

This commit is contained in:
2026-01-16 23:17:20 +08:00
parent 8cf5b446cf
commit b4c300bb32
9 changed files with 228 additions and 135 deletions

View File

@@ -17,6 +17,11 @@
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
// 定义组件名称(多词命名)
defineOptions({
name: 'LayoutBreadcrumb'
})
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const breadcrumbList = ref([]) const breadcrumbList = ref([])

View File

@@ -42,7 +42,7 @@ const router = useRouter()
// 获取图标组件 // 获取图标组件
const getIconComponent = (iconName) => { const getIconComponent = (iconName) => {
return icons[iconName] || icons.MenuOutlined return icons[iconName] || icons.FileTextOutlined
} }
// 处理菜单点击 // 处理菜单点击

View File

@@ -4,11 +4,16 @@
<div class="setting-item"> <div class="setting-item">
<div class="setting-title">布局模式</div> <div class="setting-title">布局模式</div>
<div class="layout-mode-list"> <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 }" :class="{ active: layoutStore.layoutMode === mode.value }"
@click="handleLayoutChange(mode.value)"> @click="handleLayoutChange(mode.value)"
>
<div class="layout-preview" :class="`preview-${mode.value}`"> <div class="layout-preview" :class="`preview-${mode.value}`">
<div class="preview-sidebar"></div> <div class="preview-sidebar"></div>
<div v-if="mode.value === 'default'" class="preview-sidebar-2"></div>
<div class="preview-content"> <div class="preview-content">
<div class="preview-header"></div> <div class="preview-header"></div>
<div class="preview-body"></div> <div class="preview-body"></div>
@@ -23,9 +28,14 @@
<div class="setting-item"> <div class="setting-item">
<div class="setting-title">主题颜色</div> <div class="setting-title">主题颜色</div>
<div class="color-list"> <div class="color-list">
<div v-for="color in themeColors" :key="color" class="color-item" <div
:class="{ active: themeColor === color }" :style="{ backgroundColor: color }" v-for="color in themeColors"
@click="changeThemeColor(color)"> :key="color"
class="color-item"
:class="{ active: themeColor === color }"
:style="{ backgroundColor: color }"
@click="changeThemeColor(color)"
>
<CheckOutlined v-if="themeColor === color" /> <CheckOutlined v-if="themeColor === color" />
</div> </div>
</div> </div>
@@ -38,6 +48,20 @@
<span>显示标签栏</span> <span>显示标签栏</span>
<a-switch v-model:checked="showTags" @change="handleShowTagsChange" /> <a-switch v-model:checked="showTags" @change="handleShowTagsChange" />
</div> </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> </div>
</div> </div>
@@ -45,16 +69,22 @@
</template> </template>
<script setup> <script setup>
import { ref, defineExpose } from 'vue' import { ref, defineExpose, defineOptions, watch, onMounted } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { useLayoutStore } from '@/stores/modules/layout' 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 layoutStore = useLayoutStore()
const open = ref(false) const open = ref(false)
const themeColor = ref('#1890ff') const themeColor = ref('#1890ff')
const showTags = ref(true) const showTags = ref(true)
const showBreadcrumb = ref(true)
const layoutModes = [ const layoutModes = [
{ value: 'default', label: '默认布局' }, { value: 'default', label: '默认布局' },
@@ -96,15 +126,60 @@ const handleLayoutChange = (mode) => {
// 切换主题颜色 // 切换主题颜色
const changeThemeColor = (color) => { const changeThemeColor = (color) => {
themeColor.value = color themeColor.value = color
// 这里可以实现主题切换逻辑 // 更新 CSS 变量
document.documentElement.style.setProperty('--primary-color', color)
message.success('主题颜色已更新') message.success('主题颜色已更新')
} }
// 切换标签栏显示 // 切换标签栏显示
const handleShowTagsChange = (checked) => { const handleShowTagsChange = (checked) => {
// 这里可以实现标签栏显示/隐藏逻辑 showTags.value = checked
console.log('showTags:', 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> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -133,11 +208,14 @@ const handleShowTagsChange = (checked) => {
transition: all 0.3s; transition: all 0.3s;
&:hover { &: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 { &.active {
border-color: #1890ff; border-color: var(--primary-color, #1890ff);
background-color: rgba(24, 144, 255, 0.05);
} }
.layout-preview { .layout-preview {
@@ -149,32 +227,38 @@ const handleShowTagsChange = (checked) => {
display: flex; display: flex;
background-color: #f0f2f5; background-color: #f0f2f5;
.preview-sidebar {
background-color: #001529;
}
.preview-sidebar-2 {
background-color: #fff;
border-left: 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-default { &.preview-default {
.preview-sidebar { .preview-sidebar {
width: 20px; width: 20px;
background-color: #001529;
} }
.preview-sidebar-2 { .preview-sidebar-2 {
width: 24px; width: 24px;
background-color: #fff;
border-left: 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;
}
} }
} }
@@ -184,22 +268,6 @@ const handleShowTagsChange = (checked) => {
background-color: #fff; background-color: #fff;
border-right: 1px solid #e8e8e8; 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 { &.preview-top {
@@ -212,16 +280,12 @@ const handleShowTagsChange = (checked) => {
} }
.preview-content { .preview-content {
flex: 1;
padding: 4px;
.preview-header { .preview-header {
display: none; display: none;
} }
.preview-body { .preview-body {
height: 100%; height: 100%;
background-color: #e8e8e8;
} }
} }
} }
@@ -237,7 +301,7 @@ const handleShowTagsChange = (checked) => {
position: absolute; position: absolute;
top: 4px; top: 4px;
right: 4px; right: 4px;
color: #1890ff; color: var(--primary-color, #1890ff);
font-size: 12px; font-size: 12px;
} }
} }
@@ -256,13 +320,17 @@ const handleShowTagsChange = (checked) => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: transform 0.2s; transition: all 0.2s;
border: 2px solid transparent;
&:hover { &:hover {
transform: scale(1.1); transform: scale(1.1);
} }
&.active { &.active {
border-color: #fff;
box-shadow: 0 0 0 2px var(--primary-color, #1890ff);
.anticon { .anticon {
color: #fff; color: #fff;
} }
@@ -288,6 +356,13 @@ const handleShowTagsChange = (checked) => {
} }
} }
} }
.action-buttons {
:deep(.ant-btn) {
height: 40px;
font-size: 14px;
}
}
} }
} }
</style> </style>

View File

@@ -41,9 +41,8 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, watch, onMounted } from 'vue' import { ref, watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useLayoutStore } from '@/stores/modules/layout'
import { getUserMenu } from '@/api/menu' import { getUserMenu } from '@/api/menu'
const props = defineProps({ const props = defineProps({
@@ -59,7 +58,6 @@ const props = defineProps({
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const layoutStore = useLayoutStore()
const menuList = ref([]) const menuList = ref([])
const selectedKeys = ref([]) const selectedKeys = ref([])

View File

@@ -1,12 +1,11 @@
<template> <template>
<div class="tags-view" v-show="showTags"> <div v-show="showTags" class="tags-view">
<a-dropdown :trigger="['contextmenu']"> <a-dropdown :trigger="['contextmenu']">
<div class="tags-wrapper"> <div class="tags-wrapper">
<a-space :size="4"> <a-space :size="4">
<a-tag v-for="tag in visitedViews" :key="tag.fullPath" :closable="!tag.meta?.affix" <a-tag v-for="tag in visitedViews" :key="tag.fullPath" :closable="!tag.meta?.affix" class="tag-item"
@close="closeSelectedTag(tag)" @click="clickTag(tag)" :class="{ active: isActive(tag) }" @click="clickTag(tag)" @close="closeSelectedTag(tag)"
@contextmenu.prevent="handleContextMenu($event, tag)" class="tag-item" @contextmenu.prevent="handleContextMenu($event, tag)">
:class="{ active: isActive(tag) }">
{{ tag.meta?.title || tag.name }} {{ tag.meta?.title || tag.name }}
</a-tag> </a-tag>
</a-space> </a-space>
@@ -17,7 +16,7 @@
<ReloadOutlined /> <ReloadOutlined />
<span>刷新</span> <span>刷新</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="close" v-if="!selectedTag.meta?.affix"> <a-menu-item v-if="!selectedTag.meta?.affix" key="close">
<CloseOutlined /> <CloseOutlined />
<span>关闭</span> <span>关闭</span>
</a-menu-item> </a-menu-item>
@@ -35,17 +34,17 @@
<div class="tags-actions"> <div class="tags-actions">
<a-tooltip title="刷新当前页"> <a-tooltip title="刷新当前页">
<a-button type="text" size="small" @click="refreshSelectedTag"> <a-button size="small" type="text" @click="refreshSelectedTag">
<ReloadOutlined /> <ReloadOutlined />
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip title="关闭其他"> <a-tooltip title="关闭其他">
<a-button type="text" size="small" @click="closeOthersTags"> <a-button size="small" type="text" @click="closeOthersTags">
<ColumnWidthOutlined /> <ColumnWidthOutlined />
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip title="关闭所有"> <a-tooltip title="关闭所有">
<a-button type="text" size="small" @click="closeAllTags"> <a-button size="small" type="text" @click="closeAllTags">
<CloseCircleOutlined /> <CloseCircleOutlined />
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@@ -54,10 +53,9 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, watch, onMounted, nextTick } from 'vue' import { ref, computed, watch, onMounted, defineOptions } from 'vue'
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useLayoutStore } from '@/stores/modules/layout' import { useLayoutStore } from '@/stores/modules/layout'
import { message } from 'ant-design-vue'
import { import {
ReloadOutlined, ReloadOutlined,
CloseOutlined, CloseOutlined,
@@ -65,6 +63,11 @@ import {
CloseCircleOutlined CloseCircleOutlined
} from '@ant-design/icons-vue' } from '@ant-design/icons-vue'
// 定义组件名称(多词命名)
defineOptions({
name: 'TagsView'
})
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const layoutStore = useLayoutStore() const layoutStore = useLayoutStore()
@@ -91,55 +94,75 @@ const addTags = () => {
meta: route.meta meta: route.meta
}) })
} }
return false
} }
// 移除标签 // 移除标签
const closeSelectedTag = (view) => { const closeSelectedTag = (view) => {
// 如果是固定标签,不允许关闭
if (view.meta?.affix) {
return
}
layoutStore.removeViewTags(view.fullPath) layoutStore.removeViewTags(view.fullPath)
// 如果关闭的是当前激活的标签,需要跳转
if (isActive(view)) { 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 = () => { const closeOthersTags = () => {
router.push(selectedTag.value) if (!selectedTag.value || !selectedTag.value.fullPath) {
layoutStore.viewTags = visitedViews.value.filter(tag => tag.meta?.affix || tag.fullPath === 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 closeAllTags = () => {
// 只保留固定标签
const affixTags = visitedViews.value.filter(tag => tag.meta?.affix) const affixTags = visitedViews.value.filter(tag => tag.meta?.affix)
layoutStore.viewTags = affixTags layoutStore.viewTags = affixTags
// 如果还有固定标签,跳转到第一个固定标签
if (affixTags.length > 0) { if (affixTags.length > 0) {
router.push(affixTags[0].fullPath) router.push(affixTags[0].fullPath)
}
}
// 跳转到最后一个标签
const toLastView = (visitedViews) => {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
router.push(latestView.fullPath)
} else { } else {
// 如果没有标签,跳转到首页 // 如果没有固定标签,跳转到首页
router.push('/') router.push('/home')
} }
} }
// 点击标签 // 点击标签
const clickTag = (tag) => { const clickTag = (tag) => {
selectedTag.value = tag if (!isActive(tag)) {
router.push(tag.fullPath) router.push(tag.fullPath)
}
} }
// 刷新当前标签 // 刷新当前标签
const refreshSelectedTag = () => { const refreshSelectedTag = () => {
const { fullPath } = route // 使用 router.go(0) 刷新页面
router.replace({ router.go(0)
path: '/redirect' + fullPath
})
} }
// 右键菜单处理 // 右键菜单处理
@@ -155,7 +178,7 @@ const handleMenuClick = ({ key }) => {
refreshSelectedTag() refreshSelectedTag()
break break
case 'close': case 'close':
if (!selectedTag.value.meta?.affix) { if (selectedTag.value && !selectedTag.value.meta?.affix) {
closeSelectedTag(selectedTag.value) closeSelectedTag(selectedTag.value)
} }
break break
@@ -168,16 +191,13 @@ const handleMenuClick = ({ key }) => {
} }
} }
// 监听路由变化 // 监听路由变化,自动添加标签
watch(() => route.path, () => { watch(() => route.fullPath, () => {
addTags() addTags()
// 更新当前选中的标签
selectedTag.value = visitedViews.value.find(tag => isActive(tag)) || {}
}, { immediate: true }) }, { immediate: true })
// 监听路由更新
onBeforeRouteUpdate((to) => {
addTags()
})
onMounted(() => { onMounted(() => {
addTags() addTags()
// 初始化选中的标签 // 初始化选中的标签

View File

@@ -1,12 +1,11 @@
<template> <template>
<div class="userbar"> <div class="userbar">
<!-- 菜单搜索 --> <!-- 菜单搜索 -->
<a-input-search v-model:value="searchKeyword" :placeholder="$t('common.searchMenu')" enter-button <a-tooltip :title="$t('common.search')">
@search="handleSearch" class="search-input" allow-clear> <a-button type="text" @click="showSearch" class="action-btn">
<template #prefix>
<SearchOutlined /> <SearchOutlined />
</template> </a-button>
</a-input-search> </a-tooltip>
<!-- 消息通知 --> <!-- 消息通知 -->
<a-dropdown :trigger="['click']" placement="bottomRight"> <a-dropdown :trigger="['click']" placement="bottomRight">
@@ -112,7 +111,7 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted, defineOptions } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { message, Modal } from 'ant-design-vue' import { message, Modal } from 'ant-design-vue'
import { useUserStore } from '@/stores/modules/user' import { useUserStore } from '@/stores/modules/user'
@@ -131,13 +130,17 @@ import {
} from '@ant-design/icons-vue' } from '@ant-design/icons-vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
// 定义组件名称(多词命名)
defineOptions({
name: 'UserBar'
})
const { t } = useI18n() const { t } = useI18n()
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const i18nStore = useI18nStore() const i18nStore = useI18nStore()
const isFullscreen = ref(false) const isFullscreen = ref(false)
const searchKeyword = ref('')
// 消息数据 // 消息数据
const messages = ref([ const messages = ref([
@@ -182,15 +185,9 @@ onUnmounted(() => {
document.removeEventListener('fullscreenchange', handleFullscreenChange) document.removeEventListener('fullscreenchange', handleFullscreenChange)
}) })
// 菜单搜索 // 显示搜索功能
const handleSearch = (value) => { const showSearch = () => {
if (!value.trim()) { message.info('搜索功能开发中')
message.warning(t('common.searchEmpty'))
return
}
// 这里可以实现实际的搜索逻辑,比如跳转到搜索页面或显示搜索结果
message.info(t('common.searching') + value)
searchKeyword.value = ''
} }
// 清除消息 // 清除消息
@@ -244,7 +241,7 @@ const handleLogout = () => {
await userStore.logout() await userStore.logout()
message.success(t('common.logoutSuccess')) message.success(t('common.logoutSuccess'))
router.push('/login') router.push('/login')
} catch (error) { } catch {
message.error(t('common.logoutFailed')) message.error(t('common.logoutFailed'))
} }
} }

View File

@@ -22,7 +22,10 @@
v-if="selectedParentMenu && selectedParentMenu.children && selectedParentMenu.children.length > 0" v-if="selectedParentMenu && selectedParentMenu.children && selectedParentMenu.children.length > 0"
theme="light" :collapsed="sidebarCollapsed" :collapsible="true" @collapse="handleCollapse" width="200" theme="light" :collapsed="sidebarCollapsed" :collapsible="true" @collapse="handleCollapse" width="200"
:collapsed-width="64" class="right-sidebar"> :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" <a-menu v-model:openKeys="openKeys" v-model:selectedKeys="selectedKeys" mode="inline"
:selected-keys="[route.path]"> :selected-keys="[route.path]">
<navMenu :menu-items="selectedParentMenu.children" :active-path="route.path" /> <navMenu :menu-items="selectedParentMenu.children" :active-path="route.path" />
@@ -169,7 +172,7 @@ const menuList = computed(() => {
// 获取图标组件 // 获取图标组件
const getIconComponent = (iconName) => { const getIconComponent = (iconName) => {
return icons[iconName] || icons.MenuOutlined return icons[iconName] || icons.FileTextOutlined
} }
// 处理父菜单点击(默认布局的第一级菜单) // 处理父菜单点击(默认布局的第一级菜单)
@@ -211,7 +214,6 @@ const updateMenuState = () => {
if (layoutMode.value === 'default') { if (layoutMode.value === 'default') {
// 默认布局:找到当前路由对应的父菜单 // 默认布局:找到当前路由对应的父菜单
const currentMenu = findMenuByPath(menuList.value, route.path) const currentMenu = findMenuByPath(menuList.value, route.path)
console.log(currentMenu)
if (currentMenu) { if (currentMenu) {
// 如果当前菜单有子菜单,设置为选中的父菜单 // 如果当前菜单有子菜单,设置为选中的父菜单
if (currentMenu.children && currentMenu.children.length > 0) { if (currentMenu.children && currentMenu.children.length > 0) {
@@ -276,7 +278,6 @@ watch(() => route.path, (newPath) => {
// 监听布局模式变化,确保菜单状态正确 // 监听布局模式变化,确保菜单状态正确
watch(() => layoutMode.value, () => { watch(() => layoutMode.value, () => {
console.log('布局模式变化:', layoutMode.value)
updateMenuState() updateMenuState()
}) })
@@ -315,7 +316,6 @@ onMounted(() => {
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
height: 60px; height: 60px;
z-index: 9;
.header-left { .header-left {
display: flex; display: flex;
@@ -398,17 +398,6 @@ onMounted(() => {
color: #ffffff; color: #ffffff;
background-color: #1890ff; background-color: #1890ff;
border-left-color: #ffffff; 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) { :deep(.anticon) {
@@ -436,12 +425,13 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-sizing: border-box;
gap: 5px;
height: 60px; height: 60px;
font-size: 18px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: #262626; color: #262626;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
background-color: #fafafa;
} }
:deep(.ant-menu) { :deep(.ant-menu) {

View File

@@ -79,9 +79,10 @@ const handleLogin = async () => {
if (userInfo.code == 1) { if (userInfo.code == 1) {
userStore.setUserInfo(userInfo.data) userStore.setUserInfo(userInfo.data)
} }
let menu = await getMyMenu() let authData = await getMyMenu()
if (menu.code == 1){ if (authData.code == 1){
userStore.setMenu(menu.data) userStore.setMenu(authData.data.menu)
userStore.setPermissions(authData.data.permissions)
} }
message.success('登录成功'); message.success('登录成功');

View File

@@ -11,6 +11,7 @@ export const useUserStore = defineStore(
const refreshToken = ref('') const refreshToken = ref('')
const userInfo = ref(null) const userInfo = ref(null)
const menu = ref([]) const menu = ref([])
const permissions = ref([])
// 设置 token // 设置 token
function setToken(newToken) { function setToken(newToken) {
@@ -70,6 +71,11 @@ export const useUserStore = defineStore(
menu.value = [] menu.value = []
} }
// 设置权限
function setPermissions(data){
permissions.value = data
}
// 登出 // 登出
function logout() { function logout() {
token.value = '' token.value = ''
@@ -97,6 +103,7 @@ export const useUserStore = defineStore(
setMenu, setMenu,
getMenu, getMenu,
clearMenu, clearMenu,
setPermissions,
logout, logout,
isLoggedIn, isLoggedIn,
} }