Ant Design Pro Vue使用心得

目录结构

├── public
│   └── logo.png             # LOGO
|   └── index.html           # Vue 入口模板
├── src
│   ├── api                  # Api ajax 等
│   ├── assets               # 本地静态资源
│   ├── config               # 项目基础配置,包含路由,全局设置
│   ├── components           # 业务通用组件
│   ├── core                 # 项目引导, 全局配置初始化,依赖包引入等
│   ├── router               # Vue-Router
│   ├── store                # Vuex
│   ├── utils                # 工具库
│   ├── locales              # 国际化资源
│   ├── views                # 业务页面入口和常用模板
│   ├── App.vue              # Vue 模板入口
│   └── main.js              # Vue 入口 JS
│   └── permission.js        # 路由守卫(路由权限控制)
├── tests                    # 测试工具
├── README.md
└── package.json

路由和菜单

基本结构

路由和菜单是组织起一个应用的关键骨架,pro 中的路由为了方便管理,使用了中心化的方式,在 ==router.config.js== 统一配置和管理。

  • 路由管理 通过约定的语法根据在==router.config.js==中配置路由。
  • 菜单生成 根据路由配置来生成菜单。菜单项名称,嵌套路径与路由高度耦合。
  • 面包屑 组件 ==PageHeader== 中内置的面包屑也可由脚手架提供的配置信息自动生成。
路由

目前脚手架中所有的路由都通过 ==router.config.js== 来统一管理,在 ==vue-router== 的配置中我们增加了一些参数,如 ==hideChildrenInMenu==,==meta.title==,==meta.icon==,==meta.permission==,来辅助生成菜单。其中:

  • hideChildrenInMenu 用于隐藏不需要在菜单中展示的子路由。用法可以查看 分步表单 的配置。
  • hidden 可以在菜单中不展示这个路由,包括子路由。效果可以查看 other 下的路由配置。
  • meta.title 和 meta.icon分别代表生成菜单项的文本和图标。
  • meta.permission 用来配置这个路由的权限,如果配置了将会验证当前用户的权限,并决定是否展示 *(默认情况下)。
  • meta.hidden 可以强制子菜单不显示在菜单上(和父级 hideChildrenInMenu 配合)
  • meta.hiddenHeaderContent 可以强制当前页面不显示 PageHeader 组件中的页面带的 面包屑和页面标题栏
路由配置项
/**
 * 路由配置说明:
 * 建议:sider menu 请不要超过三级菜单,若超过三级菜单,则应该设计为顶部主菜单 配合左侧次级菜单
 *
 **/
 {
  redirect: noredirect,  //重定向
  name: 'router-name',   //路由名称
  hidden: true,          //可以在菜单中不展示这个路由,包括子路由。效果可以查看 other 下的路由配置。
  meta: {
    title: 'title',      //菜单项名称
    icon: 'a-icon',      //菜单项图标
    keepAlive: true,     //缓存页面
    permission:[string]   //用来配置这个路由的权限,如果配置了将会验证当前用户的权限,并决定是否展示 *(默认情况下)
    hiddenHeaderContent: true,   //可以强制当前页面不显示 PageHeader 组件中的页面带的 面包屑和页面标题栏
  }
}

具体请参考 https://pro.loacg.com/docs/router-and-nav

菜单

菜单根据 ==router.config.js== 生成,具体逻辑在 ==src/store/modules/permission.js== 中的 ==actions.GenerateRoutes== 方法实现。

Ant Design Pro 的布局

在 Ant Design Pro 中,我们抽离了使用过程中的通用布局,都放在 ==/components/layouts== 目录中,分别为:

  • BasicLayout:基础页面布局,包含了头部导航,侧边栏和通知栏:
  • UserLayout:抽离出用于登陆注册页面的通用布局
  • PageView:基础布局,包含了面包屑,和中间内容区 (slot)
  • RouterView:空布局,专门为了二级菜单内容区自定义
  • BlankLayout:空白的布局

定义全局样式

/* 定义全局样式 */
:global(.text) {
  font-size: 16px;
}

/* 定义多个全局样式 */
:global {
  .footer {
    color: #ccc;
  }
  .sider {
    background: #ebebeb;
  }
}
//覆盖组件样式
// 使用 css 时可以用 >>> 进行样式穿透
.test-wrapper >>> .ant-select {
    font-size: 16px;
}

// 使用 scss, less 时,可以用 /deep/ 进行样式穿透
.test-wrapper /deep/ .ant-select {
    font-size: 16px;
}

// less CSS modules 时亦可用 :global 进行覆盖
.test-wrapper {
    :global {
        .ant-select {
            font-size: 16px;
        }
    }
}

与服务器交互

在 Ant Design Pro 中,一个完整的前端 UI 交互到服务端处理流程是这样的:

  1. UI 组件交互操作;
  2. 调用统一管理的 api service 请求函数;
  3. 使用封装的 request.js 发送请求;
  4. 获取服务端返回;
  5. 更新 data。

