20 KiB
[TOC]
前端通用框架
框架介绍
基于vue@2.6
, ant-design-vue@1.7
, axios
, vue-router
, vuex
, webpack@4.x
的通用后台管理系统
功能:
- 根据路由动态生成侧边导航
- 使用
mock.js
和mock-serve
进行本地mock - 集成
eslint
和prettier
配置 - 集成
commit插件
- 集成
git
钩子, 必须通过eslit
代码检查和commit
提交规范检查才能提交代码 ant-design-vue
组件按需引入, ant-design icon 按需引入- 响应错误统一处理
vue-ls
本地持久化- 支持设置主题
- 权限自定义指令
not Support
- ie11 兼容
- h5 兼容
- 响应式
快速开始
推荐使用vscode开发, 需要安装prettier
, eslint
, vetur
插件
# 需要全局安装commitizen才能使用 git cz
npm install commitizen -g
# 克隆项目
git clone xxx
git cd xxx
# 安装插件
yarn
# 启动服务
yarn run dev
# 打包
yarn run build
目录结构
├─.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组件的存放路径:
- 框架相关的组件, 存放在
@/components
- 布局相关的, 存在在
@/components/layouts
- 不相关的两个页面的公共组件, 存放在
@/viewmodules
- 父子关系/兄第关系的页面的公共组件, 且没有其他页面使用, 存放在他们的公共根目录下
@/views/xxx/components
动态路由
路由菜单不要超过3层

路由生成

路由配置项:
{
// "hidden": true, // (默认不传)不在左侧菜单中显示
// "redirect":null, // (默认不传)重定向
// "alwaysShow": false, // (默认不传)路由聚合, 从menu菜单展示变为tab栏展示
"path":"/dashboard/analysis", // 路径
"component":"dashboard/Analysis", // 组件
"name":"dashboard-analysis", // 路由名称, 使用<keep-alive>时必填
"meta":{
"keepAlive":true, // 是否缓存
"icon":"home", // 图标
"title":"首页", // 侧边栏和面包屑中展示的名字
// "url": "http://www.baidu.com" // (默认不传)当外部链接跳转时必须传, 必须带http:// 或者httPs://
},
},
举例:
"系统管理"为一级菜单
"用户管理"为子菜单
{
"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":"菜单管理"
}
}
}
路由聚合

- children中的路由不会在左侧菜单栏中显示
- children中的路由只能有一层
- children中的路由所对应的组件, 聚合到tab栏组件中, 通过tab栏切换
- redirect无效, tab栏顺序为children顺序, 默认展示第一个
{
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',
},
],
}
列表页和详情页
- 简单的详情页, 直接通过
抽屉
/模态框
渲染 - 复杂的详情页, 通过
/id
跳转到新页面


// 路由结构 举例
{
"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跳转

{
path: '/urllink',
component: 'layouts/BlankLayout',
name: 'baidu',
meta: {
title: 'url跳转',
url: 'https://www.baidu.com', // url必须完整, 带http://或者https://
},
}
动态权限
待补充完善
mock
(本地)开发模式
-
启动
yarn run dev
时, 会同时启动mock-serve
, 与mock相关的接口会转发到mock-serve
-
mock文件示例
// /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,
}
},
},
// 其他接口...
]
// /mock/index.js
// ...
const user = require('./user')
const mocks = [
...user,
]
// ...
(本地)生产模式
生产模式只能通过mock.js
模拟数据, 与mock-serve
共同使用同一份模拟数据
相关配置:
// /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功能==
数据切换
-
本地开发模式:
修改
vue.config.js
中的mockUrls
-
本地生产模式
注释掉
/mock/index.js
中的对应接口const mocks = [ ...user, // ...table ]
全部取消:
// src/main.js // 注释掉这部分 // if (process.env.NODE_ENV === 'production') { // const { mockXHR } = require('../mock') // mockXHR() // }
ui组件, icon按需引入
-
ui组件在
src/utils/lazy/lazyAntd.js
中按需引入 -
ant-design-icon在
src/utils/lazy/icons.js
中按需引入 -
其他icon:
-
使用
Iconfont(阿里巴巴图标库)
下载svg图标, 统一放在@/assets/icon/svg中
-
执行
yarn run svgo
去掉多余信息格式化图标 -
使用
<svg-icon icon-class="xxx" />
调用
-
<svg-icon style="font-size: 18px; color: pink" icon-class="svgtest" />
公共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风格
-
滚动条/滚动范围:
以表格为例: 横向溢出时, 内容加滚动条
纵向溢出, 随页面滚动, 表头页脚不固定(优先考虑分页)
组件通讯
-
父 -> 子 : props
//App.vue父组件 <template> <div id="app"> <users :users="users"></users> <!-- 前者自定义名称便于子组件调用,后者要传递数据名 --> </div> </template> <script> import Users from "./components/Users" export default { name: 'App', data(){ return{ users:["Henry","Bucky","Emily"] } }, components:{ "users":Users } } //users子组件 <template> <div class="hello"> <ul> <li v-for="user in users">{{user}}</li> <!-- 遍历传递过来的值,然后呈现到页面 --> </ul> </div> </template> <script> export default { name: 'HelloWorld', props:{ users:{ //这个就是父组件中子标签自定义名字 type:Array, required:true } } } </script>
-
子 -> 父 : $emit
// 子组件 <template> <header> <h1 @click="changeTitle">{{title}}</h1> <!-- 绑定一个点击事件 --> </header> </template> <script> export default { name: 'app-header', data() { return { title:"Vue.js Demo" } }, methods:{ changeTitle() { this.$emit("titleChanged","子向父组件传值");//自定义事件 传递值“子向父组件传值” } } } </script> // 父组件 <template> <div id="app"> <app-header v-on:titleChanged="updateTitle" ></app-header> <!-- 与子组件titleChanged自定义事件保持一致 --> <!-- updateTitle($event)接受传递过来的文字 --> <h2>{{title}}</h2> </div> </template> <script> import Header from "./components/Header" export default { name: 'App', data(){ return{ title:"传递的是一个值" } }, methods:{ updateTitle(e){ //声明这个函数 this.title = e; } }, components:{ "app-header":Header, } } </script>
-
兄弟1 -> 兄弟2 :
- 如果兄弟组件有共同的父组件(子 - 父 - 子), 通过父组件传递
- 如果链路传递超过2层, 优先考虑vuex模块( 孙 - 子 - 父 - 子)
// 参考 子 -> 父 父 -> 子
-
链路传递超过2层, 优先考虑vuex模块
-
避免使用事件总线(待补充)
-
使用插槽向引用组件传递模板
-
普通插槽
// 父组件 <MyButton>注册</MyButton> // 自定义组件 <button> <slot>按钮</slot> </button>
-
作用域插槽
<current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> <template v-slot:other="otherSlotProps"> ... </template> </current-user>
-
数据持久化
localStorage
在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
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