<template>
<div v-if="isLoginPage" class="login-page datav-page">
<dv-full-screen-container>
<router-view />
</dv-full-screen-container>
</div>
<div v-else class="admin-layout datav-page">
<dv-full-screen-container>
<el-container class="main-container">
<!-- 侧边栏 -->
<el-aside :width="isCollapse ? '64px' : '210px'" class="sidebar">
<dv-border-box-1 class="dv-border-box-container">
<div class="sidebar-logo">
<dv-decoration-2 style="width:100%;height:5px;" />
<transition name="sidebar-logo-fade">
<img v-if="isCollapse" src="./assets/logo-mini.svg" alt="Logo" class="sidebar-logo-img" />
<div v-else class="sidebar-logo-title">
<!-- <img src="./assets/logo.svg" alt="Logo" class="sidebar-logo-img" /> -->
<h1 class="sidebar-title">简历管理系统</h1>
</div>
</transition>
<dv-decoration-2 style="width:100%;height:5px;" />
</div>
<el-scrollbar>
<el-menu :default-active="activeMenu" class="el-menu-vertical datav-menu" :collapse="isCollapse" router
background-color="transparent" text-color="#bfcbd9" active-text-color="#00c0ff" :unique-opened="true"
:collapse-transition="false">
<el-menu-item index="/dashboard">
<el-icon>
<odometer />
</el-icon>
<template #title>仪表盘</template>
</el-menu-item>
<el-sub-menu index="1">
<template #title>
<el-icon>
<document />
</el-icon>
<span>简历管理</span>
</template>
<el-menu-item index="/resume">简历模板管理</el-menu-item>
<el-menu-item index="/industry-resume-templates">行业简历模板</el-menu-item>
<el-menu-item index="/recommend-templates">推荐模板管理</el-menu-item>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>
<el-icon><data-analysis /></el-icon>
<span>数据管理</span>
</template>
<el-menu-item index="/template">模型管理</el-menu-item>
<el-menu-item index="/industry-data">行业数据管理</el-menu-item>
<el-menu-item index="/bubble-tags">首页气泡设置</el-menu-item>
<el-menu-item index="/skills-management">技能管理</el-menu-item>
</el-sub-menu>
<el-menu-item index="/user">
<el-icon>
<user />
</el-icon>
<template #title>用户管理</template>
</el-menu-item>
<!-- <el-menu-item index="/settings">
<el-icon>
<setting />
</el-icon>
<template #title>系统设置</template>
</el-menu-item> -->
</el-menu>
</el-scrollbar>
</dv-border-box-1>
</el-aside>
<!-- 主内容区 -->
<el-container class="content-container">
<!-- 顶部导航栏 -->
<el-header class="header" height="50px">
<dv-border-box-3 class="dv-border-box-container">
<div class="header-container">
<div class="header-left">
<div class="toggle-sidebar" @click="toggleSidebar">
<el-icon>
<fold v-if="!isCollapse" />
<expand v-else />
</el-icon>
</div>
<div class="system-title">
<dv-decoration-3 style="width:200px;height:20px;" />
简历制作小程序后台管理系统
<dv-decoration-3 style="width:200px;height:20px;" />
</div>
</div>
<div class="header-right">
<dv-decoration-4 style="width:150px;height:30px;" />
<div class="header-item">
<el-tooltip content="全屏显示" effect="dark" placement="bottom">
<div class="fullscreen-btn" @click="toggleFullScreen">
<el-icon><full-screen /></el-icon>
</div>
</el-tooltip>
</div>
<div class="header-item">
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<div class="size-select">
<el-dropdown trigger="click" @command="handleSizeChange">
<div class="size-btn">
<el-icon><set-up /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="default">默认</el-dropdown-item>
<el-dropdown-item command="medium">中等</el-dropdown-item>
<el-dropdown-item command="small">小型</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-tooltip>
</div>
<div class="header-item user-info">
<el-dropdown trigger="click">
<span class="user-dropdown-link">
<el-avatar :size="30" :src="userAvatar"></el-avatar>
<span class="username">{{ username }}</span>
<el-icon><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item>
<el-icon>
<user />
</el-icon> 个人中心
</el-dropdown-item>
<el-dropdown-item>
<el-icon>
<key />
</el-icon> 修改密码
</el-dropdown-item> -->
<el-dropdown-item @click="handleLogout">
<el-icon><switch-button /></el-icon> 退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</dv-border-box-3>
</el-header>
<!-- 标签页导航 -->
<div class="tabs-view-container">
<dv-border-box-4 class="dv-border-box-container">
<el-tabs v-model="activeTab" type="card" closable @tab-remove="removeTab" @tab-click="handleTabClick"
class="tabs-view datav-tabs">
<el-tab-pane v-for="item in visitedViews" :key="item.path" :label="item.title"
:name="item.path"></el-tab-pane>
</el-tabs>
</dv-border-box-4>
</div>
<el-main class="main-content">
<dv-border-box-8 class="dv-border-box-container app-main-container">
<div class="app-main">
<el-scrollbar>
<router-view v-slot="{ Component }">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</el-scrollbar>
</div>
</dv-border-box-8>
</el-main>
<el-footer height="40px" class="footer">
<dv-border-box-2 class="dv-border-box-container">
<div class="footer-content">
<div class="footer-content">
<a href="https://mc.tencent.com/HRVjVcS5" target="_blank" class="footer-link">Made in CodeBuddy</a>
<span class="footer-divider">|</span>
<a href="https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/?from=csdn-hackathon-2025" target="_blank"
class="footer-link">Powered by CloudBase</a>
</div>
</div>
</dv-border-box-2>
</el-footer>
</el-container>
</el-container>
</dv-full-screen-container>
</div>
</template>
<script>
import { ref, computed, watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessageBox, ElMessage } from 'element-plus'
// 路由名称映射表
const routeTitleMap = {
'/': '首页',
'/dashboard': '仪表盘',
'/resume': '简历模板管理',
'/template': '模型管理',
'/industry-resume-templates': '行业简历模板',
'/recommend-templates': '推荐模板管理',
'/industry-data': '行业数据管理',
'/bubble-tags': '首页气泡设置',
'/skills-management': '技能管理',
'/user': '用户管理',
// '/settings': '系统设置'
}
export default {
name: 'App',
setup() {
const router = useRouter()
const route = useRoute()
const isCollapse = ref(false)
const username = ref(localStorage.getItem('username') || '管理员')
const userAvatar = ref('https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png')
const activeTab = ref('/')
const visitedViews = ref([{ title: '首页', path: '/' }])
const cachedViews = ref([])
const isLoginPage = computed(() => {
return route.path === '/login'
})
const activeMenu = computed(() => {
return route.path
})
const currentRoute = computed(() => {
return routeTitleMap[route.path] || route.meta?.title || '页面'
})
const toggleSidebar = () => {
isCollapse.value = !isCollapse.value
localStorage.setItem('sidebarStatus', isCollapse.value ? '1' : '0')
}
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
} else {
if (document.exitFullscreen) {
document.exitFullscreen()
}
}
}
const handleSizeChange = (size) => {
document.body.className = `size-${size}`
localStorage.setItem('size', size)
ElMessage.success(`布局大小已设置为${size === 'default' ? '默认' : size === 'medium' ? '中等' : '小型'}`)
}
const handleLogout = () => {
ElMessageBox.confirm('确定要退出登录吗?', '系统提示', {
confirmButtonText: '确认退出',
cancelButtonText: '取消操作',
type: 'warning',
customClass: 'datav-message-box',
distinguishCancelAndClose: true,
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = '退出中...';
setTimeout(() => {
// 清除登录信息
localStorage.removeItem('token');
localStorage.removeItem('username');
instance.confirmButtonLoading = false;
ElMessage({
type: 'success',
message: '退出登录成功',
customClass: 'datav-message'
});
router.push('/login');
done();
}, 500);
} else {
done();
}
}
}).catch(() => { })
}
// 处理界面风格切换
watch(() => route.meta.style, (newStyle) => {
if (newStyle) {
localStorage.setItem('uiStyle', newStyle)
ElMessage.success(`已切换到${newStyle === 'tech' ? 'technology格' : 'tradition风格'}界面`)
// 刷新当前页面以应用新的风格
if (route.path !== '/tech-style' && route.path !== '/traditional-style') {
router.go(0) // 刷新页面
} else {
router.push('/dashboard') // 如果在风格切换页面,则跳转到仪表盘
}
}
})
const addVisitedView = (view) => {
if (visitedViews.value.some(v => v.path === view.path)) return
// 使用路由映射表获取标题,如果没有则使用meta中的title,最后默认为"页面"
const title = routeTitleMap[view.path] || view.meta?.title || '页面'
visitedViews.value.push({
path: view.path,
title: title
})
}
const addCachedView = (view) => {
if (cachedViews.value.includes(view.name)) return
if (view.meta?.keepAlive) {
cachedViews.value.push(view.name)
}
}
const removeTab = (targetPath) => {
const index = visitedViews.value.findIndex(item => item.path === targetPath)
if (index !== -1) {
visitedViews.value.splice(index, 1)
}
if (activeTab.value === targetPath) {
const nextTab = visitedViews.value[index] || visitedViews.value[index - 1]
if (nextTab) {
activeTab.value = nextTab.path
router.push(nextTab.path)
}
}
}
const handleTabClick = (tab) => {
// 获取点击的标签页的路径
const path = tab.props.name
// 切换路由到对应的路径
router.push(path)
}
watch(() => route.path, (newPath) => {
activeTab.value = newPath
addVisitedView(route)
addCachedView(route)
})
onMounted(() => {
// 初始化侧边栏状态
const sidebarStatus = localStorage.getItem('sidebarStatus')
if (sidebarStatus) {
isCollapse.value = sidebarStatus === '1'
}
// 初始化布局大小
const size = localStorage.getItem('size') || 'default'
document.body.className = `size-${size}`
// 添加当前路由到已访问视图
if (route.path !== '/login') {
addVisitedView(route)
addCachedView(route)
}
})
return {
isCollapse,
activeMenu,
currentRoute,
username,
userAvatar,
isLoginPage,
activeTab,
visitedViews,
cachedViews,
toggleSidebar,
toggleFullScreen,
handleSizeChange,
handleLogout,
removeTab,
handleTabClick
}
}
}
</script>
<style>
html,
body {
margin: 0;
padding: 0;
height: 100%;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
background-color: #0e1c47;
color: #c0c4d3;
}
.datav-page {
width: 100%;
height: 100%;
background-color: #0e1c47;
color: #c0c4d3;
}
.dv-border-box-container {
width: 100%;
height: 100%;
padding: 5px;
box-sizing: border-box;
}
#app {
height: 100%;
}
.admin-layout {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
background-color: #0e1c47;
color: #c0c4d3;
}
/* 主容器 */
.main-container {
height: 100vh;
background-color: #0e1c47;
}
/* 侧边栏 */
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
background-color: transparent;
transition: width 0.28s;
overflow: hidden;
height: 90vh;
}
.sidebar-logo {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: transparent;
text-align: center;
overflow: hidden;
}
.sidebar-logo-img {
width: 32px;
height: 32px;
vertical-align: middle;
margin-right: 12px;
}
.sidebar-logo-title {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.sidebar-title {
display: inline-block;
margin: 0;
color: #00c0ff;
font-weight: 600;
font-size: 14px;
vertical-align: middle;
text-shadow: 0 0 10px rgba(0, 192, 255, 0.5);
}
.sidebar-logo-fade-enter-active,
.sidebar-logo-fade-leave-active {
transition: opacity 0.3s;
}
.sidebar-logo-fade-enter-from,
.sidebar-logo-fade-leave-to {
opacity: 0;
}
.el-menu-vertical:not(.el-menu--collapse) {
width: 210px;
}
.el-menu {
border-right: none !important;
}
.datav-menu {
background-color: transparent !important;
}
.datav-menu .el-menu-item.is-active {
background-color: rgba(0, 192, 255, 0.2) !important;
color: #00c0ff !important;
border-right: 2px solid #00c0ff;
}
.datav-menu .el-menu-item:hover,
.datav-menu .el-sub-menu__title:hover {
background-color: rgba(0, 192, 255, 0.1) !important;
}
.el-menu-item,
.el-sub-menu__title {
height: 50px !important;
line-height: 50px !important;
}
/* 内容区域 */
.content-container {
margin-left: 210px;
transition: margin-left 0.28s;
position: relative;
overflow: hidden;
background-color: transparent;
}
.content-container.collapsed {
margin-left: 64px;
}
/* 顶部导航栏 */
.header {
background-color: transparent;
padding: 0;
height: 50px !important;
position: relative;
z-index: 1000;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
}
.header-left {
display: flex;
align-items: center;
}
.system-title {
font-size: 18px;
font-weight: 600;
color: #00c0ff;
margin: 0 15px;
white-space: nowrap;
display: flex;
align-items: center;
text-shadow: 0 0 10px rgba(0, 192, 255, 0.5);
}
.breadcrumb {
margin-left: 15px;
}
.header-right {
display: flex;
align-items: center;
}
.header-item {
padding: 0 10px;
cursor: pointer;
height: 50px;
display: flex;
align-items: center;
}
.header-item:hover {
background-color: rgba(0, 192, 255, 0.1);
}
.user-info {
padding: 0 15px;
border-left: 1px solid #f0f0f0;
}
.fullscreen-btn,
.size-btn {
font-size: 18px;
color: #606266;
}
.user-dropdown-link {
display: flex;
align-items: center;
cursor: pointer;
}
.username {
margin: 0 8px;
color: #c0c4d3;
}
.toggle-sidebar {
font-size: 20px;
cursor: pointer;
margin-right: 15px;
color: #00c0ff;
padding: 0 10px;
height: 50px;
display: flex;
align-items: center;
}
.toggle-sidebar:hover {
background-color: rgba(0, 0, 0, 0.025);
}
/* 标签页导航 */
.tabs-view-container {
background-color: transparent;
padding: 0;
position: relative;
z-index: 999;
}
.tabs-view {
height: 40px;
line-height: 40px;
}
.main-content {
padding: 15px;
background-color: transparent;
overflow: hidden;
/* 禁止 el-main 区域滚动 */
height: calc(100vh - 140px);
}
.app-main-container {
width: 100%;
height: 100%;
position: relative;
}
.app-main {
background-color: transparent;
padding: 20px;
height: 100%;
overflow-y: auto;
/* 允许内容区域滚动 */
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/* 页脚 */
.footer {
background-color: transparent;
text-align: center;
color: #c0c4d3;
font-size: 12px;
padding: 0;
height: 40px !important;
line-height: 40px;
}
.footer-content {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 0 20px;
}
.footer-link {
color: #66d9ff;
font-size: 14px;
text-decoration: none;
text-shadow: 0 0 5px rgba(102, 217, 255, 0.5);
transition: all 0.3s ease;
}
.footer-link:hover {
color: #00c0ff;
text-shadow: 0 0 10px rgba(0, 192, 255, 0.8);
}
.footer-divider {
margin: 0 10px;
color: rgba(102, 217, 255, 0.6);
}
/* 过渡动画 */
.fade-transform-enter-active,
.fade-transform-leave-active {
transition: all 0.5s;
}
.fade-transform-enter-from {
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* 响应式布局 */
@media screen and (max-width: 992px) {
.sidebar {
width: 64px !important;
}
.content-container {
margin-left: 64px !important;
}
.system-title {
display: none;
}
}
/* 布局大小 */
body.size-default {
--el-font-size-base: 14px;
}
body.size-medium {
--el-font-size-base: 13px;
}
body.size-small {
--el-font-size-base: 12px;
}
/* DataV 边框盒子样式 */
.dv-border-box-8 {
position: relative;
box-sizing: border-box;
}
/* 自定义标签页样式 */
.datav-tabs .el-tabs__header {
background-color: transparent;
border-bottom: none;
}
.datav-tabs .el-tabs__item {
color: #c0c4d3 !important;
border: 1px solid rgba(0, 192, 255, 0.3) !important;
background-color: rgba(0, 192, 255, 0.1) !important;
transition: all 0.3s;
}
.datav-tabs .el-tabs__item.is-active {
color: #00c0ff !important;
border-color: #00c0ff !important;
background-color: rgba(0, 192, 255, 0.2) !important;
}
.datav-tabs .el-tabs__item:hover {
color: #00c0ff !important;
}
.el-tabs__nav-wrap {
padding-top: 5px;
}
</style>
<style lang="scss" scoped>
:deep(.el-scrollbar) {
height: 95% !important;
}
</style>