Vue开发实战-教程篇(上)

简介: 本文适合对vue开发过程有疑惑,以及对vue实际开发过程感兴趣的小伙伴阅读。

一、前言


本文基于开源项目:

https://github1s.com/vuejs/vue

https://vuejs.org/

   最近有不少小伙伴私聊广东靓仔,能不能出一期关于vue日常项目开发的文章,广东靓仔整理了以往开发过的vue项目,结合目前业界主流方案,因此有了这篇文章。

image.png


二、现有方案


   目前业界有很多现成的解决方案,广东靓仔列举了几个:

image.png

Ant Design Pro

image.png

D2 Admin截图

image.png

soybean-admin截图


以上都是比较稳定的管理系统解决方案,有兴趣的小伙伴可以去看看~下面我们一起来梳理梳理,vue实际开发的一些需要考虑到的点。


三、正文


   在使用vue开发我们项目的时候,一般的都是采用现有的开源项目,然后进行一些改造(二次开发),来满足我们的业务需求,这种方案效率是最高的,成本也是最低的。


下面开始讲讲vue实际项目开发需要关注的模块,具体内容如下所示:


使用Vue CLI 3快速创建项目

脚手架,不是本文要讲的重点,随便看看即可~


全局安装

npm install -g @vue/cli 
 or
 yarn global add @vue/cli


新建项目

vue create my-project


最后启动项目,看到如下效果:

image.png


自定义Webpack和Babel配置


webpack.config.js代码如下:

let path = require('path');
let webpack = require('webpack');
/*
 html-webpack-plugin插件,webpack中生成HTML的插件,
 具体可以去这里查看https://www.npmjs.com/package/html-webpack-plugin
 */
let HtmlWebpackPlugin = require('html-webpack-plugin');
/*
 webpack插件,提取公共模块
 */
let CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
let config = {
  //入口文件配置
  entry: {
    index: path.resolve(__dirname, 'src/js/page/index.js'),
    vendors: ['vue', 'vue-router','vue-resource','vuex','element-ui','element-ui/lib/theme-default/index.css'] // 需要进行单独打包的文件
  },
  //出口文件配置
  output: {
    path: path.join(__dirname, 'dist'), //输出目录的配置,模板、样式、脚本、图片等资源的路径配置都相对于它
    publicPath: '/dist/',                //模板、样式、脚本、图片等资源对应的server上的路径
    filename: 'js/[name].js',            //每个页面对应的主js的生成配置
    chunkFilename: 'js/[name].asyncChunk.js?'+new Date().getTime() //chunk生成的配置
  },
  module: {
    //加载器
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            scss: 'vue-style-loader!css-loader!sass-loader', // <style lang="scss">
            sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax' // <style lang="sass">
          }
        }
      },
      {
        test: /\.html$/,
        loader: "raw-loader"
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader'
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: ["es2015","stage-0"],
          plugins: ['syntax-dynamic-import']
        }
      },
      {
        test: /\.scss$/,
        loader: 'style-loader!css-loader!sass-loader'
      },
      {
        test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
        loader: 'file-loader'
      },
      {
        //图片加载器,雷同file-loader,更适合图片,可以将较小的图片转成base64,减少http请求
        //如下配置,将小于8192byte的图片转成base64码
        test: /\.(png|jpg|gif)$/,
        loader: 'url-loader?limit=8192&name=images/[hash].[ext]'
      }
    ]
  },
  //插件
  plugins: [
    //webpack3.0的范围提升
    new webpack.optimize.ModuleConcatenationPlugin(),
    //打包生成html文件,并且将js文件引入进来
    new HtmlWebpackPlugin({
      filename: path.resolve(__dirname, 'dist/html/index.html'), //生成的html存放路径,相对于path
      template: path.resolve(__dirname, 'src/html/index.html'), //ejs模板路径,前面最好加上loader用于处理
      inject: 'body',  //js插入的位置,true/'head'/'body'/false
      hash: true
    }),
    //提取功能模块
    new CommonsChunkPlugin({
      name: 'vendors', // 将公共模块提取,生成名为`vendors`的chunk
      minChunks: 2, //公共模块被使用的最小次数。配置为2,也就是同一个模块只有被2个以外的页面同时引用时才会被提取出来作为common chunks
      // children:true  //如果为true,那么公共组件的所有子依赖都将被选择进来
    }),
  ],
  //使用webpack-dev-server,启动热刷新插件
  devServer: {
    contentBase: path.join(__dirname, "/"),
    host: 'localhost',  //建议写IP地址,开发时候电脑的ip地址。localhost我不知道是幻觉还是怎样,有时候热刷新不灵敏
    port: 9090, //默认9090
    inline: true, //可以监控js变化
    hot: true//热启动
  },
  //搜索路径变量
  resolve: {
    alias: {
      vue: 'vue/dist/vue.js'
    },
    extensions:['.js','.scss','.vue','.json']// 可以不加后缀, 直接使用 import xx from 'xx' 的语法
  }
};
module.exports = config;


