前言
因為是在vue源碼的基礎上進行修改,所以,就沒有復制代碼在文章上,采取的是截圖對比源碼和我修改的代碼片段。要麻煩你們自己手敲了。
先來看看效果:
場景:在費用配置列表中,點擊每一項的配置,都會在頁面容器內(nèi)部打開一個新頁面,所以新頁面的路徑一樣,根據(jù)傳的參數(shù)不同,面包屑和標簽頁標題動態(tài)改變
二級路由效果(這是用菜單管理新建一條隱藏的路由做法,不推薦)
http://localhost/feeManage/feeConfigDetail?id=4&metaTitle=3253的費用配置
http://localhost/feeManage/feeConfigDetail?id=1&metaTitle=eww的費用配置
三級路由效果(推薦)
路由跳轉(zhuǎn)方法:
// 方法1
this.$router.push({
path: '/feeManage/feeConfig/feeConfigDetail',
query: {
id: row.id,},
})
// 方法2
<router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
<span>點擊跳轉(zhuǎn)</span>
</router-link>
若依路由簡單介紹:
若依vue前端能跳轉(zhuǎn)到新頁面路由的方法,我暫且知道這三種形式:
1 在若依的菜單項添加(不推薦)
缺點:因為路由暴露在外面,會發(fā)生被誤刪或者修改錯誤的情況,造成嚴重缺陷。
優(yōu)點:不用寫路由配置的代碼,可以直接進行路由跳轉(zhuǎn)了。
同時還要注意,這是詳細頁面,不應該在左側(cè)菜單欄出現(xiàn),所以要隱藏
2 在router的constantRoutes里添加路由(推薦)
?3 在router的dynamicRoutes里添加路由(推薦)
若依字典管理的動態(tài)路由配置(如果是想實現(xiàn)像若依字典這樣的路由跳轉(zhuǎn)效果,就可以直接參考若依的源碼去做:?
?
如果需要權(quán)限,需要自己寫權(quán)限標識(很麻煩,,對接很累)
先是后端寫上。。。
?然后前端。。。
代碼實現(xiàn)
路由配置
實現(xiàn)三級標題的路由怎么寫?
{
path: '/feeManage',
component: Layout, // 一級這個component: Layout必填,除非是不需要在頁面容器里打開的頁面
hidden: true, // false:顯示在側(cè)邊欄菜單
redirect: 'noRedirect', // noRedirect:面包屑不可點擊,不寫這個,父級標題樣式就和首頁一樣,黑字可點擊跳轉(zhuǎn)
meta: { title: '費用管理'}, // 一級標題,寫了才能顯示在面包屑上
children: [
{
path: '',
component:{ render: (e) => e("router-view") }, // 如果你的'feeConfig'路徑已經(jīng)在系統(tǒng)菜單中設置過了,這里的path和component就寫得和我一樣就行,直接跳轉(zhuǎn)三級路由
hidden: true, // false:顯示在側(cè)邊欄菜單
redirect: 'noRedirect', // noRedirect:面包屑不可點擊,不寫這個,父級標題樣式就和首頁一樣,黑字可點擊跳轉(zhuǎn)
meta: { title: '費用配置'}, // 二級標題,寫了才能顯示在面包屑上
// 如果你不需要二級的父級標題,那你就直接把第二個children的內(nèi)容寫在第一個children就行
children: [
{
path: 'feeConfig/feeConfigDetail',
component: () => import('@/views/feeManage/feeConfigDetail/index'),
name: 'feeConfigDetail',
meta: { title: '費用配置', activeMenu: '/feeManage/feeConfig' } // meta.title:三級標題,meta.activeMenu:側(cè)邊欄父級菜單高亮
}
]
}
]
}
也可以這樣寫(這樣寫是建立在之前寫的跳轉(zhuǎn)路徑不規(guī)范,如果不想改代碼那么多,只能自己在路由這里改,就不用動業(yè)務代碼里的跳轉(zhuǎn)路徑,當然我強迫癥,我最后都改了)
{
path: '',
component: Layout,
hidden: true,
redirect: 'noRedirect',
meta: { title: '運營中心' },
children: [
{
path: '/overseas-collocation',
component:{ render: (e) => e("router-view") }, // 如果你的'merchant'路徑已經(jīng)在系統(tǒng)菜單中設置過了,這里的path和component就寫得和我一樣就行,直接跳轉(zhuǎn)三級路由
hidden: true, // false:顯示在側(cè)邊欄菜單
redirect: 'noRedirect', // noRedirect:面包屑不可點擊,不寫這個,父級標題樣式就和首頁一樣,黑字可點擊跳轉(zhuǎn)
meta: { title: '海外拼柜'}, // 二級標題,寫了才能顯示在面包屑上
// 如果你不需要二級的父級標題,那你就直接把第二個children的內(nèi)容寫在第一個children就行
children: [
{
path: 'detail/:id(\\d+)',
component: () => import('@/views/operation-center/overseas-collocation/collocation-detail'),
name: 'overseasCollocationDetail',
meta: { title: '拼柜詳情', activeMenu: '/operation-center/overseas-collocation/overseas-collocation' }
}
]
}
]
}
<router-link :to="'/overseas-collocation/detail/' + scope.row.id">
<el-button type="text">查看</el-button>
</router-link>
改后
面包屑和標簽頁動態(tài)標題配置
配置完路由后,就要講,如何動態(tài)設置路由path不同參數(shù) 在頁面容器里打開新頁面,面包屑和標簽頁標題根據(jù)參數(shù)動態(tài)改變
?使用1方法創(chuàng)建好路由后,然后用$router.push設置傳的參數(shù),我們使用metaTitle來當頁面標題
this.$router.push({
path: '/feeManage/feeConfigDetail',
query: {
id: row.id,
metaTitle: row.chargeName + '的費用配置'
},
})
如果你只做到了這里,你就會發(fā)現(xiàn),它確實跳轉(zhuǎn)頁面了,但是它是同一個頁面進行了不同參數(shù)的刷新,然后頁面的標題也沒有動態(tài)改變,而是你之前菜單配置時寫的標題,如圖:
?? ? ? ? ? ? ? ? ?
?下面就需要改改若依的源碼了:
1、先改面包屑
?2、在頁面容器中,打開新的標簽頁,改標簽頁標題(把要修改文件和修改內(nèi)容框出來,有個明顯的對比,知道改哪里)
?
?最后在新頁面取出參數(shù)
最后效果
有bug
也是寫完上面的內(nèi)容以后,才發(fā)現(xiàn)有bug,路徑一樣,參數(shù)不一樣的標簽,去單擊的時候,沒有刷新內(nèi)容,而是保留第一次點擊的標簽的頁面。。。如圖
bug解決
原因:若依vue前端源碼中用的<router-link>標簽進行頁面跳轉(zhuǎn),因為路徑一樣,參數(shù)不一樣的頁面本質(zhì)上都是同一個vue,而這個vue已經(jīng)加載出來就不會進行銷毀重新加載了,所以我們要做的就是監(jiān)聽參數(shù)然后重新渲染,達到刷新頁面的效果
在自己的跳轉(zhuǎn)頁面vue中監(jiān)聽路由參數(shù):
二級路由效果
三級路由效果
ps: 找到更好的寫法就又補充了一下,所以截圖上有些不統(tǒng)一,記得看字看描述哈!?
修改過的若依代碼
ruoyi 3.8.3
src\components\Breadcrumb\index.vue
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">
<!-- {{ item.meta.title }} -->
{{item.metaTitle ? item.metaTitle : item.meta.title }}
</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
export default {
data() {
return {
levelList: null
}
},
watch: {
$route(route) {
// if you go to the redirect page, do not update the breadcrumbs
if (route.path.startsWith('/redirect/')) {
return
}
this.getBreadcrumb()
}
},
created() {
this.getBreadcrumb()
},
methods: {
getBreadcrumb() {
// only show routes with meta.title
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
matched.forEach(element => {
if(element.path == this.$route.path ) {
if(this.$route.query.metaTitle) {
element.metaTitle = this.$route.query.metaTitle
}
}
});
const first = matched[0]
if (!this.isDashboard(first)) {
matched = [{ path: '/index', meta: { title: '首頁' }}].concat(matched)
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
},
isDashboard(route) {
const name = route && route.name
if (!name) {
return false
}
return name.trim() === 'Index'
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(path)
}
}
}
</script>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>
src\layout\components\TagsView\index.vue
?
文章來源:http://www.zghlxwxcb.cn/news/detail-792366.html
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
<router-link
v-for="tag in visitedViews"
ref="tag"
:key="tag.fullPath"
:class="isActive(tag)?'active':''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
@contextmenu.prevent.native="openMenu(tag,$event)"
>
{{ tag.title }}
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)"><i class="el-icon-refresh-right"></i> 刷新頁面</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><i class="el-icon-close"></i> 關(guān)閉當前</li>
<li @click="closeOthersTags"><i class="el-icon-circle-close"></i> 關(guān)閉其他</li>
<li v-if="!isFirstView()" @click="closeLeftTags"><i class="el-icon-back"></i> 關(guān)閉左側(cè)</li>
<li v-if="!isLastView()" @click="closeRightTags"><i class="el-icon-right"></i> 關(guān)閉右側(cè)</li>
<li @click="closeAllTags(selectedTag)"><i class="el-icon-circle-close"></i> 全部關(guān)閉</li>
</ul>
</div>
</template>
<script>
import ScrollPane from './ScrollPane'
import path from 'path'
export default {
components: { ScrollPane },
data() {
return {
visible: false,
top: 0,
left: 0,
selectedTag: {},
affixTags: []
}
},
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
routes() {
return this.$store.state.permission.routes
},
theme() {
return this.$store.state.settings.theme;
}
},
watch: {
$route() {
this.addTags()
this.moveToCurrentTag()
},
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
}
},
mounted() {
this.initTags()
this.addTags()
},
methods: {
isActive(route) {
// return route.path === this.$route.path
return route.fullPath === this.$route.fullPath
},
activeStyle(tag) {
if (!this.isActive(tag)) return {};
return {
"background-color": this.theme,
"border-color": this.theme
};
},
isAffix(tag) {
return tag.meta && tag.meta.affix
},
isFirstView() {
try {
return this.selectedTag.fullPath === this.visitedViews[1].fullPath || this.selectedTag.fullPath === '/index'
} catch (err) {
return false
}
},
isLastView() {
try {
return this.selectedTag.fullPath === this.visitedViews[this.visitedViews.length - 1].fullPath
} catch (err) {
return false
}
},
filterAffixTags(routes, basePath = '/') {
let tags = []
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTags = this.filterAffixTags(route.children, route.path)
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
}
}
})
return tags
},
initTags() {
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
for (const tag of affixTags) {
// Must have tag name
if (tag.name) {
// this.$store.dispatch('tagsView/addVisitedView', tag)
}
}
},
addTags() {
const { name } = this.$route
if (name) {
this.$store.dispatch('tagsView/addView', this.$route)
}
return false
},
moveToCurrentTag() {
const tags = this.$refs.tag
this.$nextTick(() => {
for (const tag of tags) {
if (tag.to.path === this.$route.path) {
if(tag.to.query.metaTitle) {
if(tag.to.query.metaTitle === this.$route.query.metaTitle) {
this.$refs.scrollPane.moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
}
}
} else {
this.$refs.scrollPane.moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
}
}
break
}
}
})
},
refreshSelectedTag(view) {
this.$tab.refreshPage(view);
},
closeSelectedTag(view) {
this.$tab.closePage(view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
}
})
},
closeRightTags() {
this.$tab.closeRightPage(this.selectedTag).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
this.toLastView(visitedViews)
}
})
},
closeLeftTags() {
this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
this.toLastView(visitedViews)
}
})
},
closeOthersTags() {
this.$router.push(this.selectedTag).catch(()=>{});
this.$tab.closeOtherPage(this.selectedTag).then(() => {
this.moveToCurrentTag()
})
},
closeAllTags(view) {
this.$tab.closeAllPage().then(({ visitedViews }) => {
if (this.affixTags.some(tag => tag.path === this.$route.path)) {
return
}
this.toLastView(visitedViews, view)
})
},
toLastView(visitedViews, view) {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
this.$router.push(latestView.fullPath)
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
this.$router.replace({ path: '/redirect' + view.fullPath })
} else {
this.$router.push('/')
}
}
},
openMenu(tag, e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 15 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
this.top = e.clientY
this.visible = true
this.selectedTag = tag
},
closeMenu() {
this.visible = false
},
handleScroll() {
this.closeMenu()
}
}
}
</script>
<style lang="scss" scoped>
.tags-view-container {
height: 34px;
width: 100%;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
.tags-view-wrapper {
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
background-color: #42b983;
color: #fff;
border-color: #42b983;
&::before {
content: '';
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
</style>
<style lang="scss">
//reset element css of el-icon-close
.tags-view-wrapper {
.tags-view-item {
.el-icon-close {
width: 16px;
height: 16px;
vertical-align: 2px;
border-radius: 50%;
text-align: center;
transition: all .3s cubic-bezier(.645, .045, .355, 1);
transform-origin: 100% 50%;
&:before {
transform: scale(.6);
display: inline-block;
vertical-align: -3px;
}
&:hover {
background-color: #b4bccc;
color: #fff;
}
}
}
}
</style>
src\store\modules\tagsView.js
文章來源地址http://www.zghlxwxcb.cn/news/detail-792366.html
const state = {
visitedViews: [],
cachedViews: []
}
const mutations = {
// ADD_VISITED_VIEW: (state, view) => {
// if (state.visitedViews.some(v => v.path === view.path)) return
// state.visitedViews.push(
// Object.assign({}, view, {
// title: view.meta.title || 'no-name'
// })
// )
// },
ADD_VISITED_VIEW: (state, view) => {
if (state.visitedViews.some(v => v.path === view.path)) {
// 路徑一樣,因為參數(shù)不同,打開新的頁面
if(view.query && view.query.metaTitle) {
let list = state.visitedViews.filter(v => v.query && v.query.metaTitle && v.query.metaTitle == view.query.metaTitle)
if(list.length > 0) {
return
}
} else {
return
}
}
state.visitedViews.push(
Object.assign({}, view, {
title: view.query && view.query.metaTitle ? view.query.metaTitle:(view.meta.title || 'no-name')
})
)
},
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return
if (view.meta && !view.meta.noCache) {
state.cachedViews.push(view.name)
}
},
DEL_VISITED_VIEW: (state, view) => {
for (const [i, v] of state.visitedViews.entries()) {
if (v.path === view.path) {
state.visitedViews.splice(i, 1)
break
}
}
},
DEL_CACHED_VIEW: (state, view) => {
const index = state.cachedViews.indexOf(view.name)
index > -1 && state.cachedViews.splice(index, 1)
},
DEL_OTHERS_VISITED_VIEWS: (state, view) => {
state.visitedViews = state.visitedViews.filter(v => {
return v.meta.affix || v.path === view.path
})
},
DEL_OTHERS_CACHED_VIEWS: (state, view) => {
const index = state.cachedViews.indexOf(view.name)
if (index > -1) {
state.cachedViews = state.cachedViews.slice(index, index + 1)
} else {
state.cachedViews = []
}
},
DEL_ALL_VISITED_VIEWS: state => {
// keep affix tags
const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
state.visitedViews = affixTags
},
DEL_ALL_CACHED_VIEWS: state => {
state.cachedViews = []
},
UPDATE_VISITED_VIEW: (state, view) => {
for (let v of state.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
},
DEL_RIGHT_VIEWS: (state, view) => {
const index = state.visitedViews.findIndex(v => v.path === view.path)
if (index === -1) {
return
}
state.visitedViews = state.visitedViews.filter((item, idx) => {
if (idx <= index || (item.meta && item.meta.affix)) {
return true
}
const i = state.cachedViews.indexOf(item.name)
if (i > -1) {
state.cachedViews.splice(i, 1)
}
return false
})
},
DEL_LEFT_VIEWS: (state, view) => {
const index = state.visitedViews.findIndex(v => v.path === view.path)
if (index === -1) {
return
}
state.visitedViews = state.visitedViews.filter((item, idx) => {
if (idx >= index || (item.meta && item.meta.affix)) {
return true
}
const i = state.cachedViews.indexOf(item.name)
if (i > -1) {
state.cachedViews.splice(i, 1)
}
return false
})
}
}
const actions = {
addView({ dispatch }, view) {
dispatch('addVisitedView', view)
dispatch('addCachedView', view)
},
addVisitedView({ commit }, view) {
commit('ADD_VISITED_VIEW', view)
},
addCachedView({ commit }, view) {
commit('ADD_CACHED_VIEW', view)
},
delView({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delVisitedView', view)
dispatch('delCachedView', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delVisitedView({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_VISITED_VIEW', view)
resolve([...state.visitedViews])
})
},
delCachedView({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_CACHED_VIEW', view)
resolve([...state.cachedViews])
})
},
delOthersViews({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delOthersVisitedViews', view)
dispatch('delOthersCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delOthersVisitedViews({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_VISITED_VIEWS', view)
resolve([...state.visitedViews])
})
},
delOthersCachedViews({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_CACHED_VIEWS', view)
resolve([...state.cachedViews])
})
},
delAllViews({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delAllVisitedViews', view)
dispatch('delAllCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delAllVisitedViews({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_VISITED_VIEWS')
resolve([...state.visitedViews])
})
},
delAllCachedViews({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_CACHED_VIEWS')
resolve([...state.cachedViews])
})
},
updateVisitedView({ commit }, view) {
commit('UPDATE_VISITED_VIEW', view)
},
delRightTags({ commit }, view) {
return new Promise(resolve => {
commit('DEL_RIGHT_VIEWS', view)
resolve([...state.visitedViews])
})
},
delLeftTags({ commit }, view) {
return new Promise(resolve => {
commit('DEL_LEFT_VIEWS', view)
resolve([...state.visitedViews])
})
},
}
export default {
namespaced: true,
state,
mutations,
actions
}
到了這里,關(guān)于若依 vue前端 動態(tài)設置路由path不同參數(shù) 在頁面容器里打開新頁面(新路由),面包屑和標簽頁標題根據(jù)參數(shù)動態(tài)改變,面包屑多級標題,側(cè)邊欄對應菜單亮起的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!