update
This commit is contained in:
parent
f4eaebdb41
commit
8da758f494
@ -23,7 +23,7 @@ export default [
|
||||
data: {
|
||||
id: 1,
|
||||
name: 'zhangsan',
|
||||
role: 'visitor',
|
||||
'role|1': ['admin', 'visitor'], // 随机返回一个角色admin或visitor
|
||||
avatar: "@image('48x48', '#fb0a2a')"
|
||||
}
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
$mainColor: #409eff; // 网站主题色
|
||||
|
||||
// 菜单配置
|
||||
// 侧边栏
|
||||
$menuBg: #304156; // 菜单背景颜色
|
||||
$menuTextColor: #fff; // 菜单文字颜色
|
||||
$menuActiveTextColor: $mainColor; // 已选中菜单文字颜色
|
||||
|
||||
@ -1,16 +1,9 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<router-view v-slot="{ Component }">
|
||||
|
||||
<keep-alive :include="cacheList">
|
||||
<component
|
||||
:is="Component"
|
||||
:key="key"
|
||||
/>
|
||||
<component :is="Component" :key="key" />
|
||||
</keep-alive>
|
||||
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { computed, defineComponent } from "vue";
|
||||
@ -22,7 +15,7 @@ export default defineComponent({
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const cacheList = computed(() => store.state.tags.cacheList);
|
||||
const key = computed(() => route.path);
|
||||
const key = computed(() => route.fullPath);
|
||||
|
||||
return {
|
||||
cacheList,
|
||||
@ -31,12 +24,3 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main {
|
||||
flex: 1;
|
||||
background: #f0f2f5;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@ -10,13 +10,7 @@
|
||||
:text-color="variables.menuTextColor"
|
||||
:active-text-color="variables.menuActiveTextColor"
|
||||
>
|
||||
|
||||
<submenu
|
||||
v-for="menu in menus"
|
||||
:key="menu.url"
|
||||
:menu="menu"
|
||||
/>
|
||||
|
||||
<submenu v-for="menu in menus" :key="menu.url" :menu="menu" />
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div
|
||||
class="left"
|
||||
:class="{collapse:collapse}"
|
||||
:class="{ collapse: collapse, mobile: device === 'mobile' }"
|
||||
>
|
||||
<logo />
|
||||
<menus :collapse="collapse" />
|
||||
</div>
|
||||
<div class="mask" @click="closeSidebar"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -22,9 +23,16 @@ export default defineComponent({
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const collapse = computed(() => !!store.state.app.sidebar.collapse);
|
||||
const device = computed(() => store.state.app.device);
|
||||
|
||||
const closeSidebar = () => {
|
||||
store.commit("app/setCollapse", 1);
|
||||
};
|
||||
|
||||
return {
|
||||
collapse,
|
||||
device,
|
||||
closeSidebar,
|
||||
};
|
||||
},
|
||||
});
|
||||
@ -44,5 +52,27 @@ export default defineComponent({
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.mobile {
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
& + .mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 9;
|
||||
}
|
||||
&.collapse {
|
||||
transform: translateX(-100%);
|
||||
& + .mask {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<el-scrollbar
|
||||
ref="scrollContainer"
|
||||
:vertical="false"
|
||||
class="scroll-container"
|
||||
>
|
||||
<slot />
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scroll-container {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
::v-deep(.el-scrollbar__bar) {
|
||||
bottom: 0px;
|
||||
}
|
||||
::v-deep(.el-scrollbar__wrap) {
|
||||
height: 49px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
81
src/layout/components/Tagsbar/hooks/useContextMenu.js
Normal file
81
src/layout/components/Tagsbar/hooks/useContextMenu.js
Normal file
@ -0,0 +1,81 @@
|
||||
import { onMounted, onBeforeUnmount, reactive, toRefs, nextTick } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex'
|
||||
import { isAffix } from './useTags'
|
||||
|
||||
export const useContextMenu = (tagList) => {
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
top: 0,
|
||||
left: 0,
|
||||
selectedTag: {},
|
||||
openMenu (tag, e) {
|
||||
state.visible = true;
|
||||
state.left = e.clientX;
|
||||
state.top = e.clientY;
|
||||
state.selectedTag = tag;
|
||||
},
|
||||
closeMenu () {
|
||||
state.visible = false;
|
||||
},
|
||||
refreshSelectedTag (tag) {
|
||||
store.dispatch("tags/delCacheList", tag).then(() => {
|
||||
const { fullPath } = tag;
|
||||
nextTick(() => {
|
||||
router.replace({
|
||||
path: "/redirect" + fullPath,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
closeTag (tag) {
|
||||
if (isAffix(tag)) return;
|
||||
|
||||
const closedTagIndex = tagList.value.findIndex(
|
||||
(item) => item.fullPath === tag.fullPath
|
||||
);
|
||||
store.dispatch("tags/delTag", tag).then(() => {
|
||||
if (isActive(tag)) {
|
||||
toLastTag(closedTagIndex - 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
closeOtherTags () {
|
||||
store.dispatch("tags/delOtherTags", state.selectedTag).then(() => {
|
||||
router.push(state.selectedTag);
|
||||
});
|
||||
},
|
||||
closeAllTags () {
|
||||
store.dispatch("tags/delAllTags").then(() => {
|
||||
router.push("/");
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const isActive = (tag) => {
|
||||
return tag.fullPath === route.fullPath;
|
||||
}
|
||||
|
||||
const toLastTag = (lastTagIndex) => {
|
||||
const lastTag = tagList.value[lastTagIndex];
|
||||
if (!!lastTag) {
|
||||
router.push(lastTag.fullPath);
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("click", state.closeMenu);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("click", state.closeMenu);
|
||||
});
|
||||
|
||||
return toRefs(state)
|
||||
}
|
||||
39
src/layout/components/Tagsbar/hooks/useScrollbar.js
Normal file
39
src/layout/components/Tagsbar/hooks/useScrollbar.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useScrollbar = (tagsItem) => {
|
||||
const scrollContainer = ref(null);
|
||||
|
||||
const handleScroll = (e) => {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY;
|
||||
scrollContainer.value.wrap.scrollLeft -= eventDelta / 4;
|
||||
};
|
||||
|
||||
|
||||
const moveToTarget = (currentTag) => {
|
||||
const containerWidth = scrollContainer.value.scrollbar.offsetWidth;
|
||||
const scrollWrapper = scrollContainer.value.wrap;
|
||||
const tagList = tagsItem.value;
|
||||
|
||||
let firstTag = null;
|
||||
let lastTag = null;
|
||||
|
||||
if (tagList.length > 0) {
|
||||
firstTag = tagList[0];
|
||||
lastTag = tagList[tagList.length - 1];
|
||||
}
|
||||
if (firstTag === currentTag) {
|
||||
scrollWrapper.scrollLeft = 0;
|
||||
} else if (lastTag === currentTag) {
|
||||
scrollWrapper.scrollLeft = scrollWrapper.scrollWidth - containerWidth;
|
||||
} else {
|
||||
const el = currentTag.$el.nextElementSibling
|
||||
scrollWrapper.scrollLeft = el.offsetLeft + el.offsetWidth > containerWidth ? el.offsetLeft - el.offsetWidth : 0
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
scrollContainer,
|
||||
handleScroll,
|
||||
moveToTarget,
|
||||
};
|
||||
}
|
||||
94
src/layout/components/Tagsbar/hooks/useTags.js
Normal file
94
src/layout/components/Tagsbar/hooks/useTags.js
Normal file
@ -0,0 +1,94 @@
|
||||
import { useScrollbar } from "./useScrollbar";
|
||||
import { onMounted, watch, computed, ref, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
export const isAffix = (tag) => {
|
||||
return !!tag.meta && !!tag.meta.affix;
|
||||
};
|
||||
|
||||
export const useTags = () => {
|
||||
const store = useStore()
|
||||
const router = useRouter();
|
||||
const route = router.currentRoute;
|
||||
const routes = computed(() => router.getRoutes());
|
||||
const tagList = computed(() => store.state.tags.tagList);
|
||||
|
||||
const tagsItem = ref([])
|
||||
|
||||
const setItemRef = (i, el) => {
|
||||
tagsItem.value[i] = el
|
||||
}
|
||||
|
||||
const scrollbar = useScrollbar(tagsItem);
|
||||
|
||||
watch(() => tagList.value.length, () => {
|
||||
tagsItem.value = []
|
||||
})
|
||||
|
||||
|
||||
const filterAffixTags = (routes) => {
|
||||
return routes.filter((route) => isAffix(route));
|
||||
};
|
||||
|
||||
const initTags = () => {
|
||||
const affixTags = filterAffixTags(routes.value);
|
||||
|
||||
for (const tag of affixTags) {
|
||||
if (!!tag.name) {
|
||||
store.dispatch("tags/addTagList", tag);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addTag = () => {
|
||||
const tag = route.value;
|
||||
if (!!tag.name && tag.matched[0].components.default.name === "layout") {
|
||||
store.dispatch("tags/addTag", tag);
|
||||
}
|
||||
};
|
||||
|
||||
const saveActivePosition = (tag) => {
|
||||
const index = tagList.value.findIndex(
|
||||
(item) => item.fullPath === tag.fullPath
|
||||
);
|
||||
|
||||
store.dispatch("tags/saveActivePosition", Math.max(0, index));
|
||||
};
|
||||
|
||||
const moveToCurrentTag = () => {
|
||||
nextTick(() => {
|
||||
for (const tag of tagsItem.value) {
|
||||
if (!!tag && (tag.to.path === route.value.path)) {
|
||||
scrollbar.moveToTarget(tag);
|
||||
|
||||
if (tag.to.fullPath !== route.value.fullPath) {
|
||||
store.dispatch("tags/updateTagList", route.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
initTags();
|
||||
addTag();
|
||||
moveToCurrentTag();
|
||||
});
|
||||
|
||||
watch(route, (newRoute, oldRoute) => {
|
||||
saveActivePosition(oldRoute); // 保存标签的位置
|
||||
addTag();
|
||||
moveToCurrentTag();
|
||||
});
|
||||
|
||||
return {
|
||||
tagList,
|
||||
setItemRef,
|
||||
isAffix,
|
||||
...scrollbar
|
||||
}
|
||||
}
|
||||
@ -1,251 +1,93 @@
|
||||
<template>
|
||||
<div
|
||||
id="tags-view-container"
|
||||
class="tags-view-container"
|
||||
>
|
||||
<scroll-bar
|
||||
ref="scrollBar"
|
||||
class="tags-view-wrapper"
|
||||
@scroll="handleScroll"
|
||||
<div class="tags-container">
|
||||
<el-scrollbar
|
||||
ref="scrollContainer"
|
||||
:vertical="false"
|
||||
class="scroll-container"
|
||||
@wheel.prevent="onScroll"
|
||||
>
|
||||
<router-link
|
||||
v-for="tag in tagList"
|
||||
ref="tags"
|
||||
:key="tag.path"
|
||||
:class="isActive(tag)?'active':''"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
tag="span"
|
||||
class="tags-view-item"
|
||||
@click.middle="!isAffix(tag)?closeSelectedTag(tag):''"
|
||||
v-for="(tag, i) in tagList"
|
||||
:key="tag.fullPath"
|
||||
:to="tag"
|
||||
:ref="(el) => setItemRef(i, el)"
|
||||
custom
|
||||
v-slot="{ navigate, isExactActive }"
|
||||
>
|
||||
<div
|
||||
class="tags-item"
|
||||
:class="isExactActive ? 'active' : ''"
|
||||
@click="navigate"
|
||||
@click.middle="closeTag(tag)"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
{{ tag.title }}
|
||||
<span
|
||||
v-if="!isAffix(tag)"
|
||||
class="el-icon-refresh"
|
||||
@click.prevent.stop="refreshSelectedTag(tag)"
|
||||
/>
|
||||
<span class="title">{{ tag.title }}</span>
|
||||
<span
|
||||
v-if="!isAffix(tag)"
|
||||
class="el-icon-close"
|
||||
@click.prevent.stop="closeSelectedTag(tag)"
|
||||
@click.prevent.stop="closeTag(tag)"
|
||||
/>
|
||||
</div>
|
||||
</router-link>
|
||||
</scroll-bar>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<ul
|
||||
v-show="visible"
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
class="contextmenu"
|
||||
>
|
||||
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
|
||||
<li
|
||||
v-if="!isAffix(selectedTag)"
|
||||
@click="closeSelectedTag(selectedTag)"
|
||||
>关闭</li>
|
||||
<li @click="closeOthersTags">关闭其他</li>
|
||||
<li @click="closeAllTags(selectedTag)">关闭全部</li>
|
||||
<li v-if="!isAffix(selectedTag)" @click="closeTag(selectedTag)">关闭</li>
|
||||
<li @click="closeOtherTags">关闭其他</li>
|
||||
<li @click="closeAllTags">关闭全部</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScrollBar from "./ScrollBar.vue";
|
||||
import path from "path";
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
watch,
|
||||
toRefs,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRouter } from "vue-router";
|
||||
import { defineComponent } from "vue";
|
||||
import { useTags } from "./hooks/useTags";
|
||||
import { useContextMenu } from "./hooks/useContextMenu";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Tagsbar",
|
||||
components: { ScrollBar },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const route = router.currentRoute;
|
||||
const tags = useTags();
|
||||
const contextMenu = useContextMenu(tags.tagList);
|
||||
|
||||
const tags = ref(null);
|
||||
const scrollBar = ref(null);
|
||||
|
||||
const tagList = computed(() => store.state.tags.tagList);
|
||||
const routes = computed(() => router.getRoutes());
|
||||
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
top: 0,
|
||||
left: 0,
|
||||
selectedTag: {},
|
||||
affixTags: [],
|
||||
isReload: false,
|
||||
isActive(tag) {
|
||||
return tag.path === route.value.path;
|
||||
},
|
||||
isAffix(tag) {
|
||||
return !!tag.meta && !!tag.meta.affix;
|
||||
},
|
||||
filterAffixTags(routes) {
|
||||
return routes.filter((route) => !!route.meta && !!route.meta.affix);
|
||||
},
|
||||
initTags() {
|
||||
const affixTags = (state.affixTags = state.filterAffixTags(
|
||||
routes.value
|
||||
));
|
||||
for (const tag of affixTags) {
|
||||
// Must have tag name
|
||||
if (!!tag.name) {
|
||||
store.dispatch("tags/addTagList", tag);
|
||||
}
|
||||
}
|
||||
},
|
||||
closeTag(tag) {
|
||||
store.dispatch("tags/delTag", tag);
|
||||
},
|
||||
addTags() {
|
||||
if (route.value.name) {
|
||||
store.dispatch("tags/addTag", route.value);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
saveActivePosition(oldRoute) {
|
||||
const index = tagList.value.findIndex(
|
||||
(item) => item.fullPath === oldRoute.fullPath
|
||||
);
|
||||
|
||||
store.dispatch("tags/saveActivePosition", Math.max(0, index));
|
||||
},
|
||||
moveToCurrentTag(callback) {
|
||||
nextTick(() => {
|
||||
for (const tag of tagList.value) {
|
||||
if (tag.path === route.value.path) {
|
||||
// scrollBar.value.moveToTarget(tag);
|
||||
|
||||
if (tag.fullPath !== route.value.fullPath) {
|
||||
store.dispatch("tags/updateTagList", route.value);
|
||||
}
|
||||
callback && callback();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
reload() {
|
||||
// this.refreshSelectedTag(this.$route)
|
||||
state.isReload = true;
|
||||
},
|
||||
refreshSelectedTag(tag) {
|
||||
store.dispatch("tags/delCacheList", tag).then(() => {
|
||||
const { fullPath } = tag;
|
||||
nextTick(() => {
|
||||
router.replace({
|
||||
path: "/redirect" + fullPath,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
closeSelectedTag(tag) {
|
||||
const closedTagIndex = tagList.value.findIndex(
|
||||
(item) => item.fullPath === tag.fullPath
|
||||
);
|
||||
store.dispatch("tags/delTag", tag).then(({ tagList }) => {
|
||||
if (state.isActive(tag)) {
|
||||
state.toLastView(tagList, tag, closedTagIndex - 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
closeOthersTags() {
|
||||
router.push(state.selectedTag);
|
||||
store.dispatch("tags/delOtherTags", state.selectedTag).then(() => {
|
||||
state.moveToCurrentTag();
|
||||
});
|
||||
},
|
||||
closeAllTags(view) {
|
||||
const closedTagIndex = tagList.value.findIndex(
|
||||
(item) => item.fullPath === view.fullPath
|
||||
);
|
||||
store.dispatch("tags/delAllTags").then(({ tagList }) => {
|
||||
if (state.affixTags.some((tag) => tag.path === view.path)) {
|
||||
return;
|
||||
}
|
||||
state.toLastView(tagList, view, closedTagIndex - 1);
|
||||
});
|
||||
},
|
||||
toLastView(tagList, view, lastTagIndex) {
|
||||
const lastTag = tagList[lastTagIndex];
|
||||
if (!!lastTag) {
|
||||
router.push(lastTag.fullPath);
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
},
|
||||
openMenu(tag, e) {
|
||||
state.left = e.clientX;
|
||||
state.top = e.clientY;
|
||||
state.visible = true;
|
||||
state.selectedTag = tag;
|
||||
},
|
||||
closeMenu() {
|
||||
state.visible = false;
|
||||
},
|
||||
handleScroll() {
|
||||
state.closeMenu();
|
||||
},
|
||||
});
|
||||
|
||||
watch(route, (newRoute, oldRoute) => {
|
||||
console.log("监听路由", newRoute, oldRoute);
|
||||
state.saveActivePosition(oldRoute); // 保存上一个tag页签的位置
|
||||
state.addTags();
|
||||
state.moveToCurrentTag(() => {
|
||||
if (state.isReload) {
|
||||
state.isReload = false;
|
||||
state.refreshSelectedTag(this.$route);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
state.initTags();
|
||||
state.addTags();
|
||||
document.addEventListener("click", state.closeMenu);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("click", state.closeMenu);
|
||||
});
|
||||
const onScroll = (e) => {
|
||||
tags.handleScroll(e);
|
||||
contextMenu.closeMenu.value();
|
||||
};
|
||||
|
||||
return {
|
||||
tagList,
|
||||
routes,
|
||||
tags,
|
||||
scrollBar,
|
||||
...toRefs(state),
|
||||
onScroll,
|
||||
...tags,
|
||||
...contextMenu,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tags-view-container {
|
||||
height: 34px;
|
||||
.tags-container {
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #d8dce5;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
|
||||
.scroll-container {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
::v-deep(.el-scrollbar__bar) {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
line-height: 31px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
box-sizing: border-box;
|
||||
border-left: 1px solid #e6e6e6;
|
||||
border-right: 1px solid #e6e6e6;
|
||||
color: #5c5c5c;
|
||||
@ -253,6 +95,8 @@ export default defineComponent({
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
margin-left: -1px;
|
||||
vertical-align: bottom;
|
||||
cursor: pointer;
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
@ -260,50 +104,19 @@ export default defineComponent({
|
||||
margin-right: 15px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #f6f7f6;
|
||||
color: #333;
|
||||
border-color: #f6f7f6;
|
||||
border-top: 2px solid #333;
|
||||
border-left: 1px solid #e6e6e6;
|
||||
// &::before {
|
||||
// content: '';
|
||||
// width: 8px;
|
||||
// height: 8px;
|
||||
// position: relative;
|
||||
// }
|
||||
color: $mainColor;
|
||||
border-bottom: 2px solid $mainColor;
|
||||
}
|
||||
.title {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
.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, 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,
|
||||
.el-icon-refresh {
|
||||
.el-icon-close {
|
||||
color: #5c5c5c;
|
||||
margin-left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@ -324,4 +137,26 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: fixed;
|
||||
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, 0.3);
|
||||
white-space: nowrap;
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
<el-breadcrumb
|
||||
separator="/"
|
||||
class="breadcrumb"
|
||||
:class="{ mobile: device === 'mobile' }"
|
||||
>
|
||||
<el-breadcrumb-item
|
||||
v-for="(item, index) in breadcrumbs"
|
||||
@ -14,11 +15,14 @@
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent, ref, onBeforeMount, watch } from "vue";
|
||||
import { defineComponent, computed, ref, onBeforeMount, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const device = computed(() => store.state.app.device);
|
||||
const router = useRouter();
|
||||
const route = router.currentRoute; // 这里不使用useRoute获取当前路由,否则下面watch监听路由的时候会有警告
|
||||
const breadcrumbs = ref([]);
|
||||
@ -45,6 +49,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
return {
|
||||
device,
|
||||
breadcrumbs,
|
||||
};
|
||||
},
|
||||
@ -54,15 +59,23 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.breadcrumb {
|
||||
margin-left: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
::v-deep(a),
|
||||
::v-deep(.is-link) {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
::v-deep(.el-breadcrumb__item) {
|
||||
float: none;
|
||||
}
|
||||
.no_link {
|
||||
::v-deep(.el-breadcrumb__inner) {
|
||||
color: #97a8be !important;
|
||||
}
|
||||
}
|
||||
&.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -8,6 +8,7 @@
|
||||
<script>
|
||||
import { defineComponent, computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const store = useStore();
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<el-dropdown>
|
||||
<el-dropdown trigger="click">
|
||||
<div class="userinfo">
|
||||
<template v-if="!userinfo">
|
||||
<i class="el-icon-user" /> admin
|
||||
<i class="el-icon-user" />
|
||||
admin
|
||||
</template>
|
||||
<template v-else>
|
||||
<img
|
||||
class="avatar"
|
||||
:src="userinfo.avatar"
|
||||
/> {{userinfo.name}}
|
||||
<img class="avatar" :src="userinfo.avatar" />
|
||||
{{ userinfo.name }}
|
||||
</template>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="header">
|
||||
<div class="navigation">
|
||||
<logo v-if="device === 'mobile'" class="mobile" />
|
||||
<hamburger />
|
||||
<breadcrumbs />
|
||||
</div>
|
||||
@ -10,17 +11,28 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { defineComponent, computed } from "vue";
|
||||
import Logo from "@/layout/components/Sidebar/Logo.vue";
|
||||
import Hamburger from "./Hamburger.vue";
|
||||
import Breadcrumbs from "./Breadcrumbs.vue";
|
||||
import Userinfo from "./Userinfo.vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Logo,
|
||||
Hamburger,
|
||||
Breadcrumbs,
|
||||
Userinfo,
|
||||
},
|
||||
setup() {},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const device = computed(() => store.state.app.device);
|
||||
|
||||
return {
|
||||
device,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@ -32,6 +44,17 @@ export default defineComponent({
|
||||
.navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.mobile {
|
||||
padding-right: 0;
|
||||
::v-deep(.logo) {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
::v-deep(.title) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
46
src/layout/hooks/useResizeHandler.js
Normal file
46
src/layout/hooks/useResizeHandler.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { onBeforeMount, onBeforeUnmount, watch } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { useStore } from "vuex"
|
||||
|
||||
const WIDTH = 768
|
||||
export const useResizeHandler = () => {
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const route = router.currentRoute
|
||||
|
||||
const isMobile = () => {
|
||||
return window.innerWidth < WIDTH
|
||||
}
|
||||
|
||||
const resizeHandler = () => {
|
||||
if (isMobile()) {
|
||||
store.commit('app/setDevice', 'mobile')
|
||||
store.commit('app/setCollapse', 1)
|
||||
} else {
|
||||
store.commit('app/setDevice', 'desktop')
|
||||
store.commit('app/setCollapse', 0)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (isMobile()) {
|
||||
store.commit('app/setDevice', 'mobile')
|
||||
store.commit('app/setCollapse', 1)
|
||||
}
|
||||
window.addEventListener('resize', resizeHandler)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeHandler)
|
||||
})
|
||||
|
||||
// // 监听路由的时候不能使用useRoute获取路由,否则会有警告
|
||||
// watch(route, () => {
|
||||
// if (store.state.app.device === 'mobile' && !store.state.app.sidebar.collapse) {
|
||||
// store.commit('app/setCollapse', 1)
|
||||
// }
|
||||
// })
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -6,9 +6,11 @@
|
||||
<topbar />
|
||||
<tagsbar />
|
||||
</div>
|
||||
<div class="main">
|
||||
<content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
@ -16,14 +18,19 @@ import Sidebar from "./components/Sidebar/index.vue";
|
||||
import Topbar from "./components/Topbar/index.vue";
|
||||
import Tagsbar from "./components/Tagsbar/index.vue";
|
||||
import Content from "./components/Content/index.vue";
|
||||
import { useResizeHandler } from "./hooks/useResizeHandler";
|
||||
|
||||
export default defineComponent({
|
||||
name: "layout",
|
||||
components: {
|
||||
Sidebar,
|
||||
Topbar,
|
||||
Tagsbar,
|
||||
Content,
|
||||
},
|
||||
setup() {
|
||||
useResizeHandler();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -34,11 +41,18 @@ export default defineComponent({
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.top {
|
||||
background: #fff;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
background: #f0f2f5;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,7 @@
|
||||
// index.js
|
||||
import { createRouter, createWebHashHistory } from "vue-router"
|
||||
|
||||
import redirect from './modules/redirect'
|
||||
import error from './modules/error'
|
||||
import login from './modules/login'
|
||||
import home from './modules/home'
|
||||
@ -20,10 +21,18 @@ const router = createRouter({
|
||||
path: '/',
|
||||
redirect: '/home',
|
||||
},
|
||||
...redirect, // 统一的重定向配置
|
||||
...login,
|
||||
...allMenus,
|
||||
...error
|
||||
],
|
||||
scrollBehavior (to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
} else {
|
||||
return { left: 0, top: 0 }
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -1,16 +1,17 @@
|
||||
import layout from '@/layout/index.vue'
|
||||
const Layout = () => import('@/layout/index.vue')
|
||||
const Error = () => import("@/views/error/index.vue");
|
||||
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/error',
|
||||
component: layout,
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '403',
|
||||
name: 'error-forbidden',
|
||||
component: Error,
|
||||
meta: { title: '403' },
|
||||
props: {
|
||||
error: '403'
|
||||
}
|
||||
@ -19,6 +20,7 @@ export default [
|
||||
path: '500',
|
||||
name: 'error-server-error',
|
||||
component: Error,
|
||||
meta: { title: '500' },
|
||||
props: {
|
||||
error: '500'
|
||||
}
|
||||
@ -27,6 +29,7 @@ export default [
|
||||
path: '404',
|
||||
name: 'error-not-found',
|
||||
component: Error,
|
||||
meta: { title: '404' },
|
||||
props: {
|
||||
error: '404'
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
// home.js
|
||||
import layout from '@/layout/index.vue'
|
||||
const Layout = () => import('@/layout/index.vue')
|
||||
const Home = () => import("@/views/home/index.vue");
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/home',
|
||||
component: layout,
|
||||
component: Layout,
|
||||
name: "Dashboard",
|
||||
meta: {
|
||||
title: "Dashboard",
|
||||
@ -18,6 +18,7 @@ export default [
|
||||
component: Home,
|
||||
meta: {
|
||||
title: "首页",
|
||||
affix: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
8
src/router/modules/redirect.js
Normal file
8
src/router/modules/redirect.js
Normal file
@ -0,0 +1,8 @@
|
||||
const Redirect = () => import("@/views/redirect/index.vue");
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/redirect/:path(.*)',
|
||||
component: Redirect,
|
||||
}
|
||||
]
|
||||
@ -1,15 +1,18 @@
|
||||
import layout from '@/layout/index.vue'
|
||||
const Layout = () => import('@/layout/index.vue')
|
||||
const List = () => import("@/views/test/index.vue");
|
||||
const Add = () => import("@/views/test/Add.vue");
|
||||
const Auth = () => import("@/views/test/Auth.vue");
|
||||
const NoAuth = () => import("@/views/test/NoAuth.vue");
|
||||
const Nest = () => import("@/views/test/Nest.vue");
|
||||
const NestPage1 = () => import("@/views/test/nest/Page1.vue");
|
||||
const NestPage2 = () => import("@/views/test/nest/Page2.vue");
|
||||
const Iscache = () => import("@/views/test/Cache.vue");
|
||||
const Nocache = () => import("@/views/test/Nocache.vue");
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/test',
|
||||
component: layout,
|
||||
component: Layout,
|
||||
name: "test",
|
||||
meta: {
|
||||
title: "测试页面",
|
||||
@ -40,9 +43,38 @@ export default [
|
||||
path: "auth",
|
||||
name: "testAuth",
|
||||
component: Auth,
|
||||
meta: {
|
||||
title: "权限测试",
|
||||
roles: ["admin", "visitor"],
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "noauth",
|
||||
name: "testNoAuth",
|
||||
component: NoAuth,
|
||||
meta: {
|
||||
title: "权限页面",
|
||||
roles: ["admin"],
|
||||
},
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: "cache",
|
||||
name: "test-cache",
|
||||
component: Iscache,
|
||||
meta: {
|
||||
title: "该页面可缓存",
|
||||
roles: ["admin", "visitor"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "nocache",
|
||||
name: "test-no-cache",
|
||||
component: Nocache,
|
||||
meta: {
|
||||
title: "该页面不缓存",
|
||||
roles: ["admin", "visitor"],
|
||||
noCache: true, // 不缓存页面
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { getItem, setItem, removeItem } from "@/utils/storage"; //getItem和setItem是封装的操作localStorage的方法
|
||||
export const TOKEN = "TOKEN";
|
||||
export const TOKEN = "VEA-TOKEN";
|
||||
const COLLAPSE = "VEA-COLLAPSE";
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
@ -7,8 +8,9 @@ export default {
|
||||
title: 'Vue3 Element Admin',
|
||||
authorization: getItem(TOKEN),
|
||||
sidebar: {
|
||||
collapse: getItem('collapse')
|
||||
}
|
||||
collapse: getItem(COLLAPSE)
|
||||
},
|
||||
device: 'desktop',
|
||||
},
|
||||
mutations: {
|
||||
setToken (state, data) {
|
||||
@ -18,19 +20,22 @@ export default {
|
||||
},
|
||||
clearToken (state) {
|
||||
state.authorization = '';
|
||||
// 保存到localStorage
|
||||
|
||||
removeItem(TOKEN);
|
||||
},
|
||||
setCollapse (state, data) {
|
||||
state.sidebar.collapse = data;
|
||||
// 保存到localStorage
|
||||
setItem('collapse', data);
|
||||
setItem(COLLAPSE, data);
|
||||
},
|
||||
clearCollapse (state) {
|
||||
state.sidebar.collapse = '';
|
||||
// 保存到localStorage
|
||||
removeItem('collapse');
|
||||
}
|
||||
|
||||
removeItem(COLLAPSE);
|
||||
},
|
||||
setDevice (state, device) {
|
||||
state.device = device
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
};
|
||||
@ -1,20 +1,28 @@
|
||||
import { getItem, setItem, removeItem } from "@/utils/storage"; //getItem和setItem是封装的操作localStorage的方法
|
||||
|
||||
const TAGLIST = 'VEA-TAGLIST'
|
||||
|
||||
const state = {
|
||||
tagList: [],
|
||||
tagList: getItem(TAGLIST) || [],
|
||||
cacheList: [],
|
||||
activePosition: 0
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
ADD_TAG_LIST: (state, tag) => {
|
||||
if (state.tagList.some(v => v.path === tag.path)) return;
|
||||
ADD_TAG_LIST: (state, { path, fullPath, name, meta }) => {
|
||||
if (state.tagList.some(v => v.path === path)) return false;
|
||||
|
||||
state.tagList.splice(
|
||||
state.activePosition + 1,
|
||||
0,
|
||||
Object.assign({}, tag, {
|
||||
title: tag.meta.title || 'no-name'
|
||||
Object.assign({}, { path, fullPath, name, meta }, {
|
||||
title: meta.title || '未命名',
|
||||
fullPath: fullPath || path
|
||||
})
|
||||
)
|
||||
|
||||
// 保存到localStorage
|
||||
setItem(TAGLIST, state.tagList);
|
||||
},
|
||||
ADD_CACHE_LIST: (state, tag) => {
|
||||
if (state.cacheList.includes(tag.name)) return
|
||||
@ -25,6 +33,8 @@ const mutations = {
|
||||
|
||||
DEL_TAG_LIST: (state, tag) => {
|
||||
state.tagList = state.tagList.filter(v => v.path !== tag.path)
|
||||
// 保存到localStorage
|
||||
setItem(TAGLIST, state.tagList);
|
||||
},
|
||||
DEL_CACHE_LIST: (state, tag) => {
|
||||
state.cacheList = state.cacheList.filter(v => v !== tag.name)
|
||||
@ -32,6 +42,8 @@ const mutations = {
|
||||
|
||||
DEL_OTHER_TAG_LIST: (state, tag) => {
|
||||
state.tagList = state.tagList.filter(v => !!v.meta.affix || v.path === tag.path)
|
||||
// 保存到localStorage
|
||||
setItem(TAGLIST, state.tagList);
|
||||
},
|
||||
DEL_OTHER_CACHE_LIST: (state, tag) => {
|
||||
state.cacheList = state.cacheList.filter(v => v === tag.name)
|
||||
@ -39,6 +51,8 @@ const mutations = {
|
||||
|
||||
DEL_ALL_TAG_LIST: state => {
|
||||
state.tagList = state.tagList.filter(v => !!v.meta.affix)
|
||||
// 保存到localStorage
|
||||
removeItem(TAGLIST);
|
||||
},
|
||||
DEL_ALL_CACHE_LIST: state => {
|
||||
state.cacheList = []
|
||||
@ -47,7 +61,9 @@ const mutations = {
|
||||
UPDATE_TAG_LIST: (state, tag) => {
|
||||
const index = state.tagList.findIndex(v => v.path === tag.path);
|
||||
if (index > -1) {
|
||||
state.tagList[index] = Object.assign({}, tag)
|
||||
state.tagList[index] = Object.assign({}, state.tagList[index], tag)
|
||||
// 保存到localStorage
|
||||
setItem(TAGLIST, state.tagList);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
<template>
|
||||
<div class="home">home</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "home",
|
||||
setup() {},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home {
|
||||
color: $mainColor;
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form
|
||||
class="form"
|
||||
:model="model"
|
||||
:rules="rules"
|
||||
ref="loginForm"
|
||||
>
|
||||
<h1 class="title">欢迎登录ERP系统</h1>
|
||||
<el-form class="form" :model="model" :rules="rules" ref="loginForm">
|
||||
<h1 class="title">Vue3 Element Admin</h1>
|
||||
<el-form-item prop="userName">
|
||||
<el-input
|
||||
class="text"
|
||||
@ -29,10 +24,12 @@
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
class="btn"
|
||||
@click="submit"
|
||||
>{{btnText}}</el-button>
|
||||
>{{ btnText }}</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@ -87,20 +84,18 @@ export default defineComponent({
|
||||
if (valid) {
|
||||
state.loading = true;
|
||||
const { code, data, message } = await Login(state.model);
|
||||
state.loading = false;
|
||||
if (+code === 200) {
|
||||
ctx.$message.success({
|
||||
message: "登录成功",
|
||||
duration: 500,
|
||||
onClose: () => {
|
||||
duration: 1000,
|
||||
});
|
||||
const targetPath = route.query.redirect;
|
||||
router.push(!!targetPath ? targetPath : "/");
|
||||
},
|
||||
});
|
||||
store.commit("app/setToken", data);
|
||||
} else {
|
||||
ctx.$message.error(message);
|
||||
}
|
||||
state.loading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -124,6 +119,8 @@ export default defineComponent({
|
||||
.form {
|
||||
width: 520px;
|
||||
max-width: 100%;
|
||||
padding: 0 24px;
|
||||
box-sizing: border-box;
|
||||
margin: 160px auto 0;
|
||||
.title {
|
||||
color: #fff;
|
||||
|
||||
8
src/views/redirect/index.vue
Normal file
8
src/views/redirect/index.vue
Normal file
@ -0,0 +1,8 @@
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
this.$router.replace(this.$route.fullPath.replace(/^\/redirect/, ""));
|
||||
},
|
||||
render() {},
|
||||
};
|
||||
</script>
|
||||
@ -1,3 +1,7 @@
|
||||
<template>
|
||||
添加
|
||||
<h2>该页面入口不在菜单中显示</h2>
|
||||
<div>
|
||||
如果不需要在菜单中显示:<br />
|
||||
需要配置路由增加属性hidden: true,注意不是在meta中增加该属性,而是跟meta同级
|
||||
</div>
|
||||
</template>
|
||||
@ -1,3 +1,8 @@
|
||||
<template>
|
||||
权限页面
|
||||
<h2>
|
||||
当前用户角色:{{
|
||||
$store.state.account.userinfo && $store.state.account.userinfo.role
|
||||
}}
|
||||
</h2>
|
||||
<router-link to="/test/noauth">点击进入只有admin才能访问的页面</router-link>
|
||||
</template>
|
||||
17
src/views/test/Cache.vue
Normal file
17
src/views/test/Cache.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<dl>
|
||||
<dt>页面缓存必须满足以下条件:</dt>
|
||||
<dd>1. 路由配置name属性</dd>
|
||||
<dd>2. 当前页面设置name属性,并且跟路由配置的name属性一致,否则无法缓存</dd>
|
||||
</dl>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "test-cache", // 该name必须跟路由配置的name一致
|
||||
setup() {
|
||||
console.log("cache");
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<h1>二级菜单</h1>
|
||||
<h3>二级菜单的页面是不会缓存的</h3>
|
||||
<router-view />
|
||||
</template>
|
||||
1
src/views/test/NoAuth.vue
Normal file
1
src/views/test/NoAuth.vue
Normal file
@ -0,0 +1 @@
|
||||
<template>该页面只有admin能访问</template>
|
||||
17
src/views/test/Nocache.vue
Normal file
17
src/views/test/Nocache.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<dl>
|
||||
<dt>有以下两种方式设置页面不缓存:</dt>
|
||||
<dd>- 当前页面不设置name属性</dd>
|
||||
<dd>- 或者路由配置的meta增加noCache: true</dd>
|
||||
</dl>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "test-no-cache", // 该name必须跟路由配置的name一致,不一致或者不设置name则不缓存
|
||||
setup() {
|
||||
console.log("nocache");
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -1,3 +1,6 @@
|
||||
<template>
|
||||
列表
|
||||
<h2>列表</h2>
|
||||
<router-link to="/test/add">
|
||||
<el-button type="primary">添加一条</el-button>
|
||||
</router-link>
|
||||
</template>
|
||||
@ -1,3 +1 @@
|
||||
<template>
|
||||
Page1
|
||||
</template>
|
||||
<template>Page1</template>
|
||||
|
||||
@ -1,3 +1 @@
|
||||
<template>
|
||||
Page2
|
||||
</template>
|
||||
<template>Page2</template>
|
||||
Loading…
x
Reference in New Issue
Block a user