导航样式的自定义 vue-manage-system后台管理系统开发总结

前言

vue-manage-system,基于Vue.js和element-ui的后端管理系统模板。 自 2016 年底第一次提交以来,已经三年了,在 GitHub 上拥有 8,300 个 star。 这就是让我不断更新的动力,也导致了很多陷阱,我在这里总结一下。

github地址:vue-manage-system

在线地址:lin-xin.gitee.io/example/work/

图像

图像

图像

自定义图标

element-ui自带的字体图标相对较少,很多比较常见的都没有,所以需要引入你想要的字体图标。 最流行的图标库Font Awesome,足有675个图标,但这也导致字体文件比较大,项目中没有必要使用这么多图标。 那么这个时候,阿里巴巴图标库就是一个非常好的选择。

首先在阿里图标上创建一个项目,设置图标前缀,如el-icon-lx,设置Font Family,如lx-iconfont导航样式的自定义,将需要使用的图标添加到项目中。 我选择Font类来生成在线链接,因为所有页面都需要使用图标,只需在index.html中直接引入css链接即可




    
    vue-manage-system
    
    


然后需要设置带有前缀 el-icon-lx 的图标类名才能使用 lx-iconfont 字体。

[class*="el-icon-lx"], [class^=el-icon-lx] {
    font-family: lx-iconfont!important;
}

但这种风格应该放在哪里呢? 这可不是随便就能放进去的。 main.js中引入了element-ui样式,样式中有一段css:

