八. 使用vue+antd搭建后台管理系统(实现篇)

4,025 阅读4分钟

写在前面

上一篇完成了后台管理系统的基础搭建和一些具体功能,现在就直接开始完成页面功能的具体实现和项目优化。

页面基础功能实现

1.登录页

分为 用户名+密码登录 和 手机号+验证码登录两种,验证码mock随机六位数返回,登录成功会返回一个token,请求接口时携带token,图片是从千图网上扣的(白嫖一波)。

2.layout布局

layout 布局按照之前设想的那样,左边导航菜单,上面面包屑+用户信息,再往下就是标签页。

导航栏 看了vue-element-admin 的代码,发现还可以导航栏递归生成,顿时眼前一亮,然后照着写了一遍,发现有很多地方可能用不到,比如外链,再比如 alwaysShow 等,就简化了下,逻辑更加简单。

以前还写过用 v-for 和 v-if(item.children)  然后再 v-for item.children 再 v-if这种方式去写,然后改的时候头皮发麻

标签页 模块主要的难点是(自己手写的,没用tab组件):

1.标签页数量超出可显示区域后显示 左右移动按钮,点击移动按钮移动标签页;

2.点击新页面的时候,当前位置挪动到最后面,显示当前页面的标签页

然后再添加上下布局,得更改下样式和布局方式即可。

3.权限验证

由于之前做过的系统都是后端传路由过来,所以这是第一次尝试使用前端自己控制路由,不过还好vue-element-admin 里面权限控制已经做得非常完美了;画了个流程图,看看就好...

4.echarts图

这里添加了各种的图大部分常用的各种形态,echarts图,柱状图、折线图、饼图、关系图、地图、词云图等。如地图:分布图、散点图、航线图、热力图等,均可实现 点击下钻

如果你要做词云图或者水球图,得先去安装对应插件,如词云图需要安装 echarts-wordcloud

5.系统管理

这个按照之前设想的那样,做一个用户管理和一个角色管理,系统管理只有管理员权限才能看到,可以修改用户所看的菜单,以一个树形结构的方式展示, 但是mock不是很好模拟这些数据,所以就只做了一个假的。

6.其他

之后还添加一些我觉得比较有意思的组件,如:头像上传裁剪 、webSocket、上传Excel等有兴趣可以看看。

优化

虽然页面已经写完了,但是发现还是存在很多问题,如首页加载时间太长,打包文件太大等问题,然后就开始进一轮优化。

1. 关闭生产环境 sourceMap

关闭方法:vue.config.js 设置 productionSourceMap  为 false 即可

 productionSourceMap: false, 

2. 关闭预加载和预编译

vue会预加载后面的路由导致首屏加载时间较长,所以这里去掉。

关闭方法:vue.config.js 设置 chainWebpack :  

 config.plugins.delete('prefetch'); 
  config.plugins.delete('preload');

3. g-zip压缩

使用 compression-webpack-plugin  插件 进行g-zip压缩,需要nginx配合

const CompressionWebpackPlugin = require('compression-webpack-plugin');
config.plugin('CompressionWebpackPlugin').use(CompressionWebpackPlugin, [{  
         filename: '[path].gz[query]',        
        algorithm: 'gzip',        
         test: /\.js$|\.css/, //匹配文件名    
         threshold: 10240, //对超过10k的数据压缩    
         minRatio: 0.8        
 }  ]);

4. 清除生产环境console 和debugger

vu-cli4里面默认使用的是 terser,我们只需修改这个的属性即可。

  config.optimization.minimizer('terser').tap(args => {   
      args[0].terserOptions.compress.warnings = false;   
      args[0].terserOptions.compress.drop_console = true
      args[0].terserOptions.compress.drop_debugger = true;     
      args[0].terserOptions.compress.pure_funcs = ['console.*'];  
      return args;
 });

5. splitChunks 优化

