-
think-ant-vue:ThinkPHP与Ant Design Pro Vue基础权限系统-源码
2021-03-11 16:19:29ThinkPHP Ant Design Pro Vue ThinkPHP 6.0与Ant Design Pro Vue基础权限系统基于Ant Design Vue Pro 预览地址: 开发文档: : Ant Design Vue Pro文档: : 初步: : 服务器提供: 预览账号 超级管理员admin, ... -
ToBeAdmin:beego与Ant Design Pro Vue基础权限系统-源码
2021-03-11 16:57:09beego Ant Design Pro Vue RBAC beego与Ant Design Pro Vue基础权限系统 初步: ://beego.me ORM:gorm Ant Design Pro Vue文档: : 预览: 账号 超级管理员admin, 1234 普通管理员test, 1234 前端部署 安装 cd... -
Ant Design Pro Vue使用心得1
2020-11-06 14:24:23Ant Design Pro Vue使用心得 目录结构 ├── public │ └── logo.png # LOGO | └── index.html # Vue 入口模板 ├── src │ ├── api # Api ajax 等 │ ├── assets # 本地静态资源 │ ...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.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 }) }
<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 } = this getUser(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.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 } */)
<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 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不完全开发手册
2021-01-21 10:08:03Ant Design Pro Vue不完全开发手册前言准备工作:1、安装npm2、可选安装3、安装 vue-cli 工具:4、参考文档基础开发学习:1、git项目拉取2、安装项目3、启动项目4、文件解析vue.config.jsrouter.config.jsviews(1)...Ant Design Pro Vue不完全开发手册
前言
此文档是我为公司写的前端框架开发手册(应该没人看,哈哈),这里删除公司敏感信息后,作为自己的总结文档记录一下。
准备工作:
1、安装npm
npm更换阿里镜像
npm config set registry https://registry.npm.taobao.org/
查看是否成功
npm config get registry
2、可选安装
cnpm(同npm,淘宝镜像速度快)、yarn(https://blog.csdn.net/csdn_yudong/article/details/82015885)
3、安装 vue-cli 工具:
安装命令npm install -g @vue/cli
4、参考文档
ant design vue :http://vue.ant-design.cn/docs/vue/introduce-cn/
ant design vue pro :https://pro.loacg.com/docs/getting-started基础开发学习:
1、git项目拉取
2、安装项目
cnpm install 或者 npm install
3、启动项目
npm run serve
或者
yarn serve注:相关命令存放于package.json
启动成功信息
4、文件解析
vue.config.js
本地开发需要在
vue.config.js
配置代理对象
router.config.js
路由用于菜单配置
注意:
(1)admin账户直接读取的是router.config.js路由配置,未和数据库做交互,为全权限。
(2)其他账户经过admin-service服务读取表sys_permission获取权限,其权限为部分权限,故添加新页面时必须要向该表添加对应数据,sys_permission.title = router.config.js.title,
sys_permission.name = router.config.js.permission,其他无要求。
(3)相关权限控制代码位于
views
视图——》业务逻辑开发位置
业务开发推荐目录结构(add和update其实可以复用,为了降低开发难度,才做此拆分)
(1)api.js
——》对外接口,主要用于发送请求
使用组件为 axios(1)get请求写法
export function getAirportInfo (parameter) { return axios({ url: '...', method: 'get', params: parameter // 此处为params }) }
(2)post请求写法
export function page (parameter) { return axios({ url: '...', method: 'post', data: parameter // 此处为data }) }
(2)vue开发浅谈
1、引入组件(按需引用)
import { STable } from '@/components'
2、注册组件
3、数据定义
data () { return {...} }
4、生命周期定义
mounted () {...}
拓展学习https://cn.vuejs.org/v2/guide/instance.html#生命周期图示
5、方法定义methods: {...}
6、监听
多用于更新页面数据回显watch: {...}
7、mock
——》模拟数据存放位置,脱离后端进行调试问题与解惑
1、如何把父组件的值传到子组件
父组件中
子组件中
父组件直接传递即可
2、父组件调如何用子组件方法
如何取别名:ref=“model”
【this.$refs.{别名}】
这个的意思是获取对应vue的对象
this.$refs.{别名}.{方法}3、子组件如何调用父组件方法
调用$emit方法,其中“o”为父组件中,子组件的方法
this.$emit(o)
4、列表属性
5、导入了组件,却发现使用不了
为了前端的渲染速度,我们采用的是按需引用
框架没有引用的,我们需要手动引用组件
(1)方式一:引用组件相关所有文件
// 折叠框,直接使用会报错,在此手动引用 import ACollapse from 'ant-design-vue/es/collapse/Collapse' import ACollapsePanel from 'ant-design-vue/es/collapse/CollapsePanel' import 'ant-design-vue/lib/collapse/style'
(2)方式二(推荐)
import { Button } from 'ant-design-vue'; Vue.use(Button);
6、如何进行权限控制(按钮级)
<span v-if="$auth('luggageOrder.luggageManualRefund')"></span>
注意:luggageOrder.luggageManualRefund与权限表的关联性(别忘了router.config.js路由哟)
拓展学习
https://pro.antdv.com/docs/authority-management7、更新页面如何重置数据
在父页面Lsit,将data设置为空
8、新增页面如何重置数据
在新增页面,调用关闭方法时,重置form表单
this.$nextTick(() => { this.form.setFieldsValue({ ...formdata }) })
9、数据类型怎么定义
对象 - queryParam : {} 数组 - queryParam : [] 字符串 - queryParam : ''
10、下拉框有数据,但是为空白,不显示
多半是下拉框数据类型不匹配,请检查number还是字符串
11、时间框的对象用什么
import moment from 'moment'
拓展学习
http://momentjs.cn/12、列表字段实现toptip效果
ellipsis
13、使用watch监听时,第一次加载页面会报错。(20210220新增)
增加数据校验
if (!n || !n.id) { return }
结语
为什么这个手册叫不完全开发手册呢?因为我也是一个前端小白,从来没有系统学习过vue,都是从0开始,撸起袖子就干了。以上内容都是从经验出发,希望大家少走弯路,快速开发,所以描述啥的可能会不准确,如果有错误,同学们可以直接改正,或者联系【黎晓东】。这里引用晓东大神的一句话:“虽然我们是后端开发,但是如果连一个后台管理系统的前端页面都写下不下来,真的说不过去”。
附录
ant design vue :http://vue.ant-design.cn/docs/vue/introduce-cn/
ant design vue pro :https://pro.loacg.com/docs/getting-started
vue:https://cn.vuejs.org/
moment:http://momentjs.cn/
axios:http://www.axios-js.com/ -
Ant Design Pro Vue 填坑记
2020-07-02 09:33:26Ant Design Pro Vue 填坑记Issus Issus We’re sorry but blog doesn’t work properly without JavaScript enable Fixed issue 了很多问答,其实问题并不是出在router文件夹的index.js文件中的mode 其实是在于你的...Ant Design Pro Vue 填坑记
Issus
- We’re sorry but blog doesn’t work properly without JavaScript enable
Fixed issue
了很多问答,其实问题并不是出在router文件夹的index.js文件中的mode
其实是在于你的组件并没有正确的引入进来。
文件路径 src\router\generator-routers.js中的export const generator = (routerMap, parent) 方法
component: (constantRouterComponents[item.component || item.key]) || (() => import(@/views/${cc}
)),
需要debug这些问题
看看到底导入了没有。
未完待续
- We’re sorry but blog doesn’t work properly without JavaScript enable
-
think-admin:ThinkPHP 6.0与Ant Design Pro Vue基础前后分离权限系统-源码
2021-03-11 17:34:27ThinkPHP 6.0与Ant Design Pro Vue基础前后分离权限系统 预览地址: 前端 蚂蚁设计Vue Pro 初步 没安装Composer?请在最新版本链接下找到TAnt_Full.zip下载 安装 拉取代码 git clone ... -
vue-antdesign-admin-template:Ant Design Pro Vue模板-源码
2021-02-05 03:35:59蚂蚁设计专业Vue模板 根据改造后的开箱即用的后台管理系统模板,优化了很多内容,更轻量,更简洁,可以在此模板上快速,便捷地去建造自己的后台项目。预览:| 优化:项目结构和文件夹命名,项目结构更加合理 优化:... -
Ant Design Pro Vue使用心得
2019-06-22 13:12:21目录结构 ├── public │ └── logo.png # LOGO | └── index.html # Vue 入口模板 ├── src │ ├── api # Api ajax 等 │ ├── assets # 本地静态资源 │ ├── config ...目录结构
├── 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.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 }) }
<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 } = this getUser(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.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 } */)
<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 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学习总结。(更新中..)
2019-10-23 17:46:37Ant Design是蚂蚁金服体验技术部打磨出一个服务于企业级产品的设计体系,通过模块化解决方案降低生产成本。...ant pro vue 是一套基于Ant Design Vue的中后台管理控制台的脚手架。 直接从GitHub仓库中直接安装最... -
ant design pro vue 踩坑
2020-06-04 22:08:45在跟着官方文档一步步做的时候发现了一个问题,当使用babel-plugin-import按需... 遂照着网上的解决方案来做,但是发现很多方法都没用比如添加vue.config.js之类的,最终发现是less版本的问题,将less引入之后,它的版 -
ant vue 语言_Ant Design Pro Vue使用心得
2020-12-21 14:13:26目录结构├── public│ └── logo.png # LOGO| └── index.html # Vue 入口模板├── src│ ├── api # Api ajax 等│ ├── assets # 本地静态资源│ ├── config ... -
Ant Design Pro Vue 打包后本地无法预览
2020-08-23 22:51:51关于Ant-Design-Pro-Vue项目打包后无法预览问题 打包后的dist文件夹 打开index.html 一直处于加载状态 由于官方没有明确说明打包后的配置和部署,对于初学者来说以为打开index.html就可以预览了 预览打包后的项目... -
ant design pro vue 开发环境配置
2019-11-10 23:22:49 -
ant vue 设置中文_Ant Design Pro Vue 全局国际化-设置中文
2021-02-06 12:56:32我是vue-antd-pro 3.0版本,设置全局国际化包括日期控件,话不多说直接上手操作:打开src/App.vue,修改scriptimport { domTitle, setDocumentTitle } from '@/utils/domUtil'import { i18nRender } from '@/locales... -
Ant Design Pro Vue 全局国际化-设置中文
2020-09-25 09:54:45我是vue-antd-pro 3.0版本,设置全局国际化包括日期控件,话不多说直接上手操作: 打开src/App.vue,修改script <script> import { domTitle, setDocumentTitle } from '@/utils/domUtil' import { i18... -
ant design pro vue 设置访问开发服务器api
2019-06-11 23:15:111.首先需要将src/mock下对应的接口注释...2.在 vue.config.js配置中增加如下内容 devServer: { // development server port 8888 port: 8000, proxy: { '/api': { // 匹配模式,所有api开始的连接都匹配到下面... -
基于Ant Design Pro Vue 自定义按钮级别权限命令
2021-01-21 10:43:58const auth = Vue.directive('auth', { inserted: function (el, binding) { if (!Vue.prototype.$_has(binding.value)) { el.parentNode.removeChild(el) // el.setAttribute('disabled', 'disabled') // . -
Vue:加入百度地图经纬度选取。(Ant design pro vue)
2020-05-30 15:28:43(我这里就是上线炸了,滑稽) Vue模块: 创建一个组件 {{$t('Map.MapInputDesc')}}: 引用模块 唤起地图 import addressSelection from '@/views/account/settings/page/AddressSelection' export default { name:... -
ant design pro vue 组件未注册 Unknown custom element:<a-tree>
2020-10-16 15:17:27问题:无法使用tree组件 提示 Unknown custom element:<a-tree> - did you register the component correctly? For recursive components, make sure to provide the “name” option. 原因 按需加载默认未... -
关于Ant Design Pro Vue引入Viser图表不响应或超出的问题!
2019-01-03 16:17:03问题1:Viser在响应式布局中,会导致图表超出的问题,或者跟随导航栏伸缩导致图表过窄或过长。 可能出现的原因!1.由于图表是Canvas画布,在他加载时只确定栅格布局的宽度,当导航栏再次伸缩,就出超出,溢出。... -
基于AntDesignofVue实现的AntDesignPro
2019-08-13 04:06:19基于 Ant Design of Vue 实现的 Ant Design Pro -
antd vue表单上传文件_ant design pro vue 带上传文件的form表单,表单提交走接口上传文件...
2021-02-27 09:01:38ref="ruleForm":model="formDataAll":rules="rules":label-col="labelCol":wrapper-col="wrapperCol">v-model="formDataAll.name"@blur="()=>{$refs.name.onFieldBlur();}"/>上海北京v-model="formDataAll.... -
基于 Ant Design of Vue 实现的 Ant Design Pro 源码
2019-12-29 17:55:35基于 Ant Design of Vue 实现的 Ant Design Pro 源码 安装依赖 npm install 开发模式运行 npm run serve 编译项目 npm run build