添加tab栏

master
liuyx 2 years ago
parent a72a80d58f
commit 8776fcafe9

@ -1,3 +1,3 @@
{ {
"recommendations": ["Vue.volar"] "recommendations": ["vue.volar", "cnblogs.vscode-cnb"]
} }

@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/src/assets/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title> <title>后台管理</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

@ -13,6 +13,7 @@
"axios": "^1.1.3", "axios": "^1.1.3",
"element-plus": "^2.2.19", "element-plus": "^2.2.19",
"pinia": "^2.0.23", "pinia": "^2.0.23",
"pinia-plugin-persist": "^1.0.0",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"vue": "^3.2.41", "vue": "^3.2.41",
@ -4720,6 +4721,46 @@
} }
} }
}, },
"node_modules/pinia-plugin-persist": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/pinia-plugin-persist/-/pinia-plugin-persist-1.0.0.tgz",
"integrity": "sha512-M4hBBd8fz/GgNmUPaaUsC29y1M09lqbXrMAHcusVoU8xlQi1TqgkWnnhvMikZwr7Le/hVyMx8KUcumGGrR6GVw==",
"dependencies": {
"vue-demi": "^0.12.1"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0",
"pinia": "^2.0.0",
"vue": "^2.0.0 || >=3.0.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pinia-plugin-persist/node_modules/vue-demi": {
"version": "0.12.5",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz",
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": { "node_modules/pinia/node_modules/vue-demi": {
"version": "0.13.11", "version": "0.13.11",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz", "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz",
@ -9645,6 +9686,22 @@
} }
} }
}, },
"pinia-plugin-persist": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/pinia-plugin-persist/-/pinia-plugin-persist-1.0.0.tgz",
"integrity": "sha512-M4hBBd8fz/GgNmUPaaUsC29y1M09lqbXrMAHcusVoU8xlQi1TqgkWnnhvMikZwr7Le/hVyMx8KUcumGGrR6GVw==",
"requires": {
"vue-demi": "^0.12.1"
},
"dependencies": {
"vue-demi": {
"version": "0.12.5",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz",
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
"requires": {}
}
}
},
"posix-character-classes": { "posix-character-classes": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "resolved": "https://registry.npmmirror.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz",

@ -14,6 +14,7 @@
"axios": "^1.1.3", "axios": "^1.1.3",
"element-plus": "^2.2.19", "element-plus": "^2.2.19",
"pinia": "^2.0.23", "pinia": "^2.0.23",
"pinia-plugin-persist": "^1.0.0",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"vue": "^3.2.41", "vue": "^3.2.41",

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -10,4 +10,5 @@
.el-header { .el-header {
--el-header-padding: none; --el-header-padding: none;
height: 84px;
} }

@ -8,5 +8,5 @@
#app { #app {
height: 100vh; height: 100vh;
// font-family: 'Times New Roman', '仿宋'; font-family: 'Times New Roman', '仿宋';
} }

