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 交互到服务端处理流程是这样的:
- UI 组件交互操作;
- 调用统一管理的 api service 请求函数;
- 使用封装的 request.js 发送请求;
- 获取服务端返回;
- 更新 data。
从上面的流程可以看出,为了方便管理维护,统一的请求处理都放在 @/src/api 文件夹中,并且一般按照 model 纬度进行拆分文件,如:
api/user.jspermission.jsgoods.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})
}
<template><div><a-button @click="queryUser"></a-button><a-table :dataSource="list"></a-table></div>
</template><script>
import { getUser, getUsers } from '@/api/user'export default {data () {return {id: 0,queryParam: {pageSize: 10,pageNo: 1,username: ''},info: {},list: []}},methods: {queryUser () {const { $message } = thisgetUser(this.id).then(res => {this.info = res.data}).catch(err => {$message.error(`load user err: ${err.message}`)})},queryUsers () {getUsers(this.queryParam).then(res => {this.list = res})}}
}
</script>
*** 获取裁剪后的图片*/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.imgUrlthis.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 } */)
<template><div><quill-editor ref="myTextEditor"v-model="content":config="editorOption"@blur="onEditorBlur($event)"@focus="onEditorFocus($event)"@ready="onEditorReady($event)"></quill-editor></div>
</template><script>
//按需加载
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { quillEditor } from 'vue-quill-editor'export default {components: {quillEditor},data () {return {content: '<h2>I am Example</h2>',editorOption: {// something config}}},// 如果需要手动控制数据同步,父组件需要显式地处理changed事件methods: {onEditorBlur(editor) {console.log('editor blur!', editor)},onEditorFocus(editor) {console.log('editor focus!', editor)},onEditorReady(editor) {console.log('editor ready!', editor)},onEditorChange({ editor, html, text }) {// console.log('editor change!', editor, html, text)this.content = html}},// 如果你需要得到当前的editor对象来做一些事情,你可以像下面这样定义一个方法属性来获取当前的editor对象,实际上这里的$refs对应的是当前组件内所有关联了ref属性的组件元素对象computed: {editor() {return this.$refs.myTextEditor.quillEditor}},mounted() {// you can use current editor object to do something(editor methods)console.log('this is my editor', this.editor)// this.editor to do something...}
}
</script>
引入业务图标
参考: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自定义
<!DOCTYPE html>
<html lang="zh-cmn-Hans"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1.0"><link rel="icon" href="<%= BASE_URL %>logo.png"><title>共享云店</title><style>#loading-mask{position:fixed;left:0;top:0;height:100%;width:100%;background:#fff;user-select:none;z-index:9999;overflow:hidden}.loading-wrapper{position:absolute;top:50%;left:50%;transform:translate(-50%,-100%)}.loading-dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:64px;width:64px;height:64px;box-sizing:border-box}.loading-dot i{width:22px;height:22px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.loading-dot i:nth-child(1){top:0;left:0}.loading-dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.loading-dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.loading-dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><div id="loading-mask"><div class="loading-wrapper"><span class="loading-dot loading-dot-spin"><i></i><i></i><i></i><i></i></span></div></div></div><!-- built files will be auto injected --></body>
</html>
-更换logo在src\components\tools\Logo.vue中更换
<template><div class="logo"><router-link :to="{name:'dashboard'}"><LogoSvg alt="logo" /> //这是logo<h1 v-if="showTitle">{{ title }}</h1> //这是网站标题</router-link></div>
</template><script>
import LogoSvg from '@/assets/logo.svg?inline';
export default {name: 'Logo',components: {LogoSvg},props: {title: {type: String,default: 'Admin For Ok', //网站默认标题required: false},showTitle: { //是否显示网站标题,默认不显示type: Boolean,default: true,required: false}}
}
</script>
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 Configurationconst 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 recordnext({ ...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 = falsefor (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 = routersstate.routers = constantRouterMap.concat(routers)}},actions: {GenerateRoutes ({ commit }, data) {return new Promise(resolve => {const { roles } = dataconst 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 Configurationrouter.beforeEach((to, from, next) => {NProgress.start() // start progress barto.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使用心得相关推荐
- ant design pro vue左侧菜单自定义图标
做ant design pro vue项目的时候突然想修改左侧菜单的图标,在修改途中发现源码中提供的方法方法居然不生效(如下所示) 在src/assets/icons文件夹中下载svg图标 在src/ ...
- ant design pro vue 动态路由 流程详解
ant design pro vue 动态路由 流程详解 前言 流程图 流程1 src/permission.js 流程2 src/store/modules/user.js 流程3 src/perm ...
- Ant Design Pro Vue 登录后userInfo存在vuex问题
问题描述: 作者发现使用Ant Design Pro中存在一个问题,页面刷新后vuex数据丢失,导致UserInfo找不到影响页面的问题,故而写此文,望能助有需之人. 原因分析: 提示:Vuex的机制 ...
- 使用Ant Design 和Vue,React中后台开发套餐
2019独角兽企业重金招聘Python工程师标准>>> 前言 目前Ant Design 提供 React 和 Vue 两种整合开发框架,开箱即用的中台前端/设计解决方案,可适合中小公 ...
- 同志们,免费版的Ant Design Pro Vue3 来啦
Vue3也出了一段时间了,于是照着ant pro vue搭个免费的vue3版 希望大家喜欢 基于 Vite2 Vue3 Ant-Design-of-Vue2 TS实现的 Ant Design Pro ...
- ant design pro v5 总结
Ant Design Pro v5 使用心得 相比V4,V5到底强在哪? 如何更改 title图标和加载时的页面? defaultSettings中菜单的几种模式及主题等配置 国际化 request ...
- 搭建Vue版Ant Design Pro后台管理系统
搭建Vue版Ant Design Pro后台管理系统 此文章通过基于Vue实现的Ant DesignPro脚手架快速构建一个后台管理系统的前端 相关文档链接 1.[Ant Design Pro of ...
- Ant Design Pro of Vue方案一权限路由配置(一步步完成,包括所有后端数据库代码,萌新向)
Ant Design Pro of Vue方案一权限路由配置(一步步完成,包括所有后端数据库代码,萌新向) 参考了许多csdn的文章,感谢大家为社区做的贡献,我也来汇总一波,谢谢
- Ant Design Pro of Vue——蚂蚁金服中后台系统框架搭建
Ant Design Pro of Vue--蚂蚁金服中后台系统框架搭建 Ant Design Pro of Vue下载安装启动步骤: 一.Ant Design Pro of Vue 的介绍 Ant ...
最新文章
- HTML5新特性总结
- 二叉搜索树的简明实现(ES5 ES6)
- Windows 操作系统的安全设置
- html运行代码出现问号乱码_Java 0基础入门(初识Html)
- JavaScript基本语法(续)
- 职场心理:12个建议或许能改变你的一生(图)
- 从Excel文件中找出在TXT文件中没有出现的 行之_代码片段
- ASP如何获取客户端真实IP地址
- JavaScript数据类型之数据类型之间的转换(6)
- linux top 网络,Linux Top 详解
- DXperience-7.1.1 Sources 源代下载
- 计算机基础考试题及答案多选,2016年计算机一级考试PS及基础多选模拟试题及答案...
- NLP学习(七)使用stanford实现句法分析-Python3实现
- vue 使用qrcode生成二维码功能
- linux看门狗使用
- 一文读懂串口及各种电平信号含义
- Mac上Chrome安装 Tampermonkey插件的时候提示CRX_HEADER_INVALID
- html中如何把一个div放到页面底部,html – 如何将DIV锚定到页面的底部?
- 2008春天在玉渊潭看到传说中的榆钱树
- 当你改变不了环境的时候试着改变自己
热门文章
- 基于C++实现的股票大数据的统计分析与可视化
- jquery 执行完动画后再执行别的操作
- Java高级--->多线程的学习
- 深度学习-----数据预处理
- Ubuntu22.04TLS插入3.5mm耳机没有声音
- 抖音、今日头条、西瓜视频将打包上市
- Xftp的安装与使用
- aircrack-ng 添加Mac OS X 支持 airodump-ng和aireplay-ng都可以用了
- 显卡、GPU和CUDA
- 2021年广东专插本计算机专业学校,2021年广东省专插本学校名单和专业,广东专插本有哪些学校和那些专业...