目录
本文中使用的vue版本是 2.x
vue全家桶(技术栈)
- vue-cli:脚手架,用于快速搭建vue项目
- vue-router:路由,单页应用的核心插件
- vuex:状态管理,可管理全局数据。小项目用vuex有点繁琐,建议大项目用vuex,小项目用bus总线
- axios:对ajax的封装
使用vue-cli搭建Vue项目
1、准备工作。如果之前已安装,可以跳过这一步
- 安装npm
- 安装webpack、vue-cli
#最新稳定版
npm install webpack -g
npm install @vue/cli -g
2、使用vue-cli搭建vue项目
#初始化项目
vue create 项目名
#选择自定义,空格键选中、取消选中
#选择babel、router、vuex(根据需求)
#使用history模式的路由
#In dedicated config files,babel等配置写在单独的文件中
3、项目根目录下新建 vue.config.js ,写自定义的配置。示例
module.exports={
configureWebpack:{
devServer:{
port:9000, //使用的端口号,默认8080
open:true, //自动在浏览器中打开页面,默认false关闭
}
}
}
项目启动时会自动加载根目录下的vue.config.js,vue.config.js中配置项的写法可参考
https://cli.vuejs.org/zh/config/#vue-config-js
单页的编写
1、替tab栏图标 public/favicon.ico,根据需要修改public/index.html
2、在App.vue中写页面的公共部分
3、在views中写分页,Xxx.vue
4、要在多个分页中使用的部分,写成组件(Xxx.vue)放到components下,在分页中引用
5、静态资源放在assets目录下,在assets新建css、js、img文件夹来存放
Xxx.vue的基本骨架:<template>、<script>、<style>
<template>
<div>
<HelloWorld msg="hello"/> <!-- 使用组件,父子组件之间可以通过属性传递数据 -->
</div>
</template>
<script>
// 引入components下的组件(相当于声明组件),@是/src的别名
import HelloWorld from '@/components/HelloWorld.vue'
//不需要new Vue,然后在里面写data、methods,直接在export default中写即可
export default {
name: 'Home', //当前组件的名称
components: { //注册当前组件中要使用的其它组件
HelloWorld //有多个时逗号分隔
},
data(){
},
methods:{
},
}
</script>
<!-- <style scoped> 如果加了scoped属性,则定义的样式只在当前.vue中有效 -->
<style>
</style>
vue代码提示常用插件:Vue 2 Snippets(推荐)、VueHelper
4、在router/index.js中配置路由(分页)
//导入首页
import Login from '../views/Login.vue'
//配置分页的路由
const routes = [
{
path: '/', //配置首页
name: 'Login',
component: Login //下面懒加载的组件都是写成字符串格式,首页是直接写的组件,所以开头用了import导入
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register.vue') //除了首页,分页都使用懒加载,防止加载首页时卡顿
}
]
一个组件可以同时配置多个路由,即对应多个路径,copy后改下path即可,在组件中用 this.$route.path 可以获取当前使用的哪个 path 。
axios的使用
安装axios
npm install axios --save
在main.js中导入axios
import axios from 'axios'
// 挂载到Vue原型上,属性名常用$axios、$http
Vue.prototype.$http = axios
//axios配置
axios.defaults.baseURL = 'http://localhost:8080/msg-server' //请求url公共前缀
axios.defaults.timeout = 10000 //超时时间 ms,默认0 不限制
//withCredentials = false //发起跨域请求时是否发送cookie,默认false
说明
//如果前后端部署时使用相同的域名,比如使用的是模板引擎,可以使用 window.top.location.origin 直接获取当前js文件所在域名
axios.defaults.baseURL = window.top.location.origin + '/msg-server'
//以上是配置全局axios,也可以配置使用axios的某个实例
const axiosInstance = axios.create({
baseURL: 'http://localhost:8080/msg-server',
timeout: 10000
});
Vue.prototype.$http = axiosInstance
更多配置项可参考:https://github.com/axios/axios#request-config
axios使用示例
//get方式,请求参数放在config的params参数中,可以写成map形式,也可以直接 this.reqVo 之类传对象过去
//后端用基础类型直接接收,不用标注什么注解
this.$http.get("/task/send", {params: {username: 'chy', age: 20}})
//this.$http.get("/task/send", {params: this.reqVo})
.then(resp => {
console.log(resp.data);
});
.catch(err => {
console.log(err);
});
//post方式,第二个参数是data,即要传递的数据,可以写成map形式,也可以直接 this.reqVo 之类传对象过去
//后端需要使用实体类接收,并在参数前标注 @RequestBody
this.$http.post("/task/send", { username: "chy", age: 20 })
//this.$http.post("/task/send", this.reqVo)
.then(resp => {
console.log(resp.data);
})
.catch(error => {
console.log(error);
});
使用axios上传文件并携带其它参数
// 配置请求头,允许上传文件。默认的Content-Type是 'application/json'
let config = {headers:{'Content-Type':'multipart/form-data'}}
let param=new FormData();
//文件
param.append("file",this.uploadFile);
//要携带的其它参数
param.append("username",this.username);
//FormData不能直接添加一个数组,只能逐个添加数组中的元素,append(key,value),key相同时会把value放在一个数组中
this.friendList.forEach(ele => {
param.append("friendList",ele);
});
// 向后端发起请求
this.$http
.post("/task/add", param, config) //参数、请求头配置
.then(resp => {
console.log(resp.data);
})
.catch(error => {
console.log(error);
});
参数是(url,param,config),url必需,param,config可选;后端接收参数时不必用 @RequestBody、@RequestParam 标注形参。
vuex的使用
vuex是一个状态仓库,用于存储、管理全局数据,常用于组件、页面之间的数据传递、共享
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: { //在state中写要存储的变量
token:'',
cart:[]
},
mutations: { //在mutations中写操作变量(数据)的方法。state会自动传入
setToken(state,token){
state.token=token
}
},
actions: {
},
modules: {
}
})
main.js中自动引入了vuex的store,可以在项目中直接用store
import store from './store'
使用示例
//存储到vuex中,第一个参数指定方法名,后续的参数指定实参
this.$store.commit('setToken',resp.data.token);
//取值
this.$store.state.xxx
如果要清空vuex中的某个变量,可以this.$store.commit()传入空值,比如对象赋为空对象{ },数组赋为空数组[ ],也可以在store/index.js的mutations中写一个清空变量的方法,暴露给外部调用。
vuex中数据的保留时间
通过vue路由进行跳转时,vuex中的数据不会丢失,但刷新单页中的某个页面时 vuex中的数据会全部丢失,通常要把vuex中的数据保存在本地存储 localStorage 或 cookie 中。
userId、token、deviceId 之类体积小、服务端常用的数据,可以保存在cookie中 方便传给服务端;购物车的商品、搜索历史记录之类体积大、服务端不常用的数据,没必要保存在cookie中 每次都传给服务端,建议保存在 localStorage 中。
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let store = new Vuex.Store({
state: {
token:'',
cart: JSON.parse(localStorage.getItem('cart')) || [], //先从本地存储中获取,如果没有才置为空数组
},
mutations: {
setToken(state,token){
state.token=token
}
},
actions: {
},
modules: {
}
})
//监听mutations中的方法调用,每次调用mutations中的方法时,都会将指定数据更新、保存到本地存储中
store.subscribe((mutations,state)=>{
//可以缺省window
window.localStorage.setItem('cart',JSON.stringify(state.cart))
})
//将store模块暴露出去
export default store
axios的全局拦截
用axios请求数据时,往往需要拦截做一些额外处理,比如
- 拦截请求,在请求头中添加 token
- 拦截响应,如果响应码表示 token 已过期、不合法,则跳转到登录页;如果状态码表示服务端错误、超时,则提示xxx
src下新建 axios-interceptors.js
import axios from 'axios'
import store from './store'
import router from './router'
//设置axios拦截器
export default function setAxiosInterceptors(){
//拦截请求,在请求头中添加 token
axios.interceptors.request.use(
config => {
if(store.state.token){
//尽量用预定义的请求头字段Authorization,不要用自定义的字段 config.headers.token,否则控制台会有相关报错
//js对象用 config.headers.Authorization 或 config.headers['Authorization'] 访问都行
//前端传递 Authorization 时往往会拼接 Bearer 之类的前缀指定验证方式,服务端从Authorization获取token时需要先剔除对应的前缀
config.headers.Authorization = 'Bearer ' + store.state.token
}
return config
}
)
//拦截响应
axios.interceptors.response.use(
response => {
if(response.status === 200){
const data = response.data
//服务端返回的响应码表示token过期、不合法时
if(data.code === '1001' || data.code === '1002'){
//清空vuex、localstorage保存的token
store.commit('settoken', '')
localStorage.removeItem('token')
//跳转到login页面。使用replace(),不插入到历史记录中
router.replace({path:'/login'})
}
return data
}
return response
}
)
}
上面直接配置的 import axios from 'axios'
导入的axios,是全局配置axios拦截器,也可以把axios换成某个axios实例 axiosInstance,针对axios某个实例进行配置。
更多配置可参考:https://github.com/axios/axios#interceptors
在main.js中设置axios的拦截
import setAxiosInterceptors from './axios-interceptors'
setAxiosInterceptors()
axios的跨域问题
参考:https://blog.csdn.net/chy_18883701161/article/details/108325424
全局常量
//在 main.js 中挂载到Vue原型上
Vue.prototype.XXX='xxx';
//在 .vue 文件的js脚本中可通过 Vue.prototype 来引用
let xxx = Vue.prototype.XXX;
//如果是在Vue实例中,或者可解析的html元素中,可直接通过 this 引用
let xxx = this.XXX;
<el-upload :action="this.XXX">{{this.XXX}}</el-upload>
使用Mock模拟后端接口返回数据
mock常用于前后端分离的项目,后端还没写好暴露给前端的接口时,前端可以用mock模拟后台接口返回数据进行调试,使前端可以独立开发,不必停下开发进度等后端。
嵌套路由
嵌套路由用于在一个页面中再嵌套页面
eg. /phone/apple,/phone/huawei,/phone/xiaomi
<!-- 在一级路由中给二级路由预留位置 -->
<router-view />
{ //一级路由
path: '/phone',
name: 'Phone',
component: Phone,
children:[ //二级路由(嵌套路由),path开头不加/
{
path: 'apple',
name: 'Apple',
component: () => import('../views/Apple.vue')
},
{
path: 'huawei',
name: 'Huawei',
component: () => import('../views/Huawei.vue')
}
]
},
路由守卫
路由守卫可以在单页的路由跳转(包括刷新当前页面)时做一些前处理。
eg. 跳转到某些页面时,先校验用户是否已登录、是否有指定权限,满足条件才跳转
router.js
{ //一级路由
path: '/msg',
name: 'Index',
component: Index,
meta:{
requireAuth: true, //自定义的标志,为true时需要登录才能访问该页面
}
}
main.js
//路由守卫,beforeEach()是路由前置钩子函数。next指定要执行的方法
router.beforeEach((to,from,next)=>{
//刷新页面时vuex的数据会丢失,需要重新设置
store.commit('settoken',localStorage.getItem('token'))
if(to.meta.requireAuth){
if(store.state.token){
next() //跳转到目标页面,()中可以传递参数
}else{
next({ //跳转到登录页面
path:'/login',
query:{redirect:to.fullPath}
})
}
}else{
next()
}
})
执行的都是next(),不设置参数的next()默认是跳转到目标页面;设置了参数的可以跳转到指定页面
引入jq(不推荐)
vue本来就可以视图-内存数据双向绑定,修改内存数据时自动同步更新视图,没有必要再用 jq 更新视图,vue也提供了异步请求,使用vue的同时引入jq有点鸡肋,了解即可。
1、安装jq的依赖
npm install jquery --save
2、在vue.config.js中添加配置
const webpack = require('webpack')
module.exports = {
configureWebpack:{
devServer:{
port:9000,
open:true,
}
},
//声明jq的$、jQuery
chainWebpack: config => {
config.plugin('provide').use(webpack.ProvidePlugin, [{
$: 'jquery',
jQuery: 'jquery'
}])
},
}
3、在main.js中引入jq
import $ from 'jquery'
4、使用示例
<script>
$(function () {
//jq代码
$('#btn').click(function () {
alert('ok');
});
});
export default {
};
</script>
注意:需要把jq代码放在$(function () { }中才有效,因为vue使用的是虚拟dom,jq代码要在dom加载完成后才能执行
运行、调试
cd到项目根目录下
#如果是刚从 svn|git 拉取的项目,先安装所需依赖
#npm install
#运行
npm run serve
chrome安装插件 Vue.js devtools用于调试vue-cli项目,此插件只能调试vue-cli项目,如果只是在模板引擎之类的页面中使用Vue,则无法使用此插件进行调试。
打包
在vue.config.js中配置publicPath、assetsDir
const webpack = require("webpack");
module.exports = {
publicPath: "/mall-admin/", //部署到哪个应用中,即项目url的根目录
assetsDir: "static", //静态资源打包到哪个目录下
configureWebpack: {
devServer: {
port: 9000,
open: true,
},
},
chainWebpack: (config) => {
config.plugin("provide").use(webpack.ProvidePlugin, [
{
$: "jquery",
jQuery: "jquery",
},
]);
},
};
- publicPath:要部署到域名下的哪个目录(应用)中,默认/ 直接部署到域名下。
- outputDir:输出目录,默认dist
- assetsDir:静态资源的打包输出目录,输出到outputDir的哪个目录下,默认’’ 直接输出到outputDir下
配置参考:https://cli.vuejs.org/config
#构建生产版,会输出到dist文件夹
npm run build
部署
参考:https://cli.vuejs.org/zh/guide/deployment.html
常见问题
堆栈溢出
Uncaught RangeError: Maximum call stack size exceeded
常见原因:UI框架的标签和我们自定义的vue组件重名了,修改我们自定义的组件名称即可
路由重复
history路由模式,顾名思义,会记录请求url历史,如果请求的url就是上次路由的url,则不会再次发起请求,除非目标 url 改变或传递给目标 url 的参数发生改变。
使用history路由模式时,重复点击路由到同一页面,虽然页面显示正常,但控制台会报错
vue-router.esm.js?8c4f:2062 Uncaught (in promise) Error: Avoided redundant navigation to current location: “/xxx”.
解决方式:在router/index.js中添加
// 解决history模式,路由重复的问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
可以加在router/index.js中,也可以加在其它文件中,只要该文件导入了VueRouter即可
import VueRouter from 'vue-router'
路由传参,参数不刷新
这是组件复用时经常发生的问题,在组件创建后从后端加载数据,后续再次使用这个组件,只要参数不变就不会重新创建,显示的还是之前的数据。
常见的解决方案有2个
- 使用动态url,比如拼接一个时间戳
- 把组件初始化时加载数据的代码抽出来,作为方法放到 methods 中,监听 $route 对象中某个特征属性的变化,常见的比如path,发生变化时调用加载数据的方法重新加载数据。即原先的初始化操作不放在created()之类的方法中,改为放在数据监听中。
详细信息可参考:https://blog.csdn.net/chy_18883701161/article/details/106723292
手动调用methods中的方法时,提示方法未定义
需要通过this来调用methods中的方法
mounted() {
// 加this
this.getTaskList();
},
methods: {
getTaskList() {
},
toAdd() {
// 加this
this.getTaskList();
},
}
报错 1 error, 13 warnings…fixable with the --fix
option 确定代码本身没有问题
✖ 14 problems (1 error, 13 warnings)
0 errors and 13 warnings potentially fixable with the--fix
option.
解决方案:package.json
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
#原来是 vue-cli-service lint,把lint换为 eslint --fix --ext .js,.vue src
"lint": "vue-cli-service eslint --fix --ext .js,.vue src"
},
配置生效问题
修改src/main.js、vue.config.js后,需要重启项目才会生效
vue-cli 3.x及之后的项目中没有webpack的配置文件
从vue-cli 3.x开始,用vue.config.js代替了webpack的配置文件,可以把webpack的配置写在vue.config.js中。
配置写法参考:https://cli.vuejs.org/zh/guide/webpack.html
axios传到后端的参数都是null、或无法传递数组
参考:https://blog.csdn.net/chy_18883701161/article/details/106732406
版本控制与依赖
一般不把 node_modules 上传到svn或者git仓库中,拉取项目后需要先安装项目依赖
#安装项目依赖
npm install
过渡(滑动)效果
页面切换时,一般不直接切换,写一个过渡效果(滑动),使用户体验更好