@ -0,0 +1,156 @@
<template>
<div class="header-container">
<div class="navbar">
<!-- 折叠按钮 -->
<div class="hamburger-container" @click="toggleCollapse">
<el-icon size="25px">
<component :is="icon"></component>
</el-icon>
</div>
<!-- 面包屑 -->
<b>{{ $route.name }}</b>
<div class="right-navbar">
<!-- 全屏按钮 -->
<el-icon size="25px" class="screenfull" @click="toggleScreen">
<FullScreen />
</el-icon>
<!-- 用户头像 -->
<el-dropdown class="right-item">
<span class="el-dropdown-link">
<el-avatar :src="circleUrl" />
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="logout">退</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<!-- 历史标签栏 -->
<div class="tabs-container">
<div class="tabs-content">
<span
v-for="item of store.tabList"
:key="item.path"
class="tabs-content-item"
:class="isActive(item)"
@click="goTo(item)"
>
{{ item.name }}
<el-icon v-if="item.name !== '首页'" @click.stop="removeTab(item)"><Close /></el-icon>
</span>
</div>
<div class="tabs-close" @click="closeAllTab"></div>
</div>
</div>
</template>
<script setup>
import screenfull from 'screenfull'
import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from '@/store'
const router = useRouter()
const route = useRoute()
const circleUrl = ref('https://liuyxcc.github.io/img/avatar.png')
const store = useStore()
const toggleCollapse = () => {
store.changeCollapse()
}
const closeAllTab = () => {
store.resetTab()
router.replace('/home') //
}
const goTo = (item) => {
router.push(item)
}
const removeTab = (item) => {
store.removeTab(item)
if (route.path === item.path) {
router.replace('/home')
}
}
const logout = () => {
localStorage.clear()
router.replace('/login')
console.log('logout')
}
const toggleScreen = () => {
if (screenfull.isEnabled) {
screenfull.toggle()
}
}
const icon = computed(() => {
return !store.collapse ? 'Fold' : 'Expand'
})
const isActive = computed(() => {
return (item) => (item.path === route.path ? 'tabs-content-item-active' : '')
})
// piniastoretabList
watch(
() => route.path,
() => {
// tabListstore
if (store.tabList.findIndex((item) => item.path === route.path) === -1) {
store.tabList.push({ name: route.name, path: route.path })
}
}
)
</script>
<style lang="scss" scoped>
.header-container {
padding: 0 10px;
height: 100%;
box-shadow: 0 2px 3px #888888;
height: 84px;
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
height: 42px;
.right-navbar {
display: flex;
justify-content: center;
align-items: center;
.right-item {
margin-left: 10px;
}
}
}
}
.tabs-container {
display: flex;
align-items: center;
height: 42px;
justify-content: space-between;
.tabs-content {
text-align: center;
.tabs-content-item {
cursor: pointer;
margin-right: 5px;
background-color: #eee;
padding: 5px;
border-radius: 5px;
}
.tabs-content-item-active {
background-color: #579572;
color: #eee;
}
}
.tabs-close {
cursor: pointer;
}
}
</style>

@ -4,13 +4,13 @@
background-color="#304156" background-color="#304156"
:default-active="$router.currentRoute.value.path" :default-active="$router.currentRoute.value.path"
text-color="#fff" text-color="#fff"
unique-opened="true" :unique-opened="true"
:collapse="store.collapse" :collapse="store.collapse"
router="true" :router="true"
class="side-nav-bar" class="side-nav-bar"
> >
<!-- 首页栏 --> <!-- 首页栏 -->
<el-menu-item index="/"> <el-menu-item index="/home">
<el-icon><HomeFilled /></el-icon> <el-icon><HomeFilled /></el-icon>
<span>首页</span> <span>首页</span>
</el-menu-item> </el-menu-item>

@ -1,28 +0,0 @@
<template>
<el-dropdown>
<span class="el-dropdown-link">
<el-avatar :src="circleUrl" />
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="logout">退</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const circleUrl = ref('https://liuyxcc.github.io/img/avatar.png')
const logout = () => {
localStorage.clear()
router.replace('/login')
console.log('logout')
}
</script>
<style lang="scss" scoped></style>

@ -1,28 +0,0 @@
<template>
<div class="hamburger-container" @click="toggleCollapse">
<el-icon size="25px">
<component :is="icon"></component>
</el-icon>
</div>
</template>
<script setup>
import { useStore } from '../../../store'
import { computed } from 'vue'
const store = useStore()
const icon = computed(() => {
return !store.collapse ? 'Fold' : 'Expand'
})
const toggleCollapse = () => {
store.changeCollapse()
}
</script>
<style lang="scss" scoped>
.hamburger-container {
cursor: pointer;
}
</style>

@ -1,22 +0,0 @@
<template>
<el-icon size="25px" class="screenfull" @click="toggleScreen">
<FullScreen />
</el-icon>
</template>
<script setup>
import screenfull from 'screenfull'
// screenfull
const toggleScreen = () => {
if (screenfull.isEnabled) {
screenfull.toggle()
}
}
</script>
<style lang="scss" scoped>
.screenfull {
cursor: pointer;
}
</style>

@ -1,40 +0,0 @@
<template>
<div class="header-container">
<div class="navbar">
<Hamburger />
<div class="right-navbar">
<Screenfull />
<Avatar class="right-item" />
</div>
</div>
<div class="tabs-view-container">tabs</div>
</div>
</template>
<script setup>
import Hamburger from './components/Hamburger.vue'
import Avatar from './components/Avatar.vue'
import Screenfull from './components/Screenfull.vue'
</script>
<style lang="scss" scoped>
.header-container {
padding: 0 10px;
height: 100%;
box-shadow: 0 2px 3px #888888;
height: 60px;
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
.right-navbar {
display: flex;
justify-content: center;
align-items: center;
.right-item {
margin-left: 10px;
}
}
}
}
</style>