const isProd = process.env.NODE_ENV === 'production';
config.when(isProd, config => {   
    config.optimization.splitChunks({   
         chunks: 'all',      
        cacheGroups: {       
               libs: {         
                 name: 'chunk-libs',     
                 test: /[\\/]node_modules[\\/]/,  
                 priority: 10,         
                 chunks: 'initial'      
        },         
      commons: {         
          name: 'chunk-commons',        
          test: /[\\/]src[\\/]js[\\/]/,       
          minChunks: 2, //  minimum common number  
          priority: 5,     
         reuseExistingChunk: true   
       }       
  }   
 });

6.异步加载cdn

之前使用高德Amap获取行政区边界的时候,都是直接在public里面的index.html 直接引入,但是这样会很不好,登录页的时候就会加载AMap,有时甚至需要2-3s,所以这里我们把它抽出来动态创建 script 加载。  

这里我封装了下,直接使用即可。

const remoteLoad = url => {  
      return new Promise((resolve, reject) => { 
       const existingScript = document.getElementById(url);  
     //如果script不存在   
      if (!existingScript) {     
          const script = document.createElement('script');   
          script.id = url;   
          script.src = url;     
         script.async = true;    
        document.body.appendChild(script);   
        script.onload = function() {   
            setTimeout(() => {          
               this.onerror = this.onload = null;       
               resolve();        
          }, 500);     
        };      
    script.onerror = function() {     
         this.onerror = this.onload = null;    
         reject('加载失败' + url);     
    };    
    } 
    else {   
      setTimeout(() => {  
             resolve();      
      }, 500);    
    }  
   })
}
export default remoteLoad;

使用方法:

async init() {      
    try {      
         await remoteLoad(TinymceCDN);   
        if (window.tinymce) {        
         this.initTinymce();     
         } else {     
            this.$message.error('加载资源失败');      
         }     
      } catch (error) {       
       console.log(error);        
       this.$message.error(error);   
   }  },

7. 封装获取geoJson的方法

以前写的时候没有对geoJson进行封装,每个页面如果需要获取地图geoJson都必须得重新获取,页面看起来很多重复代码,所以这次递归生成geoJson。

使用递归的原因是由于高德不能获取到县的geoJson,所以只能从市的geoJson里面去把县的geoJson抠出来

//先异步加载cdn再 调用方法
export function getGeoJson(adcode, childAdcode = '') {  
   return new Promise((resolve, reject) => {   
      if (window.AMap && window.AMapUI) {    
          insideFun(adcode, childAdcode);    
      } else {  
          remoteLoad(AMapCDN).then(() => {  
             if (window.AMap) {       
                remoteLoad(AMapUiCDN).then(() => {  
                      if (window.AMapUI) {             
                         insideFun(adcode, childAdcode);          
                     } else {          
                         console.error('AMapUI获取失败');      
          }          });       
          } else {      
               console.error('AMap获取失败');  
            }     
        });  
         }  
        function insideFun(adcode, childAdcode) {  
              AMapUI.loadUI(['geo/DistrictExplorer'], DistrictExplorer => {  
                 var districtExplorer = new DistrictExplorer();       
                 districtExplorer.loadAreaNode(adcode, function(error, areaNode) {  
                 if (error) {           
                    console.error(error);        
                   reject(error);           
                    return;         
                 }       
                 let Json = areaNode.getSubFeatures();        
                 if (Json.length === 0) {         
                 let parent = areaNode._data.geoData.parent.properties.acroutes;   
                 insideFun(parent[parent.length - 1], adcode);      
                   return;         
                 }          
                if (childAdcode) {          
                   Json = Json.filter(item => {  
                        return item.properties.adcode == childAdcode; 
                });         
           }        
                let mapJson = {     
                    features: Json    
                };         
                resolve(mapJson);       
           });    
        });   
      }  
     });
}

8.CDN抽离

由于打包后的文件很大,所以在生产环境使用CDN,开发环境使用本地依赖,不过这样会导致线上的bug很难排查,慎用。

vue.config.jsconst 
cdn = {  css: [], 
         js: [VueCDN, AxiosCDN, VueRouterCDN, VuexCDN],  
         externals: {    
            vue: 'Vue',   
           'vue-router': 'VueRouter',  
            vuex: 'Vuex',    
           axios: 'axios'  
}};

 config.plugin('html').tap(args => {  
       args[0].cdn = cdn;     
       return args; 
});


 index.html
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { 
    %>  <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />    <% 
} %>

  <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) {
    %>    <script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>">
</script> <% } %>

最后

这样项目初始化基本上完成了,项目权限管理算是比较不错,其他基本功能也已经实现,还有些其他的功能感兴趣可以看看,本来之前还准备写几套自定义主题和国际化的,最后由于时间不够就放弃掉了。

项目地址:github.com/biubiubiu01…

其他文章