北京网站建设案例腾讯企业邮箱
后端管理系统,前后端分离的框架若依管理后台,来看下vue3+element-plus版本。
静态文本 assets
assets 静态img、svg、style
main.js import '@/assets/styles/index.scss' // global css
引入了全局样式
组件 components
breadcrumb 面包屑
从路由中获取面包屑路径
<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 }}</span><a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a></el-breadcrumb-item></transition-group></el-breadcrumb>
</template><script setup>
const route = useRoute();
const router = useRouter();
const levelList = ref([])function getBreadcrumb() {// only show routes with meta.titlelet matched = route.matched.filter(item => item.meta && item.meta.title);const first = matched[0]// 判断是否为首页if (!isDashboard(first)) {matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched)}levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}
function isDashboard(route) {const name = route && route.nameif (!name) {return false}return name.trim() === 'Index'
}
function handleLink(item) {const { redirect, path } = itemif (redirect) {router.push(redirect)return}router.push(path)
}watchEffect(() => {// if you go to the redirect page, do not update the breadcrumbsif (route.path.startsWith('/redirect/')) {return}getBreadcrumb()
})
getBreadcrumb();
</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>
hamburger
展示按钮svg图标
headerSearch 搜索框
Fuse.js——用于JavaScript中数据的模糊搜索
pagination 分页
treeSelect 树选取器
topNav 顶部导航
<template><el-menu:default-active="activeMenu"mode="horizontal"@select="handleSelect"><template v-for="(item, index) in topMenus"><el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"><svg-icon :icon-class="item.meta.icon" />{{ item.meta.title }}</el-menu-item></template><!-- 顶部菜单超出数量折叠 --><el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber"><template #title>更多菜单</template><template v-for="(item, index) in topMenus"><el-menu-item:index="item.path":key="index"v-if="index >= visibleNumber"><svg-icon :icon-class="item.meta.icon" />{{ item.meta.title }}</el-menu-item></template></el-sub-menu></el-menu>
</template><script setup>
import { constantRoutes } from "@/router"
import { isHttp } from '@/utils/validate'// 顶部栏初始数
const visibleNumber = ref(null);
// 是否为首次加载
const isFrist = ref(null);
// 当前激活菜单的 index
const currentIndex = ref(null);const store = useStore();
const route = useRoute();// 主题颜色
const theme = computed(() => store.state.settings.theme);
// 所有的路由信息
const routers = computed(() => store.state.permission.topbarRouters);// 顶部显示菜单
const topMenus = computed(() => {let topMenus = [];routers.value.map((menu) => {if (menu.hidden !== true) {// 兼容顶部栏一级菜单内部跳转if (menu.path === "/") {topMenus.push(menu.children[0]);} else {topMenus.push(menu);}}})return topMenus;
})// 设置子路由
const childrenMenus = computed(() => {let childrenMenus = [];routers.value.map((router) => {for (let item in router.children) {if (router.children[item].parentPath === undefined) {if(router.path === "/") {router.children[item].path = "/redirect/" + router.children[item].path;} else {if(!isHttp(router.children[item].path)) {router.children[item].path = router.path + "/" + router.children[item].path;}}router.children[item].parentPath = router.path;}childrenMenus.push(router.children[item]);}})return constantRoutes.concat(childrenMenus);
})// 默认激活的菜单
const activeMenu = computed(() => {const path = route.path;let activePath = defaultRouter.value;if (path !== undefined && path.lastIndexOf("/") > 0) {const tmpPath = path.substring(1, path.length);activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));} else if ("/index" == path || "" == path) {if (!isFrist.value) {isFrist.value = true;} else {activePath = "index";}}let routes = activeRoutes(activePath);if (routes.length === 0) {activePath = currentIndex.value || defaultRouter.valueactiveRoutes(activePath);}return activePath;
})
// 默认激活的路由
const defaultRouter = computed(() => {let router;Object.keys(routers.value).some((key) => {if (!routers.value[key].hidden) {router = routers.value[key].path;return true;}});return router;
})
function setVisibleNumber() {const width = document.body.getBoundingClientRect().width / 3;visibleNumber.value = parseInt(width / 85);
}
function handleSelect(key, keyPath) {currentIndex.value = key;if (isHttp(key)) {// http(s):// 路径新窗口打开window.open(key, "_blank");} else if (key.indexOf("/redirect") !== -1) {// /redirect 路径内部打开router.push({ path: key.replace("/redirect", "") });} else {// 显示左侧联动菜单activeRoutes(key);}
}
function activeRoutes(key) {let routes = [];if (childrenMenus.value && childrenMenus.value.length > 0) {childrenMenus.value.map((item) => {if (key == item.parentPath || (key == "index" && "" == item.path)) {routes.push(item);}});}if(routes.length > 0) {store.commit("SET_SIDEBAR_ROUTERS", routes);}return routes;
}onMounted(() => {window.addEventListener('resize', setVisibleNumber)
})
onBeforeUnmount(() => {window.removeEventListener('resize', setVisibleNumber)
})onMounted(() => {setVisibleNumber()
})
</script><style lang="scss">
.topmenu-container.el-menu--horizontal > .el-menu-item {float: left;height: 50px !important;line-height: 50px !important;color: #999093 !important;padding: 0 5px !important;margin: 0 10px !important;
}.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {border-bottom: 2px solid #{'var(--theme)'} !important;color: #303133;
}/* sub-menu item */
.topmenu-container.el-menu--horizontal > .el-sub-menu .el-submenu__title {float: left;height: 50px !important;line-height: 50px !important;color: #999093 !important;padding: 0 5px !important;margin: 0 10px !important;
}
</style>
router.js中指定了布局
import Layout from '@/layout'{path: '/user',// 页面布局component: Layout,hidden: true,redirect: 'noredirect',children: [{path: 'profile',component: () => import('@/views/system/user/profile/index'),name: 'Profile',meta: { title: '个人中心', icon: 'user' }}]},
layout/index.vue 实现了页面的布局
<template><div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }"><div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/><!-- 菜单栏 --><sidebar class="sidebar-container" /><!-- 标签 --><div :class="{ hasTagsView: needTagsView }" class="main-container"><div :class="{ 'fixed-header': fixedHeader }"><navbar @setLayout="setLayout" /><tags-view v-if="needTagsView" /></div><!-- 主视图 --><app-main /><settings ref="settingRef" /></div></div>
</template>
前端框架的 组件及页面布局完成。