包含一些组件的实现,一些问题的解决方法,一些技术点总结
一,面包屑的实现
组件主要使用的是Element-ui中的breadcrumb来实现,组件中的数据主要来自项目路由信息 :
在配置路由时,加上meta信息配置,code如下:
{
path: '/index',
component: () => import('@/views/index'),
name: 'index',
meta: {
title: '首页'
},
children:[
{
path:'/list',
component: () => import('@/views/list'),
name: 'list',
meta:{
title: '列表'
}
}
]
}
在breadcrumb文件中,
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item v-for="item in list" :key="item.path">
<span class="no-redirect" >{{item }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
export default {
data(){
return {
list: null
}
},
method:{
getList(){
//this.$route.matched可以获取当前路由下的所有信息,是一个数组
const matched = this.$route.matched.filter(item=>{
if (item.name && item.meta.title) { //过滤掉没有配置title的路由
return true;
}
)
this.list = matched
}
}
}
二,git pull 时出现错误: Your local changes to the following files would be overwritten by merge:
这是本地代码和仓库中的代码出现了冲突
- 解决方法:
- 第一步:git stash 将工作区恢复到上次提交的内容,同时备份本地所做的修改 备份当前的工作区的内容,从最近的一次提交中读取相关内容,让工作区保证和上次提交的内容一致。同时,将当前的工作区内容保存到Git栈中
- 第二步:git pull 可以正常拉取代码
- 第三步: git stash pop 将之前本地修改应用到当前工作区 从Git栈中读取最近一次保存的内容,恢复工作区的相关内容。由于可能存在多个Stash的内容,所以用栈来管理,pop会从最近的一个stash中读取内容并恢复
git stash的相关命令:
- git stash list :显示Git栈内的所有备份,可以利用这个列表来决定从那个地方恢复
- git stash clear: 清空Git栈。此时使用gitg等图形化工具会发现,原来stash的哪些节点都消失了。
三,富文本编辑器在mounted中初始化,切换组件不显示(使用的是tinymce编辑器)
- 解决方法: 在destroyed中执行编辑器的销毁函数
destroyed() {
const editor = window.tinymce.get('myeditor');
editor.destroy();
}
四,修改element UI组件样式
外层样式 >>> 内层 样式 {
}
.el-setting-margin >>> .el-tooltip-icon_q {
}
五, watch的高级用法
- immediate属性: immediate:true代表如果在 wacth 里声明了 监听的对象 之后,就会立即先去执行里面的handler方法,如果为 false,不会在绑定的时候就执行。
- deep属性: deep,默认值是 false,代表是否深度监听
<div>
<p>obj.a: <input type="text" v-model="obj.a"></p>
</div>
new Vue({
el: '#root',
data: {
obj: {
a: 123
}
},
watch: {
obj: {
handler(new, old) {
console.log('obj.a');
},
immediate: true
}
}
当obj.a发生改变时不会触发监听handler,只有给obj重新赋值才能触发,如果使用deep,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样的开销较大,每一个属性都加上了监听
watch: {
obj: {
handler(new, old) {
console.log('obj.a');
},
immediate: true,
deep: true
}
}
最优化的方法是具体到属性加上监听:
watch: {
'obj.a': {
handler(new, old) {
console.log('obj.a changed');
},
immediate: true
}
}
六, 导出excel表格功能实现(Chrome,firefox)
UI:
<el-button type="primary" @click="handleDownload()">导出excel</el-button>
js:
handleDownload(){
//向后端发送请求返回blob类型的数据
const excelParams = {}
axios({
method: 'get',
url: `url`,
params: excelParams,
responseType: 'blob', //一定要带上
)}.then(res=>{
const content = res.data; //拿到返回的数据
//使用moment对日期进行获取和格式化,生成文件名(之后会总结moment的使用)
const fileName = `myfile_${moment(new Date()).format('YYYYMMDD')}_${moment(new Date()).format('HHmmss')}.xls`;
if ('download' in document.createElement('a')) {
const a = document.createElement('a'); //利用a标签的download属性实现下载
a.download = fileName; //
a.href = URL.createObjectURL(content); // 创建url预览地址
document.body.appendChild(a);
a.click(); // a标签点击
URL.revokeObjectURL(a.href); // 释放URL 对象,使性能优秀
document.body.removeChild(a);
}
})
}
七, git 上传提交信息同步到issue中?
在github上首先创建了一个issue,然后根据issue序号创建了一个分支,将分支同步到了本地,按照正常操作上传,发现在code中显示了提交信息,在issue中并没有信息,解决方法: 在提交时,在提交信息中,带上issue的序号,如: issue序号是: #50 则提交信息是: git commit -m '#50 xxxxx' 然后打开issue,发现信息已经出现
八, watch和computed的区别及应用场景?
- 区别: watch中的函数是不需要调用的, watch监听的属性发生改变时,会自动调用 其中的函数,在数据变化时执行异步或开销较大的操作时使用
computed内部的函数调用的时候不需要加(),函数被当作属性来使用,函数中必须用return返回最终结果,computed 的结果会被缓存,除非依赖的属性发生变化才会重新计算,当依赖的属性没有发生变化时,会从缓存中取得结果
- 应用场景:
- computed 当一个属性受多个属性影响的时候就需要用到computed 最典型的例子: 购物车商品结算的时候
- watch 当一条数据影响多条数据的时候就需要用watch 搜索数据 监听路由的变化 watch: { $route(){ xxxx } }
九, js-cookie的使用?
npm 为我们封装好了cookie的插件:
-
下载js-cookie: npm i js-cookie -S
-
引用 import cookie from ‘js-cookie’
* 设置cookie:cookies。set('name','val,{expires:1}) * 获取cookie: cookies.get('name') * 删除cookie: cookies.remove('name',{path:'/'})
十,vue中国际化的使用(包含ElementUI)
此处使用了中文和英文两种语言
在项目src文件下:
新建文件夹lang
lang---
en--- 放英文的文件
zh--- 放中文的文件
例如: export const status = {
s1: '所有状态',
s2: '已解决',
s3: '未解决',
};
index.js 配置语言的文件
index.js:
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import Cookies from 'js-cookie';
import elementEnLocale from 'element-ui/lib/locale/lang/en'; // element-ui lang
import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN';// element-ui lang
import enLocale from './en/index';
import zhLocale from './zh/index';
Vue.use(VueI18n);
const messages = {
en: {
...enLocale,
...elementEnLocale
},
zh: {
...zhLocale,
...elementZhLocale
},
}
const getDefaultLanguage = function() {
const lans = ['zh', 'en'];
const langString = navigator.language || navigator.userLanguage;
const lang = langString.substr(0, 2);
return lans.indexOf(lang) >= 0 ? lang : 'en';
};
const defaultLang = getDefaultLanguage();
const i18n = new VueI18n({
locale: Cookies.get('language') || defaultLang,
messages
});
// locale 设置语言
// messages 语言包
// navigator.language 获取浏览器语言
// navgator.userLanguage 获取操作系统语言
export default i18n;
在项目的main.js 文件中:
import i18n from './lang'; // Internationalization
import ElementUI from 'element-ui';
Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key, value) //在组件中通过$t('')渲染文字
});
new Vue({
el: '#app',
router,
store,
i18n,
render: h => h(App)
});
在组件中切换语言: this.$i18n.locale = lang; 修改语言
在组件中使用:
<span>{{$t('status.s1')}}</span>
十一,组件内导航beforeRouteUpdate的使用
使用场景:组件复用,路由跳转
但这个守卫并不会在不同路由共用同一组件时触发
beforeRouteUpdate(to,from,next){
//在当前路由改变,但是该组件被复用时调用
//例如:从list/1跳转到list/2,渲染的都是list组件
//可以访问实例的this
}
十二,vue 中 .sync, .native修饰符的使用?
- .sync
作用: 对传递给子组件的prop数据进行双向绑定
父组件:
<Hello :msg:sync="msg"></Hello>
data(){
return{
msg:'i am test'
}
}
子组件:
<span @click="handleClick()">{{}}</span>
props:{
msg:{
type:string,
default:''
}
}
handleClick(){
this.$emit('update:msg','我是点击后的信息')
}
- .native
作用:在父组件中给子组件绑定一个原生事件,使子组件变成普通的html标签,不加native修饰符,事件无法触发
(给普通标签加native无效,仅用于自定义组件和element等)
父组件中:
<MyButton @click.native="handleClick()"/> //给子组件绑定一个点击事件,如果没有native事件不执行
handleClick() {
console.log('ccccc');
},
子组件:
<el-button type="purple">子组件点击</el-button>
十三,vue中Clipboard使用?
Clipboard: 现代化的拷贝文字, 拷贝文字不应当是一件困难的事. 不需要过多繁杂的配置或者下载很多脚本文件. 最重要的,它不应该依赖flash或者其他框架,应该保持简洁。这就是创造clipboard.js的原因和目的
官方文档http://www.clipboardjs.cn/
- 用于复制(浏览器支持)
template:
<textarea id="bar" v-model="textarea"/>
<el-button type="white" @click="handleCopy(textarea, $event)">copy it</el-button>
script:
data(){
return{
textarea:'要复制的内容'
}
}
handleCopy(text,event){
const clipboard = new Clipboard(event.target, {
text: () => text
});
clipboard.on('success', (e) => {
this.success('复制成功');
clipboard.off('error');
clipboard.off('success');
clipboard.destroy();
});
clipboard.on('error', () => {
clipboard.off('error');
clipboard.off('success');
clipboard.destroy();
});
clipboard.onClick(event);
}
- 用于剪切(????)
十四,在vue中使用节流与防抖?
节流与防抖是页面性能优化的常见方式
- 防抖
防抖是在规定时间内,多次执行一个方法,只有最后一次执行的方法生效。(在规定时间内重复执行时间重新计算)
例如:点击按钮,发送请求。避免多次发送请求
新建debounce-throttle.js 在其中编写防抖代码
export const debounce = (fn, delay) => {
let timer = null; // timer 永驻于内存中
return () => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, delay); // 当鼠标停止2s后执行
};
};
在组件中component.vue:
<el-button type="primary" @click="handleDebounce">debounce</el-button>
methods:
handleDebounce: debounce(() => {
getChart().then((res) => {
console.log('res', res);
});
}, 2000),
- 节流
节流是在一段时间内多次触发一个方法,只执行一次这个方法。
例如:鼠标移动发送请求,规定时间内只发送一次请求
debounce-throttle.js 在其中编写节流代码
export const throttle = (fn, interval) => {
let startTime = new Date();
let timer = null;
return () => {
const endTime = new Date();
if (endTime - startTime < interval) {
clearTimeout(timer);
timer = setTimeout(() => { // 鼠标停止后执行一次
startTime = new Date();
fn();
}, interval);
} else {
startTime = new Date();
fn();
}
};
};
组件中:
<p style="width:200px;height:200px;border:1px solid #000" @mousemove="handleThrottle">鼠标移动加载</p>
handleThrottle: throttle(() => {
getChart().then((res) => {
console.log('res', res);
});
}, 2000)
十五,关于深拷贝?
-
简单的深拷贝(只能实现第一层的深拷贝,当是对象数组或者多维数组时则不能使用)
es6 展开运算符
数组的slice方法
数组的concat()方法 -
复杂的深拷贝
Json.parse(Json.stringify())(当数组中含有undefined,function,symbol 会在转换过程中被忽略,所以只适用于一些简单的对象) -
最有效的方法是使用递归实现一个深拷贝方法:(但是此方法如果对象中的key值是数字,会导致对象中的顺序发生改变,需要改进)
deepCopy(nums) {
// 实现对象数组与多维数组的深拷贝
const target = Array.isArray(nums) ? [] : {};
for (const [key, value] of Object.entries(nums)) {
if (typeof value === 'object') {
target[key] = deepCopy(value);
} else {
target[key] = value;
}
}
return target;
}
十六: Vue中SortableJS 拖拽库的使用?
功能强大的JavaScript 拖拽库
npm 下载:
npm i -S sortablejs
引入到组件中:
import Sortable from 'sortablejs';
HTML:
<div id="serviceOrderFieldActiveTable">
<table>
<tbody>
<tr><td>111</td></tr>
<tr><td>222</td></tr>
<tr><td>333</td></tr>
</tbody>
</table>
</div>
data:
sortable: null;
methods:
配置 Sortable:
const el = document.querySelectorAll('#serviceOrderFieldActiveTable table tbody')[0];
this.sortable = Sortable.create(el, {
filter: '.filtered', // 过滤器,不需要进行拖动的元素
preventOnFilter: true, // 在触发过滤器`filter`的时候调用`event.preventDefault()`
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
setData: function(dataTransfer) {
dataTransfer.setData('Text', '');
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
},
onStart: (evt) => { // 开始拖拽的时候
console.log('start', this.sortable.el.querySelector('.filtered')); // 有.filtered 的一行
this.freezed = this.sortable.el.querySelector('.filtered');
},
onEnd: evt => { // 结束拖拽
const list = evt.to; // 要拖拽的tbody
const allTr = list.querySelectorAll('tr');
const lastTr = allTr[allTr.length - 1]; // 最后一个tr
console.log('oldindex', evt.oldIndex); // 拖拽前的索引
console.log('newindex', evt.newIndex); // 拖拽后的索引
const oldList = this.activeData.map(data => data.fieldId);
console.log('oldList', oldList); // 数组中fieldId
const targetRow = this.activeData.splice(evt.oldIndex, 1)[0]; // 拖拽的tr数据
console.log('targetrow', targetRow);
this.activeData.splice(evt.newIndex, 0, targetRow); // 将拖拽的数据插入到拖拽停止时的位置
// for show the changes, you can delete in you code
console.log('newlist', this.newList); // 数组元素的fieldId
const tempIndex = this.newList.splice(evt.oldIndex, 1)[0]; // 取到拖拽前的fieldId
this.newList.splice(evt.newIndex, 0, tempIndex); // 取到拖拽前的fieldId插入到拖拽后的位置
console.log('afternewlist', this.newList); // 数组元素的fieldId
}
})
十七. vue-multipane 的使用?
简介: 可改变大小的窗口 for vue.js
npm 下载:
npm i -S vue-multipane
在组件中使用:
<template>
<multipane class="vertical-panes" layout="vertical">
<div>Pane 1</div>
<multipane-resizer></multipane-resizer>
<div>Pane 2</div>
<multipane-resizer></multipane-resizer>
<div>Pane 3</div>
</multipane>
</template>
<script>
import { Multipane, MultipaneResizer } from 'vue-multipane';
export default {
components: {
Multipane,
MultipaneResizer
}
}
</script>
//修改样式:
<style>
.vertical-panes {
width: 100%;
}
.vertical-panes >>> .multipane-resizer {
margin: 0;
left: 0;
position: relative;
}
.vertical-panes >>> .multipane-resizer:before {
display: block;
content: "";
width: 2px;
height: 800px;
position: absolute;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
}
.vertical-panes >>> .multipane-resizer:hover:before {
border-color: #999;
}
</style>
十八. Vue.config.productionTip = false
作用:阻止启动生产消息,常用作指令
没有Vue.config.productionTip = false这句代码,它会显示你生产模式的消息
开发环境下,Vue 会提供很多警告来帮你对付常见的错误与陷阱。 而在生产环境下,这些警告语句却没有用,反而会增加应用的体积。 此外,有些警告检查还有一些小的运行时开销,这在生产环境模式下是可以避免的。
如果不加Vue.config.productionTip = false
加上Vue.config.productionTip = false
十九. 判断图片地址是否有效?
validateImg (path) {
let ImgObj = new Image()
ImgObj.src = path
if (ImgObj.fileSize > 0 || (ImgObj.width > 0 && ImgObj.height > 0)) {
return true
} else {
return false
}
}
二十. vue.config.js 中的配置
const CompressionPlugin = require('compression-webpack-plugin')
const productionGzipExtensions = ['js', 'css']
const DEV_BACKEND_ADDR = process.env.VUE_APP_DEV_BACKEND_ADDR
module.exports = {
publicPath: './', // 相对路径 ('./'),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径
outputDir: 'dist', // 输出文件目录,当运行 vue-cli-service build 时生成的生产环境构建文件的目录
filenameHashing: false, // 设为 false 来关闭文件名哈希
lintOnSave: true, // 是否在保存的时候使用 `eslint-loader` 进行检查
assetsDir: 'static', // 放置public下生成静态资源的文件 (js、css、img、fonts)
productionSourceMap: false, // 以减少打包后的项目体积
chainWebpack: config => { // 便于阅读打包编译后的文件
config.optimization.minimize(false)
},
configureWebpack: config => {
config.plugins.push(
new CompressionPlugin({ // 打包的时候开启gzip可以大大减少体积,非常适合于上线部署
filename: '[path].gz[query]', // 目标资源文件名
algorithm: 'gzip',
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'), // 匹配对应文件
threshold: 10240, // 只处理比这个值大的资源。按字节计算。
minRatio: 0.8, // 只有压缩率比这个值小的资源才会被处理
cache: true // 是否缓存
}))
if (process.env.NODE_ENV === 'development') {
config.devServer = {
open: true, // 项目启动打开页面
port: 24444, // 端口号配置
watchOptions: {
poll: false, // 通过传递 true 开启 polling,或者指定毫秒为单位进行轮询。
aggregateTimeout: 300 // 当第一个文件更改,会在重新构建前增加延迟。这个选项允许 webpack 将这段时间内进行的任何其他更改都聚合到一次重新构建里。以毫秒为单位:
},
proxy: {
'/api': {
target: `${DEV_BACKEND_ADDR}`,
ws: false, // ws:true / false,是否代理websockets
changeOrigin: true, // 跨域时,本地就会虚拟一个服务器接收你的请求并代你发送该请求
pathRewrite: {
'^/api': ''
}
}
}
}
}
if (process.env.NODE_ENV === 'production') {
config.devServer = {
open: true, // 项目启动打开页面
port: 2333, // 端口号配置
watchOptions: {
poll: false, // 通过传递 true 开启 polling,或者指定毫秒为单位进行轮询。
aggregateTimeout: 300 // 当第一个文件更改,会在重新构建前增加延迟。这个选项允许 webpack 将这段时间内进行的任何其他更改都聚合到一次重新构建里。以毫秒为单位:
},
proxy: {
'/api': {
target: `${DEV_BACKEND_ADDR}`,
ws: false, // ws:true / false,是否代理websockets
changeOrigin: true, // 跨域时,本地就会虚拟一个服务器接收你的请求并代你发送该请求
pathRewrite: {
'^/api': ''
}
}
}
}
config.optimization = { // 将打包后的依赖包分解, 将每个依赖包分解成单独的js文件,防止打包后依赖包体积过大
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 20000, // 依赖包超过20000bit将被单独打包
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name (module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
// npm package names are URL-safe, but some servers don't like @ symbols
return `npm.${packageName.replace('@', '')}`
}
}
}
}
}
}
}
}