设计一个高扩展性的路由

根据页面展示结构进行抽象,结合业务模块进行的合理层级划分

router.js代码如下:

import Vue from 'vue';
import Router from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import NotFound from '../views/404';
Vue.use(Router);
const router =  new Router({
 mode: 'history',
 routes: [
  {path:'/',redirect: '/user/login'},
  {
  path: '/user',
  component: {render: h=>h("router-view")},
  children: [{
    path: 'login',
    name: 'index',
    component: () =>
     import( /* webpackChunkName: "user" */ '../views/User/Login')
   },
   {
    path: 'register',
    name: 'news',
    component: () =>
     import( /* webpackChunkName: "user" */ '../views/User/Register')
   },
   {
    path: '*',
    name: '404',
    component:NotFound
   }
  ]
 }]
})
router.beforeEach((to,form,next)=>{
 NProgress.start();
 next();
});
router.afterEach(() => {
 NProgress.done();
});
export default router


可动态改变的页面布局


方案一:

定义好数据格式,一个页面可以把它划分成多个组件来构成,例如一个基本的布局:

header,main,footer。那么就可以划分成三个组件,为这三个组件添加样式,属性,事件。

{
   header:{
      style:{},
      property:{},
      event:{}
   },
   main:{
      style:{},
      property:{},
      event:{}
   }
}

当数据添加进去,生成的页面就应该根据这些数据来渲染


方案二:

定义模板,根据需要切换

var hdInput = {
    template: "<div><input/></div>"
};
var hdTextarea = {
    template: "<div><textarea></textarea></div>"
};
new Vue({
    el: "#hdcms",
    components: {hdInput,hdTextarea},
    data:{
        formType:"hdInput"
    }
});


将菜单和路由结合


具体方案:

1.前端在本地写好路由表,以及每个路由对应的角色,也就是哪些角色可以看到这个菜单/路由;

2.登录的时候,向后端请求得到登录用户的角色(管理者、普通用户);

3.利用路由拦截,根据取到的用户角色,跟本地的路由表进行对比,过滤出用户对应的路由,并利用路由进行左侧菜单渲染一、本地写好路由表


router/index.js

//代码位置:router/index.js
{
  path: '',
  component: layout, //整体页面的布局(包含左侧菜单跟主内容区域)
  children: [{
    path: 'main',
    component: main,
    meta: {
      title: '首页', //菜单名称
      roles: ['user', 'admin'], //当前菜单哪些角色可以看到
      icon: 'el-icon-info' //菜单左侧的icon图标
    }
  }]
}


二、用户登录,获取用户的角色

获取到用户角色,存放进localStorage,然后跳转主页

//代码位置:src/components/reLoad.vue
// axios.post('/temp',this.formModel).then(res=>{})      
// 我暂时就不模拟了,直接取
let getUserRole = this.formModel.user === 'admin' ? 'admin' : 'user'
localStorage.setItem('userRole', getUserRole)
this.$router.push({
  path: '/main'
})


三、路由拦截beforeEach,并过滤出角色对应的路由表

关键技术点addRoutes

//代码位置:src/permission.js
router.beforeEach((to, from, next) => {
  // 取到用户的角色
  let GetRole = localStorage.getItem("userRole")
  // 如果登录了
  if (GetRole !== 'unload') {
    next() //next()方法后的代码也会执行
    // 1.如果路由表 没根据角色进行筛选,就筛选一次
    if (!addRouFlag) {
      addRouFlag = true
      // 2.根据用户的角色、和需要动态展示的路由,生成符合用户角色的路由
      var getRoutes = baseRoleGetRouters(permissionRouter, GetRole.split(","))
      // 3.利用global属性,让渲染菜单的组件sideMeuns.vue重新生成左侧菜单
      global.antRouter = fixedRouter.concat(getRoutes)
      // 4.将生成好的路由addRoutes
      router.addRoutes(fixedRouter.concat(getRoutes))
      // 5.push之后,会重新进入到beforeEach的钩子里,直接进入第一个if判断
      router.push({ path: to.path })
    }
  } else {
    // 用户没登录,跳转到登录页面
    if (to.path === '/') {
      next()
    } else {
      next('/')
    }
  }
})


精细化的权限设计

权限控制是后台管理系统比较常见的需求,我们需要对某些页面的添加权限控制,可以在路由管理中的权限做一些校验。


一、权限校验函数

getCurrentAuthority()函数用于获取当前用户权限,一般来源于后台数据check()函数用于权限的校验匹配isLogin()函数用于检验用户是否登录


/**
*权限校验函数
* /src/utils,/auth.js演示使用路由管理用户权限
**/
// 获取当前用户权限
export function getCurrentAuthority(){
  return ["user"];
}
//权限校验
export function check(authority){
  const current getCurrentAuthority();
  return current.some(item =authority.includes(item));
}
//登录检验
export function isLogin(){
  const current getcurrentAuthority();
  return current &current[0]!="guest";
}