[class*=" el-icon-"], [class^=el-icon-]{
    font-family: element-icons!important;
    speak: none;
    font-style: normal;
    font-weight: 400;
    font-variant: normal;
    text-transform: none;
    line-height: 1;
    vertical-align: baseline;
    display: inline-block;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

显然,如果这个css在我们自定义样式之后执行,就会覆盖我们的样式,自定义图标就不会显示了。 构建项目时,会将APP.vue中的样式打包到app.css中,然后将main.js中引用的样式追加到后面。 那么我们可以把自定义的样式放到一个css文件中,然后在main.js中引入element-ui css后引入,然后就可以覆盖掉默认的字体了,然后就可以在项目中使用图标了。

聪明人发现,如果我的自定义图标的前缀不应该包含el-icon-,就不会有这样的问题。 是的,为了保持与原字体相同的样式,需要复制它的整个css

/* 假设前缀为 el-lx */
[class*="el-lx-"], [class^=el-lx-]{
    font-family: lx-iconfont!important;
    speak: none;
    font-style: normal;
    font-weight: 400;
    font-variant: normal;
    text-transform: none;
    line-height: 1;
    vertical-align: baseline;
    display: inline-block;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

导航菜单

element-ui关于导航菜单的文档也很详细,但是还是有人提出问题或者加QQ问我:如何创建三级菜单等。而且,具体的菜单项可能是返回的具体数据项由服务器根据权限进行分配,因此不能将它们硬编码在模板中。

首先,确定菜单数据的格式如下。 即使服务器返回的格式不是这样的导航样式的自定义,前端也需要将其处理成如下格式:

export default {
    data() {
        return {
            items: [{
                icon: 'el-icon-lx-home',
                index: 'dashboard',
                title: '系统首页'
            },{
                icon: 'el-icon-lx-calendar',
                index: '1',
                title: '表单相关',
                subs: [{
                    index: '1-1',
                    title: '三级菜单',
                    subs: [{
                        index: 'editor',
                        title: '富文本编辑器'
                    }]
                }]
            },{
                icon: 'el-icon-lx-warn',
                index: '2',
                title: '错误处理',
                subs: [{
                    index: '404',
                    title: '404页面'
                }]
            }]
        }
    }
}

icon是菜单图标,可以使用上面我们自定义的图标; index为路由地址; title 是菜单名称; subs 是子菜单。 模板通过判断菜单是否包含子菜单来显示二级菜单和三级菜单。


    
        
            
                
                    {{ item.title }}
                
                
                    
                        {{ subItem.title }}
                        
                        
                            {{ threeItem.title }}
                        
                    
                    
                        {{ subItem.title }}
                    
                
            
        
        
        
            
                {{ item.title }}
            
        
    

这样就完成了动态导航菜单。

通过 Header 组件中的按钮触发 Sidebar 组件的展开或折叠涉及到在组件之间传递数据。 这里,组件之间的通信是通过 Vue.js 的一个单独的事件中心(Event Bus)来管理的。

const bus = new Vue();

单击 Header 组件中的按钮时会触发折叠事件:

bus.$emit('collapse', true);

监听 Sidebar 组件中的折叠事件:

bus.$on('collapse', msg => {
    this.collapse = msg;
})

图表自适应

vue-manage-system中使用的图表插件是vue-schart,它封装了一个基于canvas的图表插件schart.js。 要使图表适应其宽度并随着窗口或父元素的大小变化而重新渲染,如果图表插件中没有实现此功能,则需要手动实现。

vue-schart 提供了 renderChart() 方法来重新渲染图表。 在Vue.js中,父组件调用子组件的方法可以通过$refs来调用。


然后监听窗口的resize事件并调用renderChart()方法重新渲染图表。

import Schart from 'vue-schart';
export default {
    components: {
        Schart
    },
    mounted(){
        window.addEventListener('resize', ()=>{
            this.$refs.bar.renderChart();
        })
    }
}

但记得在组件被销毁时移除监听器! 监听窗口的大小改变完成了,但是父元素的大小改变呢? 因为父元素的宽度设置为百分比,所以当侧边栏折叠时父元素的宽度会发生变化。 然而,div没有resize事件,无法监听其宽度变化,但我们会知道何时触发折叠。 那么是不是可以在折叠发生变化时,通过调用渲染函数来重新渲染图表呢?那么最好通过Event Bus监听侧边栏的变化,300ms后重新渲染,因为当折叠时有一个300ms的动画过程折叠式的。

bus.$on('collapse', msg => {
    setTimeout(() => {
        this.$refs.bar.renderChart();
    }, 300);
});

多个选项卡

多标签页也是提出最多问题的功能。

在A选项卡输入一些内容后,打开B选项卡,然后返回A,离开前的状态必须保留,所以需要使用keep-alive进行缓存,关闭后的选项卡将不再缓存,以避免关闭和重新开放。 还是和以前一样的状态。 keep-alive的include属性的作用是只有匹配的组件才会被缓存。 include 匹配的不是路由名称,而是组件名称,因此每个组件都需要添加 name 属性。

在Tags组件中,监听路由变化,并将打开的路由添加到标签页中:

export default {
    data() {
        return {
            tagsList: []
        }
    },
    methods: {
        setTags(route){
            const isExist = this.tagsList.some(item => {
                return item.path === route.fullPath;
            })
            if(!isExist){
                this.tagsList.push({
                    title: route.meta.title,
                    path: route.fullPath,
                    name: route.matched[1].components.default.name
                })
            }
        }
    },
    watch:{
        $route(newValue, oldValue){
            this.setTags(newValue);
        }
    }
}

setTags方法中,tag数组中保存了一个tag对象,包括title(标签显示的标题)、path(标签的路由地址)、name(组件名称,用于include匹配)。 路由地址需要使用fullPath字段。 如果使用路径字段,如果地址后面有参数,则不会保存。

在Home组件中,它监听标签的变化并缓存所需的组件。


    

export default {
    data(){
        return {
            tagsList: []
        }
    },
    created(){
        // 只有在标签页列表里的页面才使用keep-alive,即关闭标签之后就不保存到内存中了。
        bus.$on('tags', msg => {
            let arr = [];
            for(let i = 0, len = msg.length; i < len; i ++){
                // 提取组件名存到tagsList中,通过include匹配
                msg[i].name && arr.push(msg[i].name);
            }
            this.tagsList = arr;
        })
    }
}

总结

由于该项目不包含任何业务代码,因此比较简单。 不过,我从开发中积累了一些经验,可以在其他项目中开发得更加熟练。 功能虽然不多,但也勉强够用。 如果大家有什么好的建议,可以提出issue一起讨论。

更多文章:lin-xin/blog

© 版权声明
THE END
喜欢就支持一下吧
点赞155赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容