从上面的流程可以看出,为了方便管理维护,统一的请求处理都放在 @/src/api 文件夹中,并且一般按照 model 纬度进行拆分文件,如:

api/
  user.js
  permission.js
  goods.js
  ...

其中,==@/src/utils/request.js== 是基于 ==axios== 的封装,便于统一处理 ==POST==,==GET== 等请求参数,请求头,以及错误提示信息等。具体可以参看 ==request.js==。 它封装了全局 request 拦截器、response 拦截器、统一的错误处理、baseURL 设置等。

例如在 api 中的一个请求用户信息的例子:

// api/user.js
import { axios } fromm '@/utils/request'

const api = {
    info: '/user',
    list: '/users'
}

// 根据用户 id 获取用户信息
export function getUser (id) {
    return axios({
        url: `${api.user}/${id}`,
        method: 'get'
    })
}

// 增加用户
export function addUser (parameter) {
    return axios({
        url: api.user,
        method: 'post',
        data: parameter
    })
}

// 更新用户 // or (id, parameter)
export function updateUser (parameter) {
    return axios({
        url: `${api.user}/${parameter.id}`, // or `${api.user}/${id}`
        method: 'put',
        data: parameter
    })
}

// 删除用户
export function deleteUser (id) {
    return axios({
        url: `${api.user}/${id}`,
        method: 'delete',
        data: parameter
    })
}

// 获取用户列表 parameter: { pageSize: 10, pageNo: 1 }
export function getUsers (parameter) {
    return axios({
        url: api.list,
        method: 'get',
        params: parameter
    })
}



**
     * 获取裁剪后的图片
     */
    cropImage () {
      this.form.cropimg = this.$refs.cropper.getCroppedCanvas().toDataURL();
    },
    /**
     * 确认裁剪
     */
    sureCrop () {
      this.dialogVisible = false
    },
    /**
     * 上传裁剪后的图片到服务器
     */
    upCropImg () {
      //判断是否是新增还是编辑
      if (this.$route.query.id && this.$route.query.id != '') {
        //如果是编辑的就直接提交
        this.onSubmit()
      } else {
        //否则先上传裁剪图片,将64位图片转换为二进制数据流
        var formdata1 = new FormData();// 创建form对象
        formdata1.append('file', convertBase64UrlToBlob(this.form.cropimg), 'aaa.png');//
        this.$ajax
          .post(this.$api + "/upload/singleUploadImg", formdata1, { headers: { 'Content-Type': 'multipart/form-data' } })
          .then(response => {
            if (response.data.msg == "success" && response.data.code == 1) {
              this.form.imgUrl = response.data.data.imgUrl
              this.onSubmit()
            } else {
              console.log(response)
              this.$message.error(response.data.msg);
            }
          })
          .catch(function (error) {
            console.log(error);
          });
      }

    },

引入外部模块

$ npm install '组件名字' --save
使用
//全局引入
import Vue from 'vue'
import VueQuillEditor from 'vue-quill-editor'

// require styles
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'

Vue.use(VueQuillEditor, /* { default global options } */)



引入业务图标

参考:https://pro.loacg.com/docs/biz-icon、

国际化

参考:https://pro.loacg.com/docs/i18n

权限管理

参考:https://pro.loacg.com/docs/authority-management

自定义使用规则

  • 修改网站icon的文件地址在 ==public文件夹==中把logo.png换成自定义的,也可在==public/index.html==自定义


  
    
    
    
    
    共享云店
    
  
  
    
    

-更换logo在==src\components\tools\Logo.vue==中更换





pro权限管理和路由控制思路分析(粗略分析)

  • 主要是通过三个文件实现,==src\mock\services\user.js==文件通过登陆的角色获取对应的鉴权规则,具体可查看该文件下的源码
  • ==src\config\router.config.js==文件为路由配置文件,可增加路由取消路由等,变量asyncRouterMap为主要路由数组集合,可配置鉴权权限,变量constantRouterMap为基础路由,不参与鉴权
  • ==src\permission.js==文件为动态配置路由的主要逻辑,代码如下
import Vue from 'vue'
import router from './router'
import store from './store'

import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import notification from 'ant-design-vue/es/notification'
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import { ACCESS_TOKEN } from '@/store/mutation-types'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['login', 'register', 'registerResult'] // no redirect whitelist配置白名单