二、路由配置元信息

路由配置元信息meta:{ authority: ["admin"] }

/**
* 路由配置元信息
* /src/router/index.js
*/
const routes =
// 省略部分代码
{
  path:"/"
  meta:authority:["user","admin"]}
  component:()=
  import(/*webpackChunkName:"Layout"*/"../layouts/BasicLayout")
  //省略部分代码
},
{
  path:"/403",
  name:"403",
  hideInMenu:true,
  component:()=
  import(/*webpackChunkName:"exception"*/"@/views/Exception/403")
}
];


三、路由守卫router.beforeEach中判断

/**登出于形到物权限
* /src/router/index.js
*/
import findLast from "lodash/findLast";
import {check,isLogin} from "../utils/auth";
import {notification} from "ant-design-vue";
// 路由守卫判断权限
router.beforeEach((to,from,next)=>{
  if (to.path I==from.path){
    NProgress.start()
  }
  const record findLast(to.matched,record => record.meta.authority);
  if (record && !check(record.meta.authority)){
    if (lisLogin()&&to.path !=="/user/login"){
      next({
        path:"/user/login"
      })
    } else if(to.path1 !== "/403"){
      notification.error({
        message:"403",
        description:"你设有访间权限,请联系管理员"
      })
      next({
        path:"/403"
      })
    }
    NProgress.done();
  }
  next();
})


使用ECharts、Antv等其他第三方库

根据项目要求,按需引入

使用第三方的开源库,可以提高效率~


使用Mock数据进行开发


一、安装:

npm i mockjs -D

-D: 仅仅在开发环境上使用


二、项目引入:

在 main.js 文件中引入mock:import '@/mock'


三、创建mock文件夹

// 引入随机函数
import { Random } from 'mockjs'
// 引入Mock
const Mock = require('mockjs')
const userListData = Mock.mock({
    'data|10': [
        {
            id: () => Random.id(),
            nickName: () => Random.cword('零一二三四五六七八九十', 3),
            phone: () => Random.integer(11111111111, 99999999999),
            tgCount: () => Random.integer(0, 200),
            earnings: () => Random.float(2000, 10000, 0, 2),
        },
    ],
})
function userList(res) {
    return {
        code: 200,
        data: userListData.data,
        message: '获取成功',
        total: 20,
        size: 10,
        user_count: 20,
        shop_count: 20,
    }
}
const shopListData = Mock.mock({
    'data|10': [
        {
            shop_id: () => Random.id(),
            shop_name: () => Random.cword('零一二三四五六七八九十', 3),
            address: () => Random.city(true),
            shop_tel: () => Random.integer(11111111111, 99999999999),
            open_date: () => Random.date(),
            earnings: () => Random.float(2000, 10000, 0, 2),
        },
    ],
})
function shopList(res) {
    return {
        code: 200,
        data: shopListData.data,
        message: '获取推广店铺成功',
        total: 20,
        size: 10,
        earnings_count: 20000,
        shopCount: 20,
    }
}
export default {
    userList,
    shopList,
}


四、定义访问的方法,接口,请求方式,请求参数

import http from '../../plugins/http'
export const getUserList = (params) => {
    return http.get('/api/cuz/userList')
}
export const getShopListById = (id) => {
    return http.get(`/api/cuz/shopList/${id}`)
}


五、拦截匹配在api中定义的请求,并对此返回模拟出的假数据

// 引入mockjs
import Mock from 'mockjs'
// 引入模板函数类
import ratings from './modules/ratings'
import cuz from './modules/cuz'
// Mock函数
const { mock } = Mock
// 设置延时
Mock.setup({
    timeout: 400,
})
// 使用拦截规则拦截命中的请求,mock(url, post/get, 返回的数据);
mock(/\/api\/ratings\/list/, 'post', ratings.list)
mock(/\/api\/cuz\/userList/, 'get', cuz.userList)
mock(/\/api\/cuz\/shopList/, 'get', cuz.shopList)
相关文章
|
1天前
|
JavaScript API
vue学习(13)监视属性
vue学习(13)监视属性
10 2
|
1天前
|
JavaScript
vue 函数化组件
vue 函数化组件
|
1天前
|
JavaScript
vue知识点
vue知识点
7 2
|
1天前
|
JavaScript 前端开发
vue学习(15)watch和computed
vue学习(15)watch和computed
9 1
|
1天前
|
JavaScript
vue学习(14)深度监视
vue学习(14)深度监视
10 0
|
2天前
|
JavaScript 前端开发
vue动态添加style样式
vue动态添加style样式
|
2天前
|
JavaScript 前端开发
Vue项目使用px2rem
Vue项目使用px2rem
|
2天前
|
JavaScript
vue中watch的用法
vue中watch的用法
|
9天前
|
JavaScript 前端开发
vue学习(6)
vue学习(6)
30 9
|
9天前
|
JavaScript 开发者
vue学习(5)
vue学习(5)
24 7