[TOC] # 前端通用框架 ## 框架介绍 基于`vue@2.6`, `ant-design-vue@1.7`, `axios`, `vue-router`, `vuex`, `webpack@4.x` 的通用后台管理系统 功能: 1. 根据路由动态生成侧边导航 2. 使用`mock.js`和`mock-serve`进行本地mock 3. 集成`eslint`和`prettier`配置 4. 集成`commit插件` 5. 集成`git`钩子, 必须通过`eslit`代码检查和`commit`提交规范检查才能提交代码 6. `ant-design-vue` 组件按需引入, ant-design icon 按需引入 7. 响应错误统一处理 8. `vue-ls`本地持久化 9. 支持设置主题 10. 权限自定义指令 ## not Support 1. ie11 兼容 2. h5 兼容 3. 响应式 ## 快速开始 推荐使用vscode开发, 需要安装`prettier`, `eslint`, `vetur`插件 ```bash # 需要全局安装commitizen才能使用 git cz npm install commitizen -g # 克隆项目 git clone xxx git cd xxx # 安装插件 yarn # 启动服务 yarn run dev # 打包 yarn run build ``` ## 目录结构 ```json ├─.browserslistrc ------------------------ // 目标浏览器配置 ├─.env.development ----------------------- // 开发环境变量配置 ├─.env.production ------------------------ // 开发环境变量配置 ├─.eslintignore -------------------------- // eslint忽略文件 ├─.eslintrc.js --------------------------- // eslisnt配置 ├─.gitignore ----------------------------- // git忽略配置 ├─.prettierignore ------------------------ // prettier忽略配置 ├─.prettierrc.js ------------------------- // 代码风格配置 ├─.vscode -------------------------------- // vscode项目设置 │ └─settings.json ------------------------ // vscode配置 ├─README.md ├─babel.config.js ------------------------ // js编译器设置 ├─commitlint.config.js ------------------- // git commit lint ├─jsconfig.json -------------------------- // JavaScript项目的根目录配置 ├─package.json --------------------------- // npm包管理 ├─public --------------------------------- // 静态资源文件夹 │ ├─color.less --------------------------- // 主题颜色配置 │ ├─favicon.ico -------------------------- // 网站图标 │ └─index.html --------------------------- // 主页 ├─src ------------------------------------ // 代码目录 │ ├─App.vue ------------------------------ // 项目根组件 │ ├─api ---------------------------------- // api目录 │ │ └─user.js ---------------------------- // 用户相关接口 │ ├─assets ------------------------------- // 资源文件夹 │ │ ├─avatar.jpg ------------------------- // 默认头像 │ │ ├─background.svg --------------------- // 登录页背景 │ │ ├─checkcode.png ---------------------- // 验证码默认图 │ │ ├─icons ------------------------------ // 图标相关 │ │ │ ├─index.js │ │ │ ├─svg ------------------------------ // svg图标存放路径 │ │ │ │ ├─develop.svg │ │ │ │ └─svgtest.svg │ │ │ └─svgo.yml ------------------------- // svg图标格式化配置 │ │ ├─less ------------------------------- // 样式 │ │ │ └─common.less ---------------------- // 公共样式 │ │ └─logo.png │ ├─components --------------------------- // 公共组件 │ │ ├─Header.vue ------------------------- // 头部 │ │ ├─SettingDrawer ---------------------- // 系统设置(主题) │ │ │ ├─index.vue │ │ │ └─settingConfig.js ----------------- // 主题颜色生成 │ │ ├─SvgIcon.vue ------------------------ // svg图标组件 │ │ ├─TopTabs.vue ------------------------ // tab栏导航 │ │ ├─UserPassword.vue │ │ ├─layouts ---------------------------- // 布局相关 │ │ │ ├─AggregateLayout(可以删除).vue │ │ │ ├─BlankLayout.vue ------------------ // 空白布局 │ │ │ ├─GlobalLayout.vue ----------------- // 全局部局 │ │ │ ├─SideTabsLayout.vue --------------- // 侧边切换tab布局 │ │ │ └─UserLayout.vue ------------------- // 登录/注册/找回密码共用布局 │ │ └─menu │ │  ├─Contextmenu.vue ------------------ // 上下文菜单(在顶部导航鼠标右键菜单用到) │ │  └─SideMenu.js ---------------------- // 左侧导航栏生成 │ ├─config │ │ └─defaultSettings.js ----------------- // 全局主题颜色/vue-ls配置 │ ├─main.js ------------------------------ // 入口 │ ├─mock --------------------------------- // mock │ │ ├─constant --------------------------- // 存放较大的数据 │ │ │ └─userPermission.js ---------------- // 权限模拟数据 │ │ ├─index.js │ │ ├─mock-server.js --------------------- // mock-server本地服务 │ │ ├─table.js --------------------------- // mock语法示例 │ │ ├─user.js ---------------------------- // 模拟接口 │ │ └─utils.js --------------------------- // mock服务工具方法 │ ├─router ------------------------------- // 路由 │ │ ├─generateIndexRouter.js ------------- // 动态路由生成 │ │ └─index.js │ ├─store -------------------------------- // vuex │ │ ├─index.js │ │ └─modules ---------------------------- // vuex模块 │ │  ├─app.js --------------------------- // 全局 │ │  └─user.js -------------------------- // 用户相关 │ ├─utils -------------------------------- // 工具类 │ │ ├─lazy ------------------------------- // 按需加载 │ │ │ ├─icons.js ------------------------- // 图标 │ │ │ └─lazyAntd.js ---------------------- // ant-design-vue组件 │ │ └─request.js ------------------------- // axios封装 │ └─views -------------------------------- // 页面 │  ├─About.vue -------------------------- // 关于 │  ├─Home.vue --------------------------- // 首页 │  ├─account ---------------------------- // 账号相关 │  │ ├─Center.vue ----------------------- // 个人中心页 │  │ └─settings ------------------------- // 个人设置页 │  │  ├─BaseSetting.vue │  │  ├─Binding.vue │  │  ├─Custom.vue │  │  ├─Notification.vue │  │  └─Security.vue │  ├─exception -------------------------- // 404/403/500等错误页 │  │ └─404.vue │  ├─list ------------------------------- // 多级菜单 示例 │  │ ├─StandardList.vue │  │ └─TableList.vue │  ├─modules ---------------------------- // 存放页面公共组件 │  │ └─online │  │  └─desform ------------------------ // 列表页跳详情页 示例 │  │  ├─DesignFormList.vue │  │  └─DesignFormTempletList.vue │  ├─oneLevelMenu ----------------------- // 一级菜单示例页面 │  │ └─index.vue │  ├─profile ---------------------------- // 路由聚合示例 │  │ ├─Advanced.vue │  │ └─Basic.vue │  ├─sideTabs --------------------------- // 侧边栏tab标签 示例 │  │ ├─components │  │ │ ├─Tab1.vue │  │ │ ├─Tab2.vue │  │ │ ├─Tab3.vue │  │ │ └─Tab4.vue │  │ └─index.vue │  ├─title │  │ └─index.vue │  └─user ------------------------------- // 登录/注册/找回密码 相关 │  ├─Login.vue ------------------------ // 登录页 │  └─LoginSimple.vue ├─test ----------------------------------- // 测试用例 │ └─test.js ├─vue.config.js -------------------------- // vue-cli配置 └─yarn.lock ------------------------------ // npm包版本控制 ``` ### vue组件的存放路径: 1. 框架相关的组件, 存放在`@/components` 2. 布局相关的, 存在在`@/components/layouts` 3. 不相关的两个页面的公共组件, 存放在`@/viewmodules` 4. 父子关系/兄第关系的页面的公共组件, 且没有其他页面使用, 存放在他们的公共根目录下`@/views/xxx/components` ## 动态路由 ### 路由菜单不要超过3层 image.png ### 路由生成 image.png ### 路由配置项: ```json { // "hidden": true, // (默认不传)不在左侧菜单中显示 // "redirect":null, // (默认不传)重定向 // "alwaysShow": false, // (默认不传)路由聚合, 从menu菜单展示变为tab栏展示 "path":"/dashboard/analysis", // 路径 "component":"dashboard/Analysis", // 组件 "name":"dashboard-analysis", // 路由名称, 使用时必填 "meta":{ "keepAlive":true, // 是否缓存 "icon":"home", // 图标 "title":"首页", // 侧边栏和面包屑中展示的名字 // "url": "http://www.baidu.com" // (默认不传)当外部链接跳转时必须传, 必须带http:// 或者httPs:// }, }, ``` 举例: "系统管理"为一级菜单 "用户管理"为子菜单 ```json { "path":"/isystem", "component":"layouts/RouteView", "name":"isystem", "meta":{ "keepAlive":false, "icon":"setting", "title":"系统管理" }, "children":[ { "path":"/isystem/user", "component":"system/UserList", "name":"isystem-user", "meta":{ "keepAlive":false, "title":"用户管理" }, }, { "path":"/isystem/roleUserList", "component":"system/RoleUserList", "name":"isystem-roleUserList", "meta":{ "keepAlive":true, "title":"角色管理" }, }, { "path":"/isystem/newPermissionList", "component":"system/NewPermissionList", "name":"isystem-newPermissionList", "meta":{ "keepAlive":true, "title":"菜单管理" } } } ``` ### 路由聚合 image.png 1. children中的路由不会在左侧菜单栏中显示 2. children中的路由只能有一层 3. children中的路由所对应的组件, 聚合到tab栏组件中, 通过tab栏切换 4. redirect无效, tab栏顺序为children顺序, 默认展示第一个 ```json { path: '/account/settings', redirect: '/account/settings/notification', component: 'layouts/BlankLayout', alwaysShow: true, meta: { title: '个人设置', }, name: 'account-settings-Index', children: [ { path: '/account/settings/notification', component: 'account/settings/Notification', meta: { title: '新消息通知', }, name: 'account-settings-notification', }, { path: '/account/settings/base-setting', component: 'account/settings/BaseSetting', meta: { title: '基本设置', }, name: 'account-settings-base', }, { path: '/account/settings/binding', component: 'account/settings/Binding', meta: { title: '账户绑定', }, name: 'account-settings-binding', }, { path: '/account/settings/security', component: 'account/settings/Security', meta: { title: '安全设置', }, name: 'account-settings-security', }, { path: '/account/settings/custom', component: 'account/settings/Custom', meta: { title: '个性化设置', }, name: 'account-settings-custom', }, ], } ``` ### 列表页和详情页 1. 简单的详情页, 直接通过`抽屉`/ `模态框`渲染 2. 复杂的详情页, 通过`/id`跳转到新页面 image.png image.png ```json // 路由结构 举例 { "path": "/test", // 加载默认子路由, 不能有name "component":"layouts/BlankLayout", "meta": { "title":"测试页面", redirectDefaultChild: true, // 子路由不在菜单栏中显示, 进入子路由显示父路由高亮 }, "children":[ { "path": "", // 默认子路由 "name": "test-list", "component":"test/List", "meta": { "title":"测试页面列表页", // "keepAlive": true // 可以设置keepAlive使详情页回到列表页, 不重新渲染组件 }, }, { "path": "/test/:id", // 详情页 "name": "test-detail", "component":"test/Detail", "meta": { "title":"测试页面详情页" }, } ] } ``` ### 外链url跳转 image.png ```json { path: '/urllink', component: 'layouts/BlankLayout', name: 'baidu', meta: { title: 'url跳转', url: 'https://www.baidu.com', // url必须完整, 带http://或者https:// }, } ``` ## 动态权限 待补充完善 ## mock ### (本地)开发模式 1. 启动`yarn run dev`时, 会同时启动`mock-serve`, 与mock相关的接口会转发到`mock-serve` 2. mock文件示例 ```js // /mock/user.js const tokens = { admin: { token: 'admin-token', }, editor: { token: 'editor-token', }, } module.exports = [ // 登录接口 { url: '/user/login', type: 'post', response: config => { const { username } = config.body const token = tokens[username] // mock error if (!token) { return { errcode: 1, errmsg: 'Account and password are incorrect.', } } return { errcode: 0, data: token, } }, }, // 其他接口... ] ``` ```js // /mock/index.js // ... const user = require('./user') const mocks = [ ...user, ] // ... ``` ### (本地)生产模式 生产模式只能通过`mock.js`模拟数据, 与`mock-serve`共同使用同一份模拟数据 相关配置: ```js // /src/main.js if (process.env.NODE_ENV === 'production') { const { mockXHR } = require('./mock') mockXHR() } ``` 请求路径与mock.js中匹配的会被拦截, 但因为是重写了xhr方法, 无法在浏览器的network中看到请求, 可以通过控制台打印 ==所以上线前需要在`/mock/index.js`把后端已完成的相关接口注释掉, 或者直接在`src/main.js`注释掉mock功能== ### 数据切换 1. 本地开发模式: 修改`vue.config.js`中的`mockUrls` 2. 本地生产模式 注释掉`/mock/index.js`中的对应接口 ```js const mocks = [ ...user, // ...table ] ``` 全部取消: ```js // src/main.js // 注释掉这部分 // if (process.env.NODE_ENV === 'production') { // const { mockXHR } = require('../mock') // mockXHR() // } ``` ## ui组件, icon按需引入 1. ui组件在`src/utils/lazy/lazyAntd.js`中按需引入 2. ant-design-icon在`src/utils/lazy/icons.js`中按需引入 3. 其他icon: 1. 使用`Iconfont(阿里巴巴图标库)`下载svg图标, 统一放在`@/assets/icon/svg中` 2. 执行`yarn run svgo`去掉多余信息格式化图标 3. 使用``调用 ```html ``` ## 公共js , css, 工具js * 公共js, css: 框架里只保留最小使用 * 工具js: 通用性高的工具js和框架一起维护, 需要保持工具js的单一性, 完善注释 ## 体积较大的第三方js插件 例如 "excel"的导出插件, 统一放在`/src/vender`中 ## 泛用组件 例如"季度选择组件", 存放到gitlab公共组件仓库中 todo: - [ ] 打包成npm包 - [ ] git submodules ## 开发规范 ### 数据处理 * 后端接口获取的数据, 只取页面需要的, 不能直接存到data中 * 当某个接口的数据有多个页面使用时, 考虑存放到vuex/localstorage中 * 退出登录时, 需要考虑清空localStorage/sessionStorage中的敏感数据, 例如用户的登录信息, 需要更新的信息 ### 列表页跳详情页 优先使用抽屉, 模态框, 无法满足需求时才考虑跳转新页面 ### vue单文件的可维护性 建议不超过500行, 超过的需要考虑是否可以优化 > 1.抽离组件再引入 > 2.将css通过@import方式抽离再引入 > 3.将css/js/html全部抽离,只保留模块主入口文件再引入 ### ui组件 优先使用ant-design-vue, 按需引用 ### icon 优先使用ant-design icon , 按需引用 ### ui风格 1. 滚动条/滚动范围: 以表格为例: 横向溢出时, 内容加滚动条 ​ 纵向溢出, 随页面滚动, 表头页脚不固定(优先考虑分页) ### 组件通讯 * 父 -> 子 : props ```js //App.vue父组件 ``` * 子 -> 父 : $emit ```js // 子组件 // 父组件 ``` * 兄弟1 -> 兄弟2 : * 如果兄弟组件有共同的父组件(子 - 父 - 子), 通过父组件传递 * 如果链路传递超过2层, 优先考虑vuex模块( 孙 - 子 - 父 - 子) ```js // 参考 子 -> 父 父 -> 子 ``` * 链路传递超过2层, 优先考虑vuex模块 * 避免使用事件总线(待补充) * 使用插槽向引用组件传递模板 * 普通插槽 ```js // 父组件 注册 // 自定义组件 ``` * 作用域插槽 ```js ``` ## 数据持久化 ### localStorage **在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。** ```js let defaultCity = "上海" try { // 用户关闭了本地存储功能,此时在外层加个try...catch if (!defaultCity){ defaultCity = JSON.parse(window.localStorage.getItem('defaultCity')) } }catch(e){} export default new Vuex.Store({ state: { city: defaultCity }, mutations: { changeCity(state, city) { state.city = city try { window.localStorage.setItem('defaultCity', JSON.stringify(state.city)); // 数据改变的时候把数据拷贝一份保存到localStorage里面 } catch (e) {} } } }) ``` ### sessionStorage 参考`localStorage`