导航面包屑修改为tab页签
This commit is contained in:
parent
ce19454b5e
commit
ac3388f35d
5
components.d.ts
vendored
5
components.d.ts
vendored
@ -9,10 +9,7 @@ declare module '@vue/runtime-core' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||||
ElAside: typeof import('element-plus/es')['ElAside']
|
ElAside: typeof import('element-plus/es')['ElAside']
|
||||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
|
||||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
@ -37,6 +34,8 @@ declare module '@vue/runtime-core' {
|
|||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
|
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||||
|
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
ElTransfer: typeof import('element-plus/es')['ElTransfer']
|
ElTransfer: typeof import('element-plus/es')['ElTransfer']
|
||||||
ElTree: typeof import('element-plus/es')['ElTree']
|
ElTree: typeof import('element-plus/es')['ElTree']
|
||||||
|
|||||||
@ -20,12 +20,5 @@ export default [
|
|||||||
{ title: '图片资源库', path: '/api/sourceImage' },
|
{ title: '图片资源库', path: '/api/sourceImage' },
|
||||||
{ title: '歌曲库', path: '/api/music' }
|
{ title: '歌曲库', path: '/api/music' }
|
||||||
]
|
]
|
||||||
},{
|
|
||||||
name: 'tool',
|
|
||||||
title: '工具',
|
|
||||||
icon: 'Tools',
|
|
||||||
child: [
|
|
||||||
{ title: 'SQL占位符替换', path: '/tool/sqlReplace' }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { router, routePathes, filterExclude } from './router'
|
import { router, filterExclude } from './router'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
|
|
||||||
import VueAxios from 'vue-axios'
|
import VueAxios from 'vue-axios'
|
||||||
@ -50,7 +50,10 @@ service.interceptors.response.use(res=> {
|
|||||||
|
|
||||||
// 全局路由导航前置守卫
|
// 全局路由导航前置守卫
|
||||||
router.beforeEach(function (to, from, next) {
|
router.beforeEach(function (to, from, next) {
|
||||||
mountedApp.$store.commit('setBreadcrumb', routePathes[to.path] || [])
|
if (to.meta?.title) {
|
||||||
|
mountedApp.$store.commit('addTab', { title: to.meta.title, path: to.path, name: to.name })
|
||||||
|
}
|
||||||
|
mountedApp.$store.state.activeTab = to.path
|
||||||
if(filterExclude.indexOf(to.path) !== -1 || mountedApp.$store.state.loginInfo.token) {
|
if(filterExclude.indexOf(to.path) !== -1 || mountedApp.$store.state.loginInfo.token) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -8,17 +8,16 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{ path: '/login', name: 'Login', component: Login },
|
{ path: '/login', name: 'Login', component: Login },
|
||||||
{ path: '/', name: 'Home', component: Home, children: [
|
{ path: '/', name: 'Home', component: Home, children: [
|
||||||
{ path: '/', name: 'Welcome', component: Welcome },
|
{ path: '/', name: 'Welcome', component: Welcome },
|
||||||
{ path: '/system/user', name: 'SystemUser', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemUser.vue') },
|
{ path: '/system/user', name: 'SystemUser', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemUser.vue'), meta: { title: '用户管理' } },
|
||||||
{ path: '/system/role', name: 'SystemRole', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemRole.vue') },
|
{ path: '/system/role', name: 'SystemRole', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemRole.vue'), meta: { title: '角色管理' } },
|
||||||
{ path: '/system/config', name: 'SystemConfig', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemConfig.vue') },
|
{ path: '/system/config', name: 'SystemConfig', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemConfig.vue'), meta: { title: '系统配置' } },
|
||||||
{ path: '/system/article', name: 'Article', component: () => import( /* webpackChunkName: "system" */ '@/views/system/Article.vue') },
|
{ path: '/system/article', name: 'Article', component: () => import( /* webpackChunkName: "system" */ '@/views/system/Article.vue'), meta: { title: '博客文章' } },
|
||||||
{ path: '/system/statistics', name: 'Statistics', component: () => import( /* webpackChunkName: "system" */ '@/views/system/Statistics.vue') },
|
{ path: '/system/statistics', name: 'Statistics', component: () => import( /* webpackChunkName: "system" */ '@/views/system/Statistics.vue'), meta: { title: '分析统计' } },
|
||||||
|
|
||||||
{ path: '/api/music', name: 'Music', component: () => import( /* webpackChunkName: "api" */ '@/views/api/Music.vue') },
|
{ path: '/api/music', name: 'Music', component: () => import( /* webpackChunkName: "api" */ '@/views/api/Music.vue'), meta: { title: '歌曲库' } },
|
||||||
{ path: '/api/hitokoto', name: 'Hitokoto', component: () => import( /* webpackChunkName: "api" */ '@/views/api/Hitokoto.vue') },
|
{ path: '/api/hitokoto', name: 'Hitokoto', component: () => import( /* webpackChunkName: "api" */ '@/views/api/Hitokoto.vue'), meta: { title: '一言' } },
|
||||||
{ path: '/api/photoWall', name: 'PhotoWall', component: () => import( /* webpackChunkName: "api" */ '@/views/api/PhotoWall.vue') },
|
{ path: '/api/photoWall', name: 'PhotoWall', component: () => import( /* webpackChunkName: "api" */ '@/views/api/PhotoWall.vue'), meta: { title: '照片墙' } },
|
||||||
{ path: '/api/sourceImage', name: 'SourceImage', component: () => import( /* webpackChunkName: "api" */ '@/views/api/SourceImage.vue') },
|
{ path: '/api/sourceImage', name: 'SourceImage', component: () => import( /* webpackChunkName: "api" */ '@/views/api/SourceImage.vue'), meta: { title: '图片资源库' } },
|
||||||
{ path: '/tool/sqlReplace', name: 'SqlReplace', component: () => import( /* webpackChunkName: "api" */ '@/views/tool/SqlReplace.vue') },
|
|
||||||
]}
|
]}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -27,14 +26,4 @@ export const router = createRouter({
|
|||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
import menus from './config/menu'
|
|
||||||
export const routePathes : {[propName: string]: string[]} = {
|
|
||||||
'/': ['首页'],
|
|
||||||
}
|
|
||||||
for(let menu of menus) {
|
|
||||||
for(let submenu of menu.child) {
|
|
||||||
routePathes[submenu.path] = ['首页', menu.title, submenu.title]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const filterExclude = ['/login']
|
export const filterExclude = ['/login']
|
||||||
@ -1,4 +1,3 @@
|
|||||||
@panel-shadow-color: #ddd;
|
|
||||||
html,body,#app,.layout {
|
html,body,#app,.layout {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -25,14 +24,14 @@ html,body,#app,.layout {
|
|||||||
padding: 10px !important;
|
padding: 10px !important;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
> .layout-breadcrumb{
|
> .layout-tabs{
|
||||||
padding: 10px 15px 0;
|
margin: 15px 15px 0 15px;
|
||||||
}
|
}
|
||||||
> .layout-content{
|
> .layout-content{
|
||||||
margin: 15px;
|
margin: 0 15px 15px 15px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: -2px -2px 5px 0px @panel-shadow-color;
|
border: 1px solid #e1e1e1;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
@ -74,4 +73,8 @@ html,body,#app,.layout {
|
|||||||
> .el-drawer__body {
|
> .el-drawer__body {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.el-tabs--card.nav-tabs > .el-tabs__header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
@ -1,16 +1,17 @@
|
|||||||
import { createStore } from 'vuex'
|
import { createStore } from 'vuex'
|
||||||
import { StateType, UserInfo } from './types'
|
import { StateType, UserInfo, TabItem } from './types'
|
||||||
|
|
||||||
class Store {
|
class Store {
|
||||||
constructor(breadcrumb: string[]) {
|
constructor(tabs: TabItem[]) {
|
||||||
this.state.breadcrumb = breadcrumb
|
this.state.tabs = tabs
|
||||||
}
|
}
|
||||||
state: StateType = {
|
state: StateType = {
|
||||||
loginInfo: {
|
loginInfo: {
|
||||||
userInfo: null,
|
userInfo: null,
|
||||||
token: localStorage.getItem('login_token')
|
token: localStorage.getItem('login_token')
|
||||||
},
|
},
|
||||||
breadcrumb: [],
|
tabs: [],
|
||||||
|
activeTab: null,
|
||||||
pageSizeOpts: [10, 20, 50, 100],
|
pageSizeOpts: [10, 20, 50, 100],
|
||||||
pageLayout: 'sizes, prev, pager, next, total, ->, jumper'
|
pageLayout: 'sizes, prev, pager, next, total, ->, jumper'
|
||||||
}
|
}
|
||||||
@ -35,16 +36,30 @@ class Store {
|
|||||||
state.loginInfo.userInfo = null
|
state.loginInfo.userInfo = null
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 设置面包屑导航
|
* 添加导航页签
|
||||||
* @param {Object} state
|
* @param {Object} state
|
||||||
* @param {Array} breadcrumbArr 面包屑导航的内容
|
* @param {TabItem} tab 新加入的页签
|
||||||
*/
|
*/
|
||||||
setBreadcrumb(state: StateType, breadcrumbArr: string[]): void {
|
addTab(state: StateType, tab: TabItem): void {
|
||||||
localStorage.setItem('breadcrumb', JSON.stringify(breadcrumbArr))
|
if (state.tabs.findIndex(item => item.path === tab.path) === -1) {
|
||||||
state.breadcrumb = breadcrumbArr
|
state.tabs.push(tab)
|
||||||
|
localStorage.setItem('tabs', JSON.stringify(state.tabs))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 移除导航页签
|
||||||
|
* @param {Object} state
|
||||||
|
* @param {TabItem} name 页签标识名称
|
||||||
|
*/
|
||||||
|
removeTab(state: StateType, path: string): void {
|
||||||
|
const removeIndex = state.tabs.findIndex(item => item.path === path)
|
||||||
|
if (removeIndex !== -1) {
|
||||||
|
state.tabs.splice(removeIndex, 1)
|
||||||
|
localStorage.setItem('tabs', JSON.stringify(state.tabs))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const breadcrumbStr = localStorage.getItem('breadcrumb')
|
const tabsStr = localStorage.getItem('tabs')
|
||||||
export default createStore(new Store(breadcrumbStr ? JSON.parse(breadcrumbStr) : []))
|
export default createStore(new Store(tabsStr ? JSON.parse(tabsStr) : []))
|
||||||
@ -5,12 +5,19 @@ export interface UserInfo {
|
|||||||
role_ids: string[] // 角色ID
|
role_ids: string[] // 角色ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TabItem {
|
||||||
|
title: string // 标题文字
|
||||||
|
path: string // 路由地址
|
||||||
|
name: string // 组件名称
|
||||||
|
}
|
||||||
|
|
||||||
export interface StateType {
|
export interface StateType {
|
||||||
loginInfo: { // 登录信息
|
loginInfo: { // 登录信息
|
||||||
userInfo: null | UserInfo
|
userInfo: null | UserInfo
|
||||||
token: null | string
|
token: null | string
|
||||||
}
|
}
|
||||||
breadcrumb: string[] // 面包屑导航文字
|
tabs: TabItem[] // 导航页签
|
||||||
|
activeTab: string | null // 当前激活的页签
|
||||||
pageSizeOpts: number[] // 分页大小可选列表
|
pageSizeOpts: number[] // 分页大小可选列表
|
||||||
pageLayout: string // 分页工具栏
|
pageLayout: string // 分页工具栏
|
||||||
}
|
}
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</el-header>
|
</el-header>
|
||||||
<el-container>
|
<el-container>
|
||||||
<el-aside width="200px">
|
<el-aside width="200px">
|
||||||
<el-menu :default-active="activeMenuItem" :default-openeds="openMenuNames" style="height: 100%;">
|
<el-menu :default-active="defaultActiveMenuKey" :default-openeds="openMenuNames" style="height: 100%;">
|
||||||
<el-sub-menu v-for="menu in menus" :key="menu.name" :index="menu.name">
|
<el-sub-menu v-for="menu in menus" :key="menu.name" :index="menu.name">
|
||||||
<template #title>
|
<template #title>
|
||||||
<component :is="menu.icon" style="width: 18px; margin-right: 5px;"></component>
|
<component :is="menu.icon" style="width: 18px; margin-right: 5px;"></component>
|
||||||
@ -34,13 +34,18 @@
|
|||||||
</el-menu>
|
</el-menu>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
<el-main class="layout-main">
|
<el-main class="layout-main">
|
||||||
<div class="layout-breadcrumb">
|
<div class="layout-tabs">
|
||||||
<el-breadcrumb separator="/">
|
<el-tabs type="card" class="nav-tabs" v-model="$store.state.activeTab" @tab-change="openMenu" @tab-remove="removeTab">
|
||||||
<el-breadcrumb-item v-for="(item,index) in $store.state.breadcrumb" :key="index">{{item}}</el-breadcrumb-item>
|
<el-tab-pane label="首页" name="/"></el-tab-pane>
|
||||||
</el-breadcrumb>
|
<el-tab-pane v-for="tab in $store.state.tabs" :key="tab.name" :label="tab.title" :name="tab.path" closable></el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-content">
|
<div class="layout-content">
|
||||||
<router-view class="main-view"></router-view>
|
<router-view class="main-view" v-slot="{ Component }">
|
||||||
|
<keep-alive :include="keepViews">
|
||||||
|
<component :is="Component" />
|
||||||
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-copy">2016-{{currentYear}} © colorfulsweet 上次更新:{{ version }}</div>
|
<div class="layout-copy">2016-{{currentYear}} © colorfulsweet 上次更新:{{ version }}</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
@ -60,16 +65,21 @@ export default class Home extends Vue{
|
|||||||
currentYear = new Date().getFullYear()
|
currentYear = new Date().getFullYear()
|
||||||
// 菜单项
|
// 菜单项
|
||||||
menus = menus
|
menus = menus
|
||||||
activeMenuItem: string | null = null
|
defaultActiveMenuKey: string | null = null
|
||||||
openMenuNames: string[] = []
|
openMenuNames: string[] = []
|
||||||
get realname(): null | string { // 当前用户的显示名称
|
get realname(): null | string { // 当前用户的显示名称
|
||||||
return this.$store.state.loginInfo.userInfo
|
return this.$store.state.loginInfo.userInfo
|
||||||
? this.$store.state.loginInfo.userInfo.realname : null
|
? this.$store.state.loginInfo.userInfo.realname : null
|
||||||
}
|
}
|
||||||
|
get keepViews(): string[] {
|
||||||
|
return this.$store.state.tabs
|
||||||
|
.filter(item => item.name)
|
||||||
|
.map(item => item.name)
|
||||||
|
}
|
||||||
async created(): Promise<void> {
|
async created(): Promise<void> {
|
||||||
this.activeMenuItem = this.$route.path
|
this.defaultActiveMenuKey = this.$route.path
|
||||||
if(this.activeMenuItem) {
|
if(this.defaultActiveMenuKey) {
|
||||||
let result = /^\/(.*)\//.exec(this.activeMenuItem)
|
let result = /^\/(.*)\//.exec(this.defaultActiveMenuKey)
|
||||||
if(result) {
|
if(result) {
|
||||||
this.openMenuNames.push(result[1])
|
this.openMenuNames.push(result[1])
|
||||||
}
|
}
|
||||||
@ -103,5 +113,12 @@ export default class Home extends Vue{
|
|||||||
openMenu(path: string): void {
|
openMenu(path: string): void {
|
||||||
this.$router.push(path)
|
this.$router.push(path)
|
||||||
}
|
}
|
||||||
|
removeTab(path: string): void {
|
||||||
|
this.$store.commit('removeTab', path)
|
||||||
|
if (this.$store.state.activeTab === path) { // 关闭的是当前打开的页签
|
||||||
|
const { tabs } = this.$store.state
|
||||||
|
this.openMenu(tabs[tabs.length - 1]?.path || '/')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -106,7 +106,7 @@
|
|||||||
v-model="markdownPreview.show"
|
v-model="markdownPreview.show"
|
||||||
:title="markdownPreview.title"
|
:title="markdownPreview.title"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
custom-class="custom-drawer">
|
class="custom-drawer">
|
||||||
<div v-html="markdownPreview.content"></div>
|
<div v-html="markdownPreview.content"></div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,71 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-form :label-width="100">
|
|
||||||
<el-form-item label="SQL语句">
|
|
||||||
<el-input v-model="sql" type="textarea" :rows="5"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="参数">
|
|
||||||
<el-input v-model="params" type="textarea" :rows="3"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="替换结果">
|
|
||||||
<el-input v-model="replaceResult" ref="resultInput" readonly/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<div style="text-align:center">
|
|
||||||
<el-button-group size="large" >
|
|
||||||
<el-button type="primary" icon="DocumentCopy" @click="replacePlaceholder">替换占位符</el-button>
|
|
||||||
<el-button type="default" @click="clear">清空</el-button>
|
|
||||||
</el-button-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Options, Vue } from 'vue-class-component'
|
|
||||||
import { ElInput, ElMessage } from 'element-plus'
|
|
||||||
|
|
||||||
@Options({
|
|
||||||
name: 'SqlReplace'
|
|
||||||
})
|
|
||||||
export default class SqlReplace extends Vue {
|
|
||||||
sql: string = ''
|
|
||||||
params: string = ''
|
|
||||||
replaceResult: string = ''
|
|
||||||
|
|
||||||
replacePlaceholder() {
|
|
||||||
let sql = this.sql
|
|
||||||
const reg = /(.+?)\s*\((String|Integer|Boolean)\),?/g
|
|
||||||
let execResult = reg.exec(this.params)
|
|
||||||
let replaceMent = ''
|
|
||||||
while(execResult && sql.indexOf('?') !== -1) {
|
|
||||||
switch(execResult[2]) {
|
|
||||||
case 'String':
|
|
||||||
replaceMent = `'${execResult[1].trim()}'`
|
|
||||||
break
|
|
||||||
case 'Integer':
|
|
||||||
replaceMent = execResult[1].trim()
|
|
||||||
break
|
|
||||||
case 'Boolean':
|
|
||||||
replaceMent = eval(execResult[1]) ? '1' : '0'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sql = sql.replace('?', replaceMent)
|
|
||||||
execResult = reg.exec(this.params)
|
|
||||||
}
|
|
||||||
this.replaceResult = sql
|
|
||||||
Promise.resolve('已复制到剪贴板').then(message => {
|
|
||||||
(this.$refs.resultInput as typeof ElInput).select()
|
|
||||||
if (document.execCommand('copy')) {
|
|
||||||
ElMessage.success(message)
|
|
||||||
} else {
|
|
||||||
ElMessage.warning('复制失败,请手动复制')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
clear() {
|
|
||||||
this.sql = ''
|
|
||||||
this.params = ''
|
|
||||||
this.replaceResult = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user