跨域常见处理
一、CORS(跨域资源共享),后台需要在响应头中设置 Access-Control-Allow-Origin 可接受的域和是否允许携带 cookie,如果后台需要使用 cookie 验证,前端 axios 的withCredentials 的值需要设置为 true,默认为 false,如果后台不允许携带 cookie,withCredentials 为 true 会报跨域问题
二、nginx 反向代理,利用 nginx 反向代理则是在同一域请求,则不会出现跨域问题,后台需要在 nginx 进行额外配置,vue 在开发环境测试时配置 proxyTable 可以实现代理解决跨域
三、JSONP,以前 jquery 等解决跨域方案,利用 script 标签(script 的 src 不受同源策略约束来跨域获取数据),添加回调函数 jsonpCallback:"jsonp_handler(回调函数名称)"信息返回给后台,后台通过这个函数把数据信息返回,不过只能发起 get 请求,返回的数据有时因为不是严格的 json 格式,无法使用 JSON.parse 转换,就只能使用 eval("(" + res.data + ")") 来转换
axios
axios 的 post 和 put 请求需对数据 qs.stringify 将对象序列化,否则后台接收数据为空,由于 axios 默认发送数据时,数据格式是 application/json,而并非我们常用的 x-www-form-urlencoded 表单格式,后端未必能正常获取到,所以在发送之前,如果后台接收格式是 x-www-form-urlencoded 则需要使用qs模块对其进行处理,可以在 axios 请求拦截统一处理:
// 请求拦截器
axios.interceptors.request.use( config => {
if((config.method === 'post' || config.method === 'put') &&
config.headers['Content-Type'] == "application/x-www-form-urlencoded;charset=UTF-8"){
config.data=qs.stringify(config.data);
}
if(sessionStorage.getItem('token')){
config.headers['Authorization'] = sessionStorage.getItem('token')
}
return config;
},
error => { // 对请求错误做些什么
return Promise.reject(error);
}
)
qs.stringify({ a: ['b', 'c', 'd'] });// 'a[0]=b&a[1]=c&a[2]=d'
qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });// 'a=b&a=c&a=d'
qs.stringify({ id: ['b', 'c'] }, { arrayFormat: 'indices' })// 'id[0]=b&id[1]=c'
qs.stringify({ id: ['b', 'c'] }, { arrayFormat: 'brackets' })// 'id[]=b&id[]=c'
qs.stringify({ id: ['b', 'c'] }, { arrayFormat: 'repeat' })// 'id=b&id=c'
axios 传递字段数据时,当数据为 null 时不会传递该字段
axios的onUploadProgress上传文件事件会在浏览器接收新数据期间周期性地触发,我们就可以为用户创建一个进度条或者加载器:
uploadFile (param,callback){
return axios.post('/file/upload',param,{
headers : {
"Content-Type": "multipart/form-data;charset=UTF-8"
},
onUploadProgress: progressEvent => {
callback(progressEvent);
}
})
}
scss 使用
先安装有关 scss 的包 npm install sass-loader node-sass sass-resources-loader --save-dev,然后在 build->utils 中设置 scss 的默认导入文件( vue 修改了配置文件需要重启服务):
scss: generateLoaders('sass').concat( {
loader: 'sass-resources-loader',
options: { resources: path.resolve(__dirname, '../src/assets/css/base.scss') }
})
也可以在 app.vue 直接导入,安装 node-sass 依赖时,会从 github.com 上下载 .node 文件。由于国内网络环境的问题,这个下载时间可能会很长,甚至导致超时失败,推荐使用淘宝镜像源 set SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ && npm install node-sass下载
scss 的深度作用选择器 /deep/ 可以在组件样式的设置、覆盖(自己定义的组件和第三方的组件如 elementui 等)和 v-html 等环境中使用
vue 中 js 调用 scss 的变量,在 scss 文件中用 :export 导出你要使用的变量对象,然后在你要导入的文件中引入这个文件
图片路径
图片在 assets 文件夹,需要使用 require()进行引入,图片在static文件夹,可以直接使用绝对路径,因为使用 assets 下面的资源,在js中使用的话,路径要经过 webpack 中的 file-loader 编译,路径不能直接写,而 static 文件夹中是不会被 wabpack 处理的,它们会被直接复制到最终的打包目录下面(默认是 dist/static ),且必须使用绝对路径来引用这些文件
Promise
Promise 传递异步操作的消息 ,可以将异步操作队列化,按照期望的顺序执行:
function asyncFunction() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('Async Hello world');
}, 1000)
})}
asyncFunction().then(function (data) {
console.log(data) //通过返回的data数据进行下一步操作
}).catch(function (error) { console.log(error);})
new Promise(() => {}).then() , new Promise() 这一部分是一个构造函数,这是一个同步任务,后面的 .then() 是一个异步微任务即先 new 一个实例,再调用方法
当遇到发送多个请求并根据请求顺序获取和使用数据的场景,可以使用 Promise.all,例如一个 Promise 失败会影响所有 Promise 回调执行以及 promise.all 会有一些阻塞的情况,即一个失败后续的 .then 都会停止,几个异步操作是强相关的,后续步骤必须依赖这几个步骤全部成功才能进行
async/await 本质上还是基于 Promise 的一些封装,await 等待的是一个表达式,这个表达式的返回值可以是一个 promise 对象也可以是其他值,await 会暂停当前 async 函数的执行,等待后面的 Promise 的计算结果返回以后再继续执行当前的 async 函数,await 等待的不是所有的异步操作,等待的只是 Promise,你如果没有给他返回个 Promise,那它还是会继续向下执行,达不到想象的结果,async/await 就是要完善 Promise 还不够完美的地方,如:
async function testAsync() {
return new Promise(function (resolve, reject) {
// 异步操作
setTimeout(()=>{
resolve('hello')
}, 1000) })}
async function test() {
const hello = await testAsync()
const world = 'world' //1s之后hello world
console.log(hello)
console.log(world)
}
test()
vue 打包配置
样式和路径不显示:
一、build->utils.js,fallback: 'vue-style-loader' 后添加 publicPath: '../../'
二、config->index.js->build 中的 assetsPublicPath: '/' 修改为 assetsPublicPath: './',如果使用 history 模式就直接 assetsPublicPath: '/
productionSourceMap 显示:config->index.js->build 中的 productionSourceMap 设置为 false,不然在 source 的 webpack 中可以看到未压缩的源代码,并且增加了打包之后的体积
去掉 console.log:build->webpack.prod.conf.js中:
new UglifyJsPlugin({ uglifyOptions: { compress: {
warnings: false,
drop_debugger: true,
drop_console: true } }
, sourceMap: config.build.productionSourceMap, parallel: true})
uuid 唯一标识生成
function uuid{
return 'xxxxxx4xxxyxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16) })
}
echarts
echarts tab 切换 出现宽度变成100px 问题,而设置的宽是百分比,原因是初始创建 echarts 实例时获取不到隐藏tab的宽,所以建议在切换 tab 时在创建 echarts 实例,如果 echarts 是固定的宽,不需要响应,也可以设置具体的值而不是百分比
echarts 移动端字体模糊,通过 dpr 来设置不同大小或直接设置100%
echarts 双纵轴需要设置 min 和 max 才会显示右边纵轴,不然会默认重合
echarts 图表数据切换会出现不明竖线,,可先清空 clear 再重新绘制或者 setOption 设置 true,即不跟之前设置的 option 进行合并
echarts 使用地图需要注册地图文件,注册中国地图方式:
import chinaJson from 'echarts/map/json/china.json'
echarts.registerMap('china', chinaJson)
数组去重
单元素数组去重:
let arr = [1,3,3,5,4,85,4,2,2,1]
let newArr = [...new Set(arr)]
console.log(newArr) //[1, 3, 5, 4, 85, 2]
对象数组去重:
//reduce去重
let person = [
{id: 0, name: "小明"}, {id: 1, name: "小张"},
{id: 2, name: "小李"}, {id: 3, name: "小孙"},
{id: 1, name: "小张"}, {id: 2, name: "小李"}
]
let obj = {}
let peon = person.reduce((cur,next) => {
obj[next.id] ? "" : obj[next.id] = true && cur.push(next); return cur},[])
//设置cur默认类型为数组,并且初始值为空的数组console.log(peon)
//filter去重
let person = [
{id: 0, name: "小明"},
{id: 1, name: "小张"},
{id: 2, name: "小李"},
{id: 3, name: "小孙"},
{id: 1, name: "小张"},
{id: 2, name: "小李"}
]
let peon = person.filter((x, index)=>{
let arrids = []
person.forEach((item,i) => {
arrids.push(item.id)
})
return arrids.indexOf(x.id) === index
})
console.log(peon)
嵌套路由 path
嵌套路由子路由的 path 添加/会默认从根目录开始匹配,没有添加则会加上父组件path
路由组件复用
beforeRouteUpdate 路由钩子使用于当前路由改变,但是该组件被复用时调用,如动态路由等,当路由种嵌套子路由时,在父路由里面添加此回调,当子路由发生任何变化时,都会触发该回调或使用 watch 监听路由
data 数据之间的调用
vue 中 data 数据之间的调用,直接在 data 中赋值是无法获取,需要在 mounted() 里赋值才可以取到或使用 computer
vue data render 函数写成箭头函数 this 会指向 vue 实例,不然会无法获取实例的数据和方法
computed 与 watch
computed 计算属性会依赖于使用它的data属性,只要是依赖的 data 属性值有变动,则自定义重新调用计算属性执行一次,watch 则是在监控的 data 属性值发生变动时,其会自动调用watch 回调函数
computed 直接从缓存中获取,而 watch 需要重新编译,性能偏低一点
computed 计算属性可以直接使用,不需要在 data 数据中定义,而 watch 必须是 data 数据域中所存在的,否则无法使用(watch 的 handler 方法,immediate 属性和 deep 属性一般监听对象的某个属性)
Computed property "isReceive" was assigned to but it has no setter:一个计算属性,如果没有设置 setter 或者 getter,当你尝试直接修改这个计算属性的值时,都会报这个错误,添加上 get、set 就可以了
导入和导出
CommonJS 的规范:exports 导出的东西需要 require 引入,如 module.exports 和 exports,输出是一个值的拷贝,运行时加载
ES6规范:export 导出的东西需要 import 引入,如 export default 和 export,输出是一个值的引用,编译时输出接口
provide / inject
在父组件中通过 provider 来提供变量,然后在子组件中通过 inject 来注入变量 ,不论子组件有多深,只要调用了 inject 那么就可以注入 provider 中的数据 ,provide 和 inject 绑定并不是可响应的,然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的
组件 props
props 默认值的类型为数组或者对象,一定要在函数中返回这个默认值,而不是直接赋默认值
props: {
columns: {
type: Array,
default() {
return []
}
},
parameter: {
type: Object,
default() {
return {}
}
}
}
iview
iview 组件 tab 切换存在 table 时,如果只有部分列设置了宽度,在不是当前tab改变浏览器窗口时会出现没有设置宽度的列 width 变成0,可以给没设置的列设置个 minWidth 属性
elementui
elementui 时间范围选择清空时,值并不是空数组而是null,所以在数据列表通过时间查找时不单单判断是否为空数组,还要判断是否为null
elementui 组件添加自定义参数,如 element-ui 的 change 事件默认参数是选中的 value 值 ,但是实际项目中我们要传的一般不止这一个参数,如果直接追加两个参数可能获取不到,这时需要把参数中的默认参数名换成 $event 即可,@change='selectChange($event, extraValue)' 或 @change='(val)=>selectChange(value, extraValue )'
elementui table render 中无法使用 v-model,只能通过 value 来切换变化
elementui 使用 :before-upload 时会触发 :on-remove,所以需通过 :on-remove 中 file 的 file.status === 'success' 来判断
使用 el-image 赋值 src,在 created 中赋值或者给 el-image,v-if 判断当 url 存在时在显示,不然会出现直接渲染图片错误插槽,因为 e-image 的 src 路径一旦初始化加载失败的话就没有异步刷新,created 可以给 data 赋值,但是无法获取 dom 节点
el-tree 的 default-checked-keys 默认勾选只会赋值响应一次,所以使用 setCheckedKeys 方法来每次设置所勾选,但当弹框没有渲染的时候,由于 tree 控件 dom 没有加载,setCheckedKeys 是不存在的,会报错,所以我们需要使用 this.$nextTick(callback) 方法,该方法会在 dom 加载完毕之后,执行回调函数
el-dialog modal-append-to-body 控制遮罩是否在 body 中,对控制显示不同层级 z-index 有可能用到
el-tooltip 的文字信息必须嵌套在标签中在放入 el-tooltip 中
el-table 使用 reserve-selection 和 row-key 来实现记住勾选框问题,当分页点击全选时,当在点击其它页时数据存在但勾选框没勾选,如判断勾选全选每次分页查询手动赋值会出现数据重复添加问题,因为每次点击分页时使用 toggleRowSelection 会触发 selectchang 事件
el-table 分页时,通过 tag 标签调用 toggleRowSelection 方法关闭已勾选项,反复点击分页再关闭时会出现失效问题
el-form resetFields() 是重置表单到初始值,不是清空表单,所以如果在 modal 弹框中先打开编辑表单在打开新增表单数据不会清空,当表单第一次在页面中渲染时所用的数据就是初始数据,如果修改对象的表单赋值没有放在 nextTick 中,就会在表单渲染时就会将这个修改对象作为初始值,可以使用 $nextTick:
//弹框打开
this.dialogBox.boxShow = true;
this.$nextTick(() => {
// 初始化再赋值
this.$refs['projectRef'].resetFields();
this.projectForm.formModel = {
name: row.name,
linkUrl: row.linkUrl,
startTime: row.startTime,
endTime: row.endTime,
status: row.status,
description: row.description }
}
)
主题切换
一、通过正则匹配和替换,将基本颜色变量替换成你需要的,之后动态添加 style 标签来覆盖原有的 css 样式
优点
无需准备多套主题,可以自由动态换肤
缺点
自定义不够,只支持基础颜色的切换
二、本地存放多套主题,两者有不同的命名空间,我们动态的在 body 上 添加 class
过程:
a、安装主题工具 element-theme 和 element-theme-chalk ,et -i 初始化变量生成 element-variables.scss,et 修改编译 element-variables.scss 文件生成 theme 文件夹
b、通过 gulp-css-wrap 批量给 css 文件的所有选择器添加命名空间
c、引入所有主题 css 文件,通过不同 class 来显示不同主题,主题过多可按需引入,动态的加载 css element-admin 通过给 body 设置 class 的 overflow ,hidden 来隐藏滚动条,主题添加 class 应该添加到 htm l中而不是 body 中,不然会出现 body 同级的 class 样式无法应用
三、直接通过 scss @mixin 来覆盖和设置不同的样式,如设置亮暗2个背景色:
@mixin bg-color($lightColor: transparent, $darkColor: transparent){
background-color: $lightColor;
[data-theme='custom-light'] & {
background-color: $lightColor;
}
[data-theme='custom-dark'] & {
background-color: $darkColor;
}
}
图片懒加载
v-lazy图片懒加载 <img v-lazy="图片地址" :key="图片地址">,需要添加 :key,不然会出现内容更新图片不更新,图片会存在缓存情况
vue修改对象数组属性,数据变化视图不变化
因为修改对象或数组嵌套层次比较深,修改时不能检测到数据的变动,从而没有响应式,可以通过 Vue.set(this.data, index, this.data[index]) 或 this.$set(this.data, index, this.data[index]) 来手动触发更新,如:对象- this.$set(this.student,"age", 24),数组- this.$set(this.Arr, index, this.Arr[index])
使用 this.$forceUpdate() 强制更新,视图和数据重新渲染,它仅仅影响实例本身和插入插槽内容的子组件
预渲染 prerender-spa-plugin
prerender-spa-plugin 打包生成静态页的时候我们发现在 console 控制台出现会出现 webpackJsonp is not defined 错误提示,这是因为 js 文件加载顺序导致,可以在 build --> webpack.prod.conf.js 的配置中引入 chunks: ['manifest', 'vendor', 'app'](还是出现错误)或在所有 index.html 中把 async 异步的属性我们把他换成了 defer,使用预渲染 prerender-spa-plugin 模式必须是 history 并且 config 的 assetsPublicPath 为'/'而不是'./'
在安装 prerender-spa-plugin 时 会发现 进度一直停在 build puppeteer 上,那是因为 puppeteer 默认的源 下载太慢了,我们可以使用淘宝源安装 npm config set puppeteer_download_host=https://npm.taobao.org/mirrors npm i puppeteer或$ npx cross-env PUPPETEER_DOWNLOAD_HOST=https://npm.taobao.org/mirrors/ npm install apify --save
用户体验
一、表单提交可以绑定 enter 事件来提高用户体验
二、骨架屏注入,代替部分页面的
浅拷贝和深拷贝
浅拷贝是拷贝一层,深层次的对象级别的就拷贝引用 ,方式:Object.assign() 方法,解构赋值,直接用=赋值和 for in,如:
let obj1 = {a: 1, b: 2}
let obj2 = Object.assign({}, obj1)
obj2.a = 4
console.log(obj1) //{a: 1, b: 2}
console.log(obj2) //{a: 4, b: 2}
let obj3 = {a: 1, b: {c: 3}}
let obj4 = Object.assign({}, obj3)
obj4.b = {c: 99} //这里因为更换了b对象的地址,所以obj3没有跟着变化
console.log(obj3) //{a: 1, b: {c: 3}}
console.log(obj4) //{a: 1, b: {c: 99}}
let obj5 = {a: 1, b: {c: 3}}
let obj6 = Object.assign({}, obj5)
obj6.b.c = 99 //这里因为还是使用b对象的指向地址,所以obj5也跟着变化
console.log(obj5) //{a: 1, b: {c: 99}}
console.log(obj6) //{a: 1, b: {c: 99}}
深拷贝方式,JSON.parse(JSON.stringify(obj)) 和递归拷贝,JSON.parse(JSON.stringify(obj)) 这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构,如对象中值为 undefined 或者 function 的时候会丢失,递归拷贝实现:
// 数组对象递归深拷贝
function deepCopy(arr) {
let copyArr = (arr.constructor === Array) ? [] : {} // 判断是数组还是对象
for(let i in arr) {
if(typeof arr[i] === 'object') {
// 判断是值类型还是引用类型
copyArr[i] = deepCopy(arr[i])
// 引用类型的话进行递归操作
} else {
copyArr[i] = arr[i]
// 值类型直接赋值
}
}
return copyArr
}