导航面包屑修改为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 {
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
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']
|
||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
@ -37,6 +34,8 @@ declare module '@vue/runtime-core' {
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
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']
|
||||
ElTransfer: typeof import('element-plus/es')['ElTransfer']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
|
||||
@ -20,12 +20,5 @@ export default [
|
||||
{ title: '图片资源库', path: '/api/sourceImage' },
|
||||
{ title: '歌曲库', path: '/api/music' }
|
||||
]
|
||||
},{
|
||||
name: 'tool',
|
||||
title: '工具',
|
||||
icon: 'Tools',
|
||||
child: [
|
||||
{ title: 'SQL占位符替换', path: '/tool/sqlReplace' }
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -1,6 +1,6 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { router, routePathes, filterExclude } from './router'
|
||||
import { router, filterExclude } from './router'
|
||||
import store from './store'
|
||||
|
||||
import VueAxios from 'vue-axios'
|
||||
@ -50,7 +50,10 @@ service.interceptors.response.use(res=> {
|
||||
|
||||
// 全局路由导航前置守卫
|
||||
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) {
|
||||
next()
|
||||
} else {
|
||||
|
||||
@ -8,17 +8,16 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{ path: '/login', name: 'Login', component: Login },
|
||||
{ path: '/', name: 'Home', component: Home, children: [
|
||||
{ path: '/', name: 'Welcome', component: Welcome },
|
||||
{ path: '/system/user', name: 'SystemUser', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemUser.vue') },
|
||||
{ path: '/system/role', name: 'SystemRole', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemRole.vue') },
|
||||
{ path: '/system/config', name: 'SystemConfig', component: () => import( /* webpackChunkName: "system" */ '@/views/system/SystemConfig.vue') },
|
||||
{ path: '/system/article', name: 'Article', component: () => import( /* webpackChunkName: "system" */ '@/views/system/Article.vue') },
|
||||
{ path: '/system/statistics', name: 'Statistics', component: () => import( /* webpackChunkName: "system" */ '@/views/system/Statistics.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'), meta: { title: '角色管理' } },
|
||||
{ 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'), meta: { title: '博客文章' } },
|
||||
{ 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/hitokoto', name: 'Hitokoto', component: () => import( /* webpackChunkName: "api" */ '@/views/api/Hitokoto.vue') },
|
||||
{ path: '/api/photoWall', name: 'PhotoWall', component: () => import( /* webpackChunkName: "api" */ '@/views/api/PhotoWall.vue') },
|
||||
{ path: '/api/sourceImage', name: 'SourceImage', component: () => import( /* webpackChunkName: "api" */ '@/views/api/SourceImage.vue') },
|
||||
{ path: '/tool/sqlReplace', name: 'SqlReplace', component: () => import( /* webpackChunkName: "api" */ '@/views/tool/SqlReplace.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'), meta: { title: '一言' } },
|
||||
{ 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'), meta: { title: '图片资源库' } },
|
||||
]}
|
||||
]
|
||||
|
||||
@ -27,14 +26,4 @@ export const router = createRouter({
|
||||
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']
|
||||
@ -1,4 +1,3 @@
|
||||
@panel-shadow-color: #ddd;
|
||||
html,body,#app,.layout {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -25,14 +24,14 @@ html,body,#app,.layout {
|
||||
padding: 10px !important;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
> .layout-breadcrumb{
|
||||
padding: 10px 15px 0;
|
||||
> .layout-tabs{
|
||||
margin: 15px 15px 0 15px;
|
||||
}
|
||||
> .layout-content{
|
||||
margin: 15px;
|
||||
margin: 0 15px 15px 15px;
|
||||
overflow: auto;
|
||||
background: #fff;
|
||||
box-shadow: -2px -2px 5px 0px @panel-shadow-color;
|
||||
border: 1px solid #e1e1e1;
|
||||
padding: 10px;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
@ -74,4 +73,8 @@ html,body,#app,.layout {
|
||||
> .el-drawer__body {
|
||||
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 { StateType, UserInfo } from './types'
|
||||
import { StateType, UserInfo, TabItem } from './types'
|
||||
|
||||
class Store {
|
||||
constructor(breadcrumb: string[]) {
|
||||
this.state.breadcrumb = breadcrumb
|
||||
constructor(tabs: TabItem[]) {
|
||||
this.state.tabs = tabs
|
||||
}
|
||||
state: StateType = {
|
||||
loginInfo: {
|
||||
userInfo: null,
|
||||
token: localStorage.getItem('login_token')
|
||||
},
|
||||
breadcrumb: [],
|
||||
tabs: [],
|
||||
activeTab: null,
|
||||
pageSizeOpts: [10, 20, 50, 100],
|
||||
pageLayout: 'sizes, prev, pager, next, total, ->, jumper'
|
||||
}
|
||||
@ -35,16 +36,30 @@ class Store {
|
||||
state.loginInfo.userInfo = null
|
||||
},
|
||||
/**
|
||||
* 设置面包屑导航
|
||||
* 添加导航页签
|
||||
* @param {Object} state
|
||||
* @param {Array} breadcrumbArr 面包屑导航的内容
|
||||
* @param {TabItem} tab 新加入的页签
|
||||
*/
|
||||
setBreadcrumb(state: StateType, breadcrumbArr: string[]): void {
|
||||
localStorage.setItem('breadcrumb', JSON.stringify(breadcrumbArr))
|
||||
state.breadcrumb = breadcrumbArr
|
||||
addTab(state: StateType, tab: TabItem): void {
|
||||
if (state.tabs.findIndex(item => item.path === tab.path) === -1) {
|
||||
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')
|
||||
export default createStore(new Store(breadcrumbStr ? JSON.parse(breadcrumbStr) : []))
|
||||
const tabsStr = localStorage.getItem('tabs')
|
||||
export default createStore(new Store(tabsStr ? JSON.parse(tabsStr) : []))
|
||||
@ -5,12 +5,19 @@ export interface UserInfo {
|
||||
role_ids: string[] // 角色ID
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
title: string // 标题文字
|
||||
path: string // 路由地址
|
||||
name: string // 组件名称
|
||||
}
|
||||
|
||||
export interface StateType {
|
||||
loginInfo: { // 登录信息
|
||||
userInfo: null | UserInfo
|
||||
token: null | string
|
||||
}
|
||||
breadcrumb: string[] // 面包屑导航文字
|
||||
tabs: TabItem[] // 导航页签
|
||||
activeTab: string | null // 当前激活的页签
|
||||
pageSizeOpts: number[] // 分页大小可选列表
|
||||
pageLayout: string // 分页工具栏
|
||||
}
|
||||
@ -21,7 +21,7 @@
|
||||
</el-header>
|
||||
<el-container>
|
||||
<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">
|
||||
<template #title>
|
||||
<component :is="menu.icon" style="width: 18px; margin-right: 5px;"></component>
|
||||
@ -34,13 +34,18 @@
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main class="layout-main">
|
||||
<div class="layout-breadcrumb">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item v-for="(item,index) in $store.state.breadcrumb" :key="index">{{item}}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<div class="layout-tabs">
|
||||
<el-tabs type="card" class="nav-tabs" v-model="$store.state.activeTab" @tab-change="openMenu" @tab-remove="removeTab">
|
||||
<el-tab-pane label="首页" name="/"></el-tab-pane>
|
||||
<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 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 class="layout-copy">2016-{{currentYear}} © colorfulsweet 上次更新:{{ version }}</div>
|
||||
</el-main>
|
||||
@ -60,16 +65,21 @@ export default class Home extends Vue{
|
||||
currentYear = new Date().getFullYear()
|
||||
// 菜单项
|
||||
menus = menus
|
||||
activeMenuItem: string | null = null
|
||||
defaultActiveMenuKey: string | null = null
|
||||
openMenuNames: string[] = []
|
||||
get realname(): null | string { // 当前用户的显示名称
|
||||
return this.$store.state.loginInfo.userInfo
|
||||
? 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> {
|
||||
this.activeMenuItem = this.$route.path
|
||||
if(this.activeMenuItem) {
|
||||
let result = /^\/(.*)\//.exec(this.activeMenuItem)
|
||||
this.defaultActiveMenuKey = this.$route.path
|
||||
if(this.defaultActiveMenuKey) {
|
||||
let result = /^\/(.*)\//.exec(this.defaultActiveMenuKey)
|
||||
if(result) {
|
||||
this.openMenuNames.push(result[1])
|
||||
}
|
||||
@ -103,5 +113,12 @@ export default class Home extends Vue{
|
||||
openMenu(path: string): void {
|
||||
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>
|
||||
@ -106,7 +106,7 @@
|
||||
v-model="markdownPreview.show"
|
||||
:title="markdownPreview.title"
|
||||
direction="rtl"
|
||||
custom-class="custom-drawer">
|
||||
class="custom-drawer">
|
||||
<div v-html="markdownPreview.content"></div>
|
||||
</el-drawer>
|
||||
</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