@ -6,17 +6,22 @@
<el-container :class="'main-container ' + isHide"> <el-container :class="'main-container ' + isHide">
<el-header><Header /></el-header> <el-header><Header /></el-header>
<el-main> <el-main>
<transition name="fade" mode="out-in"> <!-- <transition name="fade" mode="out-in">
<router-view class="m-content"></router-view> <router-view class="m-content"></router-view>
</transition> </transition> -->
<router-view class="m-content" v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</el-main> </el-main>
</el-container> </el-container>
</el-container> </el-container>
</template> </template>
<script setup> <script setup>
import Menu from './menu/Menu.vue' import Menu from './components/Menu.vue'
import Header from './header/index.vue' import Header from './components/Header.vue'
import { useStore } from '../store' import { useStore } from '../store'
import { computed } from 'vue' import { computed } from 'vue'

@ -1,5 +1,4 @@
import { createApp } from 'vue' import { createApp, h } from 'vue'
// import './style.css'
import App from './App.vue' import App from './App.vue'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
@ -7,6 +6,7 @@ import './assets/scss/index.scss'
import router from './router' import router from './router'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import * as ElementPlusIconsVue from '@element-plus/icons-vue' import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import piniaPersist from 'pinia-plugin-persist'
// 引入md编辑器 // 引入md编辑器
import VueMarkdownEditor from '@kangc/v-md-editor' import VueMarkdownEditor from '@kangc/v-md-editor'
@ -15,9 +15,12 @@ import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js'
import '@kangc/v-md-editor/lib/theme/style/vuepress.css' import '@kangc/v-md-editor/lib/theme/style/vuepress.css'
import Prism from 'prismjs' import Prism from 'prismjs'
const app = createApp(App) const app = createApp({
render: () => h(App)
})
const pinia = createPinia() const pinia = createPinia()
pinia.use(piniaPersist)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) { for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component) app.component(key, component)

@ -9,34 +9,42 @@ const routes = [
children: [ children: [
{ {
path: '/home', path: '/home',
name: '首页',
component: () => import('@/views/home/Home.vue') component: () => import('@/views/home/Home.vue')
}, },
{ {
path: '/article', path: '/article',
name: '发布文章',
component: () => import('@/views/article/Article.vue') component: () => import('@/views/article/Article.vue')
}, },
{ {
path: '/article-list', path: '/article-list',
name: '文章列表',
component: () => import('@/views/article/ArticleList.vue') component: () => import('@/views/article/ArticleList.vue')
}, },
{ {
path: '/tags', path: '/tags',
name: '标签管理',
component: () => import('@/views/tag/Tag.vue') component: () => import('@/views/tag/Tag.vue')
}, },
{ {
path: '/categories', path: '/categories',
name: '分类管理',
component: () => import('@/views/category/Category.vue') component: () => import('@/views/category/Category.vue')
}, },
{ {
path: '/page', path: '/page',
name: '页面图片',
component: () => import('@/views/page/Page.vue') component: () => import('@/views/page/Page.vue')
}, },
{ {
path: '/about', path: '/about',
name: '关于我',
component: () => import('@/views/about/About.vue') component: () => import('@/views/about/About.vue')
}, },
{ {
path: '/users', path: '/users',
name: '用户列表',
component: () => import('@/views/user/User.vue') component: () => import('@/views/user/User.vue')
} }
] ]

@ -3,11 +3,28 @@ import { defineStore } from 'pinia'
export const useStore = defineStore('store', { export const useStore = defineStore('store', {
state: () => ({ state: () => ({
collapse: false, collapse: false,
token: localStorage.getItem('token') || '' token: localStorage.getItem('token') || '',
tabList: [{ name: '首页', path: '/home' }]
}), }),
actions: { actions: {
changeCollapse() { changeCollapse() {
this.collapse = !this.collapse this.collapse = !this.collapse
},
resetTab() {
// 重置tab列表
this.tabList = [{ name: '首页', path: '/home' }]
},
removeTab(item) {
this.tabList.splice(
this.tabList.findIndex((it) => it.name === item.name),
1
)
} }
},
persist: {
enabled: true,
strategies: [
{ storage: sessionStorage, paths: ['collapse', 'tabList'] }
]
} }
}) })

Loading…
Cancel
Save