router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  //生成动态网站标题
  to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
  if (Vue.ls.get(ACCESS_TOKEN)) {
    /* has token 如果有token并且是从登录页来的就直接跳到工作空间*/
    if (to.path === '/user/login') {
      next({ path: '/dashboard/workplace' })
      NProgress.done()
    } else {
    //否则检测是不是没有检测到规则,请求获取用户信息,获取用户权限
      if (store.getters.roles.length === 0) {
      //请求mock模拟数据获取用户权限
        store
          .dispatch('GetInfo')
          .then(res => {
            const roles = res.result && res.result.role
            //调用src\store\modules\permission.js里面的GenerateRoutes方法,处理数据
            store.dispatch('GenerateRoutes', { roles }).then(() => {
              // 根据roles权限生成可访问的路由表
              // 动态添加可访问路由表
              router.addRoutes(store.getters.addRouters)
              const redirect = decodeURIComponent(from.query.redirect || to.path)
              if (to.path === redirect) {
                // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
                next({ ...to, replace: true })
              } else {
                // 跳转到目的路由
                next({ path: redirect })
              }
            })
          })
          .catch(() => {
            notification.error({
              message: '错误',
              description: '请求用户信息失败,请重试'
            })
            store.dispatch('Logout').then(() => {
              next({ path: '/user/login', query: { redirect: to.fullPath } })
            })
          })
      } else {
        next()
      }
    }
  } else {
    if (whiteList.includes(to.name)) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next({ path: '/user/login', query: { redirect: to.fullPath } })
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }
})

router.afterEach(() => {
  NProgress.done() // finish progress bar
})

  • ==src\store\modules\permission.js==文件为路由数据的详细处理逻辑,配合src\permission.js文件使用,代码如下:
import { asyncRouterMap, constantRouterMap } from '@/config/router.config'

/**
 * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除
 *
 * @param permission
 * @param route
 * @returns {boolean}
 */
function hasPermission (permission, route) {
  if (route.meta && route.meta.permission) {
    let flag = false
    for (let i = 0, len = permission.length; i < len; i++) {
      flag = route.meta.permission.includes(permission[i])
      if (flag) {
        return true
      }
    }
    return false
  }
  return true
}

/**
 * 单账户多角色时,使用该方法可过滤角色不存在的菜单
 *
 * @param roles
 * @param route
 * @returns {*}
 */
// eslint-disable-next-line
function hasRole(roles, route) {
  if (route.meta && route.meta.roles) {
    return route.meta.roles.includes(roles.id)
  } else {
    return true
  }
}

function filterAsyncRouter (routerMap, roles) {
  const accessedRouters = routerMap.filter(route => {
    if (hasPermission(roles.permissionList, route)) {
      if (route.children && route.children.length) {
        route.children = filterAsyncRouter(route.children, roles)
      }
      return true
    }
    return false
  })
  return accessedRouters
}

const permission = {
  state: {
    routers: constantRouterMap,
    addRouters: []
  },
  mutations: {
    SET_ROUTERS: (state, routers) => {
      state.addRouters = routers
      state.routers = constantRouterMap.concat(routers)
    }
  },
  actions: {
    GenerateRoutes ({ commit }, data) {
      return new Promise(resolve => {
        const { roles } = data
        const accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
        commit('SET_ROUTERS', accessedRouters)
        resolve()
      })
    }
  }
}

export default permission

跨域请求设置

在==vue.config.js==文件中修改

 // 配置跨域
  devServer: {
    // development server port 8000
    // port: 8000,
    proxy: {
      '/apis': {
        // target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro',
        target: 'http://192.168.1.73:8092/okcloud/',
        // target: 'http://39.107.78.120:8083/okcloud/',

        ws: false,
        changeOrigin: true,  //是否允许跨域
        pathRewrite: {
          '^/apis': ''
        }
      }
    }

axios拦截器

在文件==src\utils\request.js==中设置

// request interceptor
service.interceptors.request.use(config => {
  const token = Vue.ls.get(ACCESS_TOKEN)
  if (token) {
    config.headers['okcloud_token'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
  }
  return config
}, err)

// response interceptor
service.interceptors.response.use((response) => {
  if (response.data.code === 10000) {
    notification.warning({
      message: '提示',
      description: response.data.message
    })
  } else {
    return response.data
  }
}, err)

自定义角色的菜单权限

  • 在==src\main.js==文件中注释掉"// import './permission' // permission control 权限控制"
  • 自定义路由权限文件==src\routeGuard.js==,代码如下
import Vue from 'vue'
import router from './router'
// import store from './store'

import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import { ACCESS_TOKEN } from '@/store/mutation-types'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`))
  if (to.path === '/user/login' && Vue.ls.get(ACCESS_TOKEN)) {
    next({ path: '/dashboard/workplace' })
    NProgress.done()
  } else if (to.path !== '/user/login' && !Vue.ls.get(ACCESS_TOKEN)) {
    next({ path: '/user/login' })
    NProgress.done()
  } else {
    next()
    NProgress.done()
  }
})

router.afterEach(() => {
  NProgress.done() // finish progress bar
})

  • 在==main.js==中引入import './routeGuard'
  • 对==src\components\Menu\menu.js==文件进行自定义菜单改造
  • 在==src\store\modules\app.js==文件中store加入menu,在actions中进行获取菜单的异步操作,获取菜单信息,然后进行全局变量动态获取
  • 在==src\layouts\BasicLayout.vue==中进行全局变量的引用
    ...mapState({
      // 动态主路由
      menus: state => state.app.menu
    }),

动态方法的引用

...mapActions(['setSidebar', 'setMenu']),

调用获取动态方法

this.setMenu()

你可能感兴趣的:(Ant Design Pro Vue使用心得)