Vue后台管理系统
此项目是 vue + element-ui 构建的后台管理系统(单页面应用),所有的数据都是从服务器实时获取的真实数据,具有真实的登录、数据显示、新增数据、修改数据、删除数据等功能。
前端技术栈:vue + vuex + vue-cli + vue-r outer + ES6 + sass + element-ui
调试工具:devtools + postman
vue-cli搭建项目结构解析:
- node_modules 需要下载的所有依赖包
- public 静态资源服务器
- src 源码文件目录
- assets 静态资源
- main.js 入口文件,代码开始运行的地方(创建根容器、挂载路由系统)
- router.js 路由系统,用于实现单页面应用程序
- components 公共组件
- pages 页面组件
- store 仓库(包含应用中状态)
- utils 工具(联调接口)
- App.vue 根组件
- package.json 项目信息(记录依赖包)
- readme 项目解析笔记
- vue.config.js 解决跨域配置文件
路由系统解析:
1. 安装路由
cnpm install vue-router -S
2. src 根目录中新建一个 router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // 路由注册
export default new VueRouter({
routes: []
})
3.在 main.js 入口文件中,把路由系统挂载到Vue系统中去
import router from './router.js'
new Vue({
router
})
4.定义路由匹配规则
// 定义路由匹配规则
const routes = [
{ path: '/home', component: Home },
{ path: '/user', component: User },
{ path: '/find', component: Find }
]
5、 显示url匹配成功的视图(组件)
<router-view></router-view>
6.如何改变URL?
- **声明式路由导航:**建议使用vue-router内置 组件来实现。
- 注:默认用a标签显示,使用tag属性可以实现改变标签
- **编程式路由导航:**使用 路由api【$router.push()/replace()/back()】来实现页面跳转。
- 【编程式路由跳转的三种写法】
- 1、使用path来跳转 this.$router.push(’/useroiwioewoieoioiewoiewoiewoiewi’)
- 2、使用路由别名 this.$router.push(’/u’)
- 3、命名路由命名 this.$router.push({name:‘me’})
7、动态路由
路由匹配规则的写法:{path: ‘/detail/:id’, component: ‘’, props: true }
两种路由参数传参的方式:$route / props
8、三个命名
- 视图命名:当加了name属性时,在路由匹配规则定义时要使用 components 字段。
实际应用:登录页和Layout(系统内页)是有你无我的。他俩敌人(兄弟)关系。
但是Login页面也需要通过 /#/login 路由的方式进行访问,也需要把 Login加入到路由系统中。
为了避免Login 和 其它所谓的 pages 页面抢那个名字叫default的椅子(router-view)。
所以,当我们把Login加入到路由系统中时,给它一个VIP专座(名字叫login的router-view)来展示,放在App.vue中。
components: {
abc: Home,
efg: Find,
default: MusicList
}
- 路由别名:给路由匹配规则中的复杂path,取一个容易记忆的小名。
name: 'me'
- 路由命名:给路由匹配规则取一个名字。
alias: '/u'
9.路由懒加载
是一种性能优化的方案,是导入组件的一种写法,使用webpack代码分割功能和异步组件的特点来实现
const Home = ()=>import('./home/Home.vue')
const User = ()=>import('./user/User.vue')
const Find = ()=>import('./home/Find.vue')
异步组件
Vue.component('qf-async', (resolve, reject)=>{
setTimeout(()=>{
resolve({
template: `<div></div>`,
methods: {}
})
}, 2000)
})
10.嵌套视图
即,被 承载的组件中还可以再使用 ,形成嵌套关系。 在路由匹配规则中,使用 children 属性来实现。
{
path: '/find',
component: Find,
// 嵌套视图
children: [
{ path: '', component: FindPanelA },
{ path: 't2', component: FindPanelB }
]
}
状态管理工具解析
1. 如何理解“状态”?
在应用程序中,状态就是数据。 状态管理工具在Vue项目架构中是可选的。但是,从项目发展角度看,最好还安装、集成一下。 Vuex,它是Vue全家桶中最流利使用的状态工具。
2. 作用
- 组件之间数据共享
- 实现数据缓存
3. Vuex中的五大概念
- state 存储中心,所有需要被共享或缓存的数据,都在这里定义
- getters 相当于计算属性,过滤出来一些值,当它所关系的state变量发生变化时,会自动重新计算
- mutations mutations是Vuex中专门用于更新state
- actions 异步操作方法,与后端api打交道
- mudules 模块化
5. Vuex使用
- 安装Vuex
npm i vuex --save
- 引用Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
- 创建仓库Store
const store = new Vuex.Store({});
- 在 main.js 中挂载
import store from './store/index.js'
new Vue({
store
}).$mount('#app')
6. 如何优雅地使用Vuex来管理应用程序中的外部数据?
把外部数据定义在Vuex的state中,页面中就可以通过 $store.state来使用了。 封装api接口(参考utils/api.js) 在Vuex导入api方法,在actions中定义与后端交互的逻辑,把处理完成的数据通过mutations方法提交到state 在mutations中定义 更新state的方法。当state变化时,页面自动更新 那么actions在哪里被触发呢?在页面逻辑中触发它。
【温馨提示】:建议在组件使用 map* 系列方法来使用Vuex中的数据和函数 mapState 和 mapGetters 在写在 computed 中 mapActions 和 mapMutations 写在 methods 中
axios解析
是一个HTTP工具,用于与后端进行数据交互。 特点: 1、基于Promise封装的库 2、在客户端、Node端都可以使用
在项目中怎么使用呢? 1、cnpm install axios -S 2、一定要封装请求拦截器和响应拦截器。参考 utils/axios.js 3、最好把所有api统一封装可以复用的方法。参考 utils/api.js
浏览器同源策略阻止了ajax,怎么解决? 在项目根目录里新建vue.config.js文件,代码如下:
module.exports = {
devServer: {
// 用代理的方式来解决浏览器同源策略对ajax的限制
proxy: {
'/soso': {
target: 'https://c.y.qq.com', // 目标远程服务器
ws: true,
changeOrigin: true // 允许改变“域”
}
}
}
}
生命周期解析
- beforeCreate 这是第一个生命周期函数;此时,组件的data和methods以及页面DOM结构,都还没有初始化;所以此阶段,什么都做不了。
- created 这个组件创建阶段第二个生命周期函数,此时,组件的data和methods已经可以用了;但是页面还没有渲染出来;在这个生命周期函数中,一般会发起Ajax请求。
- beforeMount 当模板在内存中编译完成,会立即执行实例创建阶段的第三个生命周期函数beforeMount,此时内存中的模板结构,还没有真正渲染到页面上;此时,页面上看不到真实的数据,用户看到的只是一个模板页面而已。
- mounted mounted是组件创建阶段最后的一个生命周期函数;此时,页面已经真正的渲染好了,用户可以看到真实的页面数据了;当这个生命周期函数执行完,组件就离开了创建阶段,进入到了运行中的阶段;如果使用到一些第三方的UI插件,而且这个插件还需要被初始化,那么,必须在mounted中来初始化插件。
- beforeUpdate 在执行beforeUpdate运行中的生命周期函数的时候,数据肯定是最新的;但是页面上呈现的数据还是旧的。
- updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以这时可以执行依赖于 DOM 的操作。
- beforeDestroy 当执行beforeDestroy的时候,组件即将被销毁,但是还没有真正开始销毁,此时组件还是正常可用的;data、methods等数据或方法,依旧可以被正常访问。
- destroyed 组件已完成了销毁,组件无法使用,data和methods都不可使用。
项目开发
页面渲染
此项目为单页面应用,一切皆组件,路由系统原理是当路由发生变化,切换main内容组件,可以通过watch监听路由系统url的变化。
- 布局渲染::在omponents/common文件夹里写父组件QfLayout 子组件QfAsaide(左侧菜单栏)、QfHeader(右侧顶部)、QfMain(右侧主体显示url匹配组件),然后统一在index.js里抛出。在App.vue里导入并应用QfLayout组件完成基本布局渲染。
- 菜单栏: 遍历pages/index.js里的路由数组,拿到text完成渲染,在路由数组里添加isNotNav键实现控制是否渲染成菜单,添加exact键控制是否精准匹配实现高亮显示。
- 主体: 通过 渲染在QfMain,再导入到QfLayout,通过App.vue使用(椅子)渲染到页面。
- 登录页 登录页和Layout(系统内页)是(兄弟)关系,不同时存在。为了避免Login 和 其它所谓的 pages 页面抢那个名字叫default的椅子(router-view)。所以,当我们把Login加入到路由系统中时,给它一个VIP专座(名字叫login的router-view)来展示,放在App.vue中。它俩的胜负关系,由$route.path===’/login’来决定胜负。
<router-view v-if='$route.path==="/login"' name='login'></router-view>
<QfLayout v-else></QfLayout>
功能技术分析
使用嵌套路由实现find页面tab选项卡切换效果
//定义路由规则
{
id: 1002,
path: '/find',
component: Find,
text: '公司新闻',
exact: false,
// 嵌套路由
children: [
{ path: '', component: FindPanelA }, // /find/
{ path: 't2', component: FindPanelB } // /find/t2
],
}
//渲染
<router-link tag='span' exact-active-class='on' to='/find/'>今日</router-link>
<router-link tag='span' exact-active-class='on' to='/find/t2'>昨日</router-link>
使用状态管理实现TodoList效果
- 添加任务: 在todo.vue里的input标签添加回车事件confirm,然后在methods里定义回车事件confirm,在confirm事件里向Vuex提交添加任务的申请并传参,在todo.js的state里定义list空数组,在mutations里写定义的任务并接受参数,每当页面confirm事件触发,就会提交添加任务申请,就会触发mutation定义好的任务,在任务里向list空数组里添加页面v-model双向绑定的数据,循环list拿到数据即可渲染在页面上。
// 向状态管理中添加一条任务
confirm() {
// 向Vuex提交添加任务的申请
// this.$store.commit('addTask', this.task)
this.addTask(this.task)
this.task = ''
},
mutations: {
addTask(state, payload) { // 负荷
console.log('addTask', state, payload)
state.list.push({task: payload, id: Date.now()})
},
}
- 删除任务: 在todo.vue组件里的span标签添加点击事件remove,然后在methods里定义remove事件向Vuex提交删除任务的申请并传ID作为参数,在todo.js的mutations里写定义的删除任务并接受参数,使用数组的filter方法删除指定ID的任务。
// 删除一条任务
remove(item) {
// 向Vuex提交删除任务的申请
// this.$store.commit('delTask', item.id)
this.delTask(item.id)
}
mutations: {
delTask(state, payload) {
state.list = state.list.filter(ele=>ele.id!==payload)
}
},
- 计算任务数量: 在getters里定义total方法并传state为参数计算list数组长度(任务数量),state相关变量发生变化,getters里跟state相关的变量就会重新计算,用v-text把tatol渲染在页面上完成计算功能。
getters: {
total(state) {
return state.list.length
}
},
调取接口实现简版QQ音乐歌曲搜索
- src目录里新建utils文件表示工具文件,新建axios.js文件,在里面导入axios模块,并且创建一个axios实例,封装请求拦截器(在请求被send出去之前)跟响应拦截器(当后端返回数据,抵达客户端之前时)。
- 新建api.js,导入axios实例,定义fetchQqMusic方法联调接口并抛出。
- 创建子store(music.js)文件并抛出,然后在实例上导入。
export function fetchQqMusic(params) {
return fetch({
url: '/soso/fcgi-bin/client_search_cp',
method: 'GET',
params
})
}
export default {
fetchQqMusic,
}
- 状态管理流程
- 在MusicList.vue使用定义好的getMusic方法并传接口地址参数。难点:查询字符串转换成json对象,附代码。
- 在子store(music.js)中,用getMusic方法接收接口地址参数,并用fetchQqMusisc方法调接口拿到音乐列表数据,在actions里commit一个updateMusicList方法并传音乐列表参数,用来更新音乐列表。
- 在mutations里定义updataMusicList方法更新音乐列表。
//查询字符串转换成json对象
let params = {}
str.split('&').map(ele=>{
let arr = ele.split('=')
params[arr[0]] = arr[1]
})
项目经验
- 工作中不用把所有文件搞明白,先弄懂全局的东西(vue.config.js/README.md/package.json/main.js/App.vue/及utils封装的api)
- 工作中为了调试效果一般先需要假数据
- 管理系统中不要用浮动操作
- 要写低耦合(动态)代码,即改一个地方就把所有需要用的地方都改,要学会改造代码,该循环循环,该封装的地方封装。
- 做导航页面跳转,如果需要做高亮样式就用声明式导航(router-link)做,如果需要做业务逻辑,就用编程式导航做(this.$router.history.push/replace/back(’’))。
- 在vue中封装方法不要用delete,deletey是一元操作符,定义删除方法用remove。
- 使用状态管理时,不要直接在页面中操作state,要使用mutations更新state,这样Devtools才能有记录。
- 在传递参数的时候,中文会被编译成字符串,可以使用decodeURI反编码
- 在computed里导入并使用vuex中的mapState/mapGetters方法,可以把子store中的需要用到的变量作为json对象映射进当前页面,在当前页面直接用this.变量名就可以调用,达到简化代码的目的。
- 在methods里导入并使用vuex中的mapActions/mapMutations方法,可以把子store中的需要用到的方法作为json对象映射进当前页面,在当前页面直接用this.方法名就可以调用,达到简化代码的目的。
- 在methods里导入并使用vuex中的mapActions方法,可以把子store中的需要用到的方法作为json对象映射进当前页面,在当前页面直接用this.方法名就可以调用,达到简化代码的目的。