什么是 MVVM
MVVM(Model-View-ViewModel)一种软件设计模式,旨在将应用程序的数据模型(Model)与视图层(View)分离,并通过 ViewModel 来实现它们之间的通信。降低了代码的耦合度。
Model 代表数据模型,是应用程序中用于处理数据的部分。在 Vue.js 中,Model 通常指的是组件的 data 函数返回的对象,或者 Vue 实例的 data 属性。
View 是用户界面,是用户与应用程序进行交互的部分。在 Vue.js 中,View 通常指的是 Vue 组件的模板(template),它使用声明式的语法来描述如何渲染数据。Vue 的模板语法允许开发者以声明的方式将 DOM 绑定到底层 Vue 实例的数据上。
ViewModel 是 MVVM 模式的核心,它作为 View 和 Model 之间的桥梁,负责将 Model 的数据同步到 View 显示出来,以及将用户对 View 的操作反映到 Model 上。
Vue 实例通过其响应式系统(基于 ES6 的 Proxy 或 Object.defineProperty 实现)来监听 Model 的变化,并自动更新 DOM(View)
Vue 钩子函数
Vue 八大生命周期钩子函数:
| 函数 | 调用时间 |
|---|---|
| beforeCreate | vue实例初始化之前调用 |
| created | vue实例初始化之后调用 |
| beforeMount | 挂载到DOM树之前调用 |
| mounted | 挂载到DOM树之后调用 |
| beforeUpdate | 数据更新之前调用 |
| updated | 数据更新之后调用 |
| beforeDestroy | vue实例销毁之前调用 |
| destroyed | vue实例销毁之后调用 |
Vue 实例
每个 Vue 应用都是通过用 Vue 构造函数创建一个新的 Vue 实例开始的
var vm = new Vue({
// 选项
})
Vue 实例和 Vue 生命周期有什么关系?
在 Vue 中,当创建一个组件或 Vue 实例时,它会经历一系列初始化步骤,这就是所谓的生命周期。
Vue 生命周期描述了 Vue 实例从创建到销毁的整个过程。
Vue 实例提供了一系列有用的方法,用于与 Vue 实例进行交互、操作 DOM、监听事件等。以下是一些常用的 Vue 实例方法:
-
DOM 操作方法:
标题 $mount(elementOrSelector, [options]) 手动挂载 Vue 实例到一个 DOM 元素上。如果未提供参数,则创建一个新的 DOM 元素并将 Vue 实例挂载到该元素上。 $appendTo(elementOrSelector, [callback]) 将 Vue 实例的根 DOM 元素插入到目标元素中。 $before(elementOrSelector, [callback]) 将 Vue 实例的根 DOM 元素插入到目标元素之前。 $after(elementOrSelector, [callback]) 将 Vue 实例的根 DOM 元素插入到目标元素之后。 $remove([callback]) 从 DOM 中移除 Vue 实例的根 DOM 元素。 -
事件监听与触发
标题 $emit(eventName, […args]) 触发当前实例上的自定义事件。 $on(eventName, callback) 监听当前实例上的自定义事件。 $once(eventName, callback) 监听一个自定义事件,但是只触发一次。 $off([eventName, callback]) 移除自定义事件监听器。 -
DOM 更新与延迟执行
标题 $nextTick(callback) 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用它,然后等待 DOM 更新。 -
数据监听与更新
标题 $watch(expOrFn, callback, [options]) 监听 Vue 实例上某个表达式的变化,当表达式的值改变时调用回调函数。 $set(target, key, value) 向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。 $delete(target, key) 删除对象上的属性。如果对象是响应式的,确保删除能触发视图更新。 $forceUpdate() 强制 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。 -
实例属性访问
标题 $data 访问 Vue 实例的 data 对象。 $options 访问用于当前 Vue 实例的初始化选项。 $refs 一个对象,持有注册过 ref 特性的所有 DOM 元素和子组件实例。
上述方法并非全部 Vue 实例方法,但涵盖了常用的部分。
// 创建一个新的 Vue 实例,但没有指定 el 选项
var vm = new Vue({
data: {
message: 'Hello Vue!'
},
// 生命周期钩子和其他选项...
});
// 手动挂载到 #app 元素上
vm.$mount('#app');
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
// 生命周期函数
mounted: function() {
console.log('Component mounted!');
// 此时可以执行依赖于 DOM 的操作
}
// 其他选项...
});
Vue.prototype
用途:
-
Vue.prototype 是 Vue 构造函数的原型对象,用于向所有 Vue 实例添加共享的方法和属性。
-
通过在 Vue.prototype 上定义方法或属性,可以确保这些方法或属性在所有 Vue 实例中都是可用的。
用法:
-
添加全局方法:
Vue.prototype.$myMethod = function() { ... }; -
添加全局属性:
Vue.prototype.$myProperty = 'some value';
为什么要以 $ 符号开头?
这是 Vue 中的一个简单约定,为了避免和组件中定义的数据、方法、计算属性产生冲突。
Vue.prototype 加一个变量,只是给每个组件加了一个属性,这个属性的值并不具有全局性。
例如:
// main.js
Vue.prototype.$test = "测试"
//test1.vue
mounted() {
this.$test = "哈哈"
console.log(this.$test) // 哈哈
this.$router.push("/test2")
}
//test2.vue
mounted() {
console.log(this.$test) // 测试
}
如果要实现全局变量的功能,需要把属性变为引用类型
// main.js
Vue.prototype.$test = { name: "测试" }
//test1.vue
mounted() {
this.$test.name= "哈哈"
console.log(this.$test) // 哈哈
this.$router.push("/test2")
}
//test2.vue
mounted() {
console.log(this.$test) // 哈哈
}
created 和 mounted 区别
-
created:在模板渲染成 html 前调用,通常初始化某些属性值,然后再渲染成视图。
-
mounted:在模板渲染成 html 后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作。
例如 chart.js 的使用
var ctx = document.getElementById(id),这个就一定要等 html 渲染完成后才可以完成,这就要用 mounted
computed 和 watch 区别
computed 计算属性
-
只有当计算属性所依赖的响应式数据发生变化时,计算属性才会重新求值。如果多次访问计算属性,但依赖项没有变化,那么它将返回之前的计算结果,而不是重新计算。
-
在模板中可以直接使用计算属性
-
计算属性总是返回一个值
watch 监听器
-
watch 中的回调函数可以执行异步操作,而 computed 则不可以。
-
没有缓存,每次触发时重新计算
-
接收两个参数(newValue, oldValue)
总结
-
当多个属性通过计算影响一个属性的时候,建议用 computed
-
当一个值发生变化之后,会引起一系列的操作,这种情况就适合用 watch
computed 如何传参
<test :text="text('123')" />
computed: {
text(){
return function (params) {
console.log(params) // 123
return params
}
}
}
执行顺序
父子组件生命周期执行顺序
-
页面初始化时:
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted-> 父 mounted
-
页面销毁时:
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed-> 父 destroyed
页面跳转的生命周期执行顺序
-
旧页面跳转到新页面:
新页面 created > 新 beforeMount > 旧 beforeDestroy > 旧 destroyed > 新 mounted
computed 、watch、created 、mounted 的先后顺序
-
immediate 为 false 时: created => computed => mounted => watch
-
immediate 为 true 时: watch =>created=> computed => mounted
[ɪˈmiːdiət]
$nextTick
原理
nextTick 是 Vue 中用于等待下一次 DOM 更新完成后的一个方法。
由于 Vue 的 DOM 更新是异步的,即数据变化后,Vue 会等待当前执行栈中的所有同步任务完成后,才进行 DOM 的更新。如果在数据变化后直接尝试获取或操作 DOM,可能会得到更新前的状态。nextTick 提供了一个机制,允许我们在 DOM 更新完成后再执行某些操作,从而确保我们操作的是更新后的 DOM。
nextTick 的实现原理主要是基于 JavaScript 的事件循环机制:
-
如果浏览器支持 Promise,Vue 会使用 Promise.resolve().then(callback) 的方式将回调函数放入微任务队列。
-
如果浏览器支持 MutationObserver,则利用 MutationObserver 的回调函数放入微任务队列。
- MutationObserver 是 Web API 的一部分,它提供了一种能够异步监视 DOM 变动的能力。这个 API 可以用来检测 DOM 树的变化,比如节点的增减、属性的变化等,并在这些变化发生时执行指定的回调函数。
-
如果不支持上述 2 个,则会使用 setImmediate 或 setTimeout 来模拟异步操作,会放入宏任务队列。
使用场景
-
比如一个下拉框,我在 methods 方法里手动赋值,然后我想获取赋值后的 label,这时候就要用 $nextTick
-
两个 tab 切换,切换后需要让 tab 中的一个 input 获取焦点,这时候就要在 $nextTick 中使用 focus()
@click.native
原生点击事件。
通常情况下,我们在组件上使用 @click 监听点击事件时,实际上是监听了组件内部封装的一个模拟点击事件,并不是真正的原生点击事件。
而使用 @click.native 可以直接监听原生的点击事件,不需要经过组件的封装和处理。这对于一些特殊的场景可能会更加方便和灵活。
<el-card @click="handleClick1"></el-card>
<el-card @click.native="handleClick2"></el-card>
<script>
methods: {
handleClick1() {
// 不会触发
},
handleClick2() {
// 可以触发
}
}
</script>
@click.stop
阻止事件冒泡,即阻止事件向父级元素传递。
<div id="app">
<div v-on:click="parentClick">
<button v-on:click="childClick">阻止单击事件继续传递</button>
</div>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
name: "Vue.js"
},
methods: {
parentClick: function () {
alert("parentClick");
},
childClick: function () {
alert("childClick");
},
}
});
</script>
// 将会先弹出 "childClick", 再弹出 "parentClick"。
<div id="app">
<div v-on:click="parentClick">
<button v-on:click.stop="childClick">阻止单击事件继续传播</button>
</div>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
name: "Vue.js"
},
methods: {
parentClick: function () {
alert("parentClick");
},
childClick: function () {
alert("childClick");
},
}
});
</script>
// 只弹出 "childClick"
使用场景:点击子元素区域的的时候,不触发父级元素的点击事件。
@click.prevent
阻止事件的默认行为。
// 阻止 a 标签跳转,仅执行函数 test4
<a href="http://www.baidu.com" @click.prevent="test4">百度一下</a>
// 阻止表单提交,仅执行函数 test5
<form action="/xxx" @submit.prevent="test5">
<input type="submit" value="注册">
</form>
@keyup.enter
按键修饰符。
// 按下 enter时,执行方法 test7
<input type="text" @keyup.enter="test7">
directive 自定义指令
什么是自定义指令?
Vue 的自定义指令允许你对 DOM 进行低级别的操作。自定义指令通过 Vue 的全局方法 Vue.directive() 或组件的 directives 选项来注册。注册之后,你可以在 Vue 模板中通过 v- 前缀加上指令名来使用这个指令。
低级别操作是指直接通过 JavaScript 对 DOM 树中节点的进行增删改查,以及对节点的属性、样式、事件等进行操作。
全局自定义指令
Vue2:
// 在main.js中
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
Vue3:
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})
局部组件指令
Vue2:
created() {},
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
Vue3:
在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
export default {
setup() {
/*...*/
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
}
}
}
然后你可以在模板中任何元素上使用新的 v-focus property,如下:
<input v-focus>
钩子函数
Vue2:
| 函数 | 描述 |
|---|---|
| bind | 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 |
| inserted | 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 |
| update | 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。 |
| componentUpdated | 指令所在组件的 VNode 及其子 VNode 全部更新后调用。 |
| unbind | 只调用一次,指令与元素解绑时调用。 |
Vue3:
| 函数 | 描述 |
|---|---|
| created | 在绑定元素的 attribute 前或事件监听器应用前调用 |
| beforeMount | 在元素被插入到 DOM 前调用 |
| mounted | 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用 |
| beforeUpdate | 绑定元素的父组件更新前调用 |
| updated | 在绑定元素的父组件及他自己的所有子节点都更新后调用 |
| beforeUnmount | 绑定元素的父组件卸载前调用 |
| unmounted | 绑定元素的父组件卸载后调用 |
钩子函数参数:
-
el:指令绑定到的元素。这可以用于直接操作 DOM。
-
binding:一个对象,包含指令值(value)、旧的指令值(oldValue)、参数(arg)等等
-
vnode
-
oldVnode
原理
当 Vue 组件的模板被编译时,Vue 会识别出模板中所有的 v- 前缀指令,这会包含 Vue 的内置指令和自定义指令。对于自定义指令,Vue 会根据注册的钩子函数生成相应的指令对象,这些对象包含了指令的名称、参数、表达式等信息。然后会将这些信息存储在之后生成的 VNode 对象中。最后在生成真实 DOM 的时候,Vue 会在相应的 DOM 元素上调用自定义指令的钩子函数。
v-model 的原理
v-model 是 Vue.js 中的一个语法糖,它内部实际上是使用了 v-bind 和 v-on 两个指令。
-
v-bind:实现数据到 DOM 的绑定
-
修改 data 中的属性值会体现在页面上
-
v-bind 的原理主要是基于 Vue 的响应式系统,当数据变化时触发相应的 setter,来触发渲染过程,以更新 DOM
-
-
v-on:实现 DOM 到数据的绑定
-
在页面上修改的属性值会作用到 data 中
-
v-on 的原理是监听 DOM 元素上指定的事件,当事件发生时,执行相应的 JavaScript 代码或方法,从而更新属性值。
-
Vue 中使用 CSS 变量
<template>
<!-- 方式一:
在根或父或当前节点使用 --varName 来定义变量
然后当前节点使用 var(--varName) 来使用变量 -->
<div class="parent">
<!-- 方式二:可以在 data 中定义变量,在 template 中直接使用 -->
<span :style="{ color: myColor1 }">parent</span>
<!-- 方式三:data 中定义变量,css 中使用 v-bind 绑定变量 -->
<div class="child1">child1</div>
<!-- 方式四:通过 style module -->
<div :class="classModule.child2">child2</div>
<!-- 方式五:不好理解,不知道这样写的意义在哪里 -->
<div class="child3" :style="{ '--my-color': myColor3 }">child3</div>
</div>
</template>
<script lang="ts" setup>
const myColor1 = 'red'; // 方式二
const myColor2 = 'yellow'; // 方式三
const myColor3 = 'blue'; // 方式五
</script>
<style>
.parent {
/* 方式一 */
--parent-bg-color: skyblue;
/* 方式一 */
background-color: var(--parent-bg-color);
}
.child1 {
/* 方式三 */
color: v-bind(myColor2);
}
.child3 {
/* 方式五 */
color: var(--my-color);
}
</style>
<style module="classModule">
/* 方式四 */
.child2 {
color: green;
}
</style>
Vue 项目结构
| 文件名 | 注释 |
|---|---|
| build | 项目构建(webpack)相关代码 |
| config | 项目配置 |
| dev.env.js | 开发环境变量 |
| prod.env.js | 生产环境变量 |
| test.env.js | 测试环境变量 |
| index.js | 项目配置文件 |
| package.json | npm 包配置文件,npm 脚本 |
| src | 日常开发主要在这个文件夹 |
| asset | 放置静态资源,会被 webpack 构建 |
| main.js | 项目入口文件 |
| components | 公共组件 |
| App.vue | 根组件 |
| pages | 页面文件 |
| api | 接口文件 |
| router | 路由文件 |
| static | 纯静态资源,不会被 webpack 构建 |
| index.html | 首页入口文件 |
| README.md | 项目的说明文档 |
| dist | 打包之后的文件 |
Vue 组件规范
1、按功能分
-
组件分为容器组件,和 UI 组件(或称为展示组件);
-
将每个 .vue 文件拆成一个文件夹,其中 index.vue 作为容器组件;
-
其余如 Table.vue 和 Search.vue 等作为 UI 组件。
-
2、按可复用性分
-
组件分为全局基础组件和页面级组件;
-
将全局基础组件提取至 /src/components 内;
-
页面组件放至 /src/pages 内。
-
Vue 功能代码
动态加载组件
<template>
<div>
<div class="block-menus" v-show="$store.state.modulesFlag">
<div
v-for="(item, index) in modulesMenu"
:key="index"
class="block-menu"
@click="handleClick(item)"
>
<span class="el-icon-menu"></span><span>{{ item.meta.title }}</span>
</div>
</div>
<component :is="dynamicComponent" v-show="!$store.state.modulesFlag"></component>
</div>
</template>
<script>
export default {
data() {
return {
dynamicComponent: null
};
},
created() {
this.modulesMenu = this.$store.state.modulesMenu;
},
mounted() {
},
methods: {
handleClick(val) {
this.dynamicComponent = () => Promise.resolve(require(`@/views/${val.component}.vue`).default)
this.$store.commit('changeModulesFlag', false)
},
},
watch: {
'$store.state.modulesMenu': {
handler(val) {
this.modulesMenu = val
}
}
}
};
</script>
Vue Tagsview
<template>
<div class="el-tagsview">
<!-- contextmenu.prevent: vue的鼠标右键事件-->
<div
@contextmenu.prevent="openMenu(item, $event)"
:class="isActive(item.url) ? 'active' : ''"
class="tagsview"
v-for="(item, index) in tags"
:key="index"
@click="handleClick(item)"
>
{{ item.meta.title }}
<!-- 这个地方一定要click加个stop阻止,不然会因为事件冒泡一直触发父元素的点击事件,无法跳转另一个路由 -->
<span
class="el-icon-close tagsicon"
@click.stop="handleClose(item, index)"
></span>
<ul
v-if="visible && url === item.url"
class="contextmenu"
:style="{ left: left + 'px', top: top + 'px' }"
>
<li @click.stop="handleRefresh(item, index)">刷新</li>
<li @click.stop="handleClose(item, index)">关闭</li>
<li @click.stop="handleCloseOther(item, index)">关闭其他</li>
<li @click.stop="handleCloseAll($route.path)">关闭所有</li>
</ul>
</div>
</div>
</template>
<script>
//这个就是导入vuex的数据,配合下面...map用
import { mapState, mapMutations } from "vuex";
export default {
data() {
return {
//右键菜单隐藏对应布尔值
visible: false,
//右键菜单对应位置
top: 0,
left: 0,
url: ""
};
},
computed: {
//引入vuex中state中的tags数据,一样this调用就行
...mapState(["tags"]),
},
mounted() {
},
watch: {
//监听右键菜单的值是否为true,如果是就创建全局监听点击事件,触发closeMenu事件隐藏菜单,如果是false就删除监听
visible(value) {
if (value) {
document.body.addEventListener("click", this.closeMenu);
} else {
document.body.removeEventListener("click", this.closeMenu);
}
},
},
methods: {
//引入vuex中mutation方法,可以直接this.xxx调用他
...mapMutations(["removeTag", "removeTags", "addTags"]),
//点击叉叉删除的事件
handleClose(item, index) {
//先把长度保存下来后面用来比较做判断条件
let length = this.tags.length - 1;
// 如果length=0,也就是说只剩最后一个标签,且是首页,那么什么都不做
if (length === 0 && this.$route.path === "/dashboard") {
return;
}
//vuex调方法,上面...map引入的vuex方法,不会这种方法的看vue官网文档
this.removeTag(item);
// 如果关闭的标签不是当前路由的话,就不跳转
if (item.url !== this.$route.path) {
return;
}
// 判断:如果index和length是一样的,那就代表都是一样的长度,就是最后一位,那就往左跳转一个
if (index === length) {
//再判断:如果length=0,也就是说你删完了所有标签
if (length === 0) {
//那么再判断:如果当前路由不等于首页(不需要)
if (this.$route.path !== "/dashboard") {
//那么就跳转首页。这一步的意思是:如果删除的最后一个标签不是首页就统一跳转首页,如果你删除的最后一个标签是首页标签,已经在这个首页路由上了,你还跳个什么呢。这不重复操作了吗。
this.$router.push({ path: "/" });
}
} else {
//那么,如果上面的条件都不成立,没有length=0.也就是说你还有好几个标签,并且你删除的是最后一位标签,那么就往左边挪一位跳转路由
this.$router.push({ path: this.tags[index - 1].url });
}
} else {
// 如果你点击不是最后一位标签,点的前面的,那就往右边跳转
this.$router.push({ path: this.tags[index].url });
}
this.closeMenu();
},
// 刷新
handleRefresh(item, index) {
this.$router.replace({ path: item.url });
// this.$router.push({path: item.url})
this.closeMenu();
},
// 关闭其他
handleCloseOther(item, index) {
const routePaht = this.$route.path
this.removeTags();
if(routePaht !== item.url) {
this.$router.push({ path: item.url }).catch(err => err);
}
this.addTags(item)
this.closeMenu();
},
//点击跳转路由
handleClick(item) {
//判断:当前路由不等于当前选中项的url,也就代表你点击的不是现在选中的标签,是另一个标签就跳转过去,如果你点击的是现在已经选中的标签就不用跳转了,因为你已经在这个路由了还跳什么呢。
if (this.$route.path !== item.url) {
//用path的跳转方法把当前项的url当作地址跳转。
this.$router.push({ path: item.url });
}
},
//通过判断路由一致返回布尔值添加class,添加高亮效果
isActive(url) {
return url === this.$route.path;
},
//右键事件,显示右键菜单,并固定好位置。
openMenu(tag, e) {
this.url = tag.url
this.visible = true;
this.selectedTag = tag;
const offsetLeft = this.$el.getBoundingClientRect().left;
this.left = e.clientX - offsetLeft + 210; //右键菜单距离左边的距离
this.top = e.clientY + 10; //右键菜单距离上面的距离 这两个可以更改,看看自己的右键菜单在什么位置,自己调
},
//隐藏右键菜单
closeMenu() {
this.url = ""
this.visible = false;
},
//右键菜单关闭所有选项,触发vuex中的方法,把当前路由当参数传过去用于判断
handleCloseAll(val) {
this.removeTags();
//跳转到首页,val接受传过来的当前路由
if (val !== "/dashboard") {
this.$router.push({ path: "/" });
} else {
this.addTags(this.$store.state.routers[0].children[0])
}
this.closeMenu();
},
},
};
</script>
<style scoped>
/*标签导航样式*/
.tagsview {
cursor: pointer;
/* margin: 10px 4px 10px 0px; */
margin: 2px 6px 2px 0px;
line-height: 26px;
padding: 0 8px;
border: 1px solid #d8dce5;
/* border-radius: 5px; */
color: #000;
font-size: 12px;
display: inline-block;
}
/*叉号鼠标经过样式*/
.tagsicon:hover {
color: #f56c6c;
}
/*标签高亮*/
.active {
/* background-color: #40ba84; */
color: #fff;
background-color: #6bb4f8;
border-color: #6bb4f8;
}
/*右键菜单样式*/
.contextmenu {
margin: 0;
background: #fff;
z-index: 100;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
}
.contextmenu li {
margin: 0;
padding: 5px 16px;
cursor: pointer;
}
.contextmenu li :hover {
background: #eee;
}
</style>
addTags(state, val) {
//如果等于-1说明tabs不存在,那么插入,否则什么都不做
//findindex找角标,循环判断一下,如果等于那么就代表有相同的,就不必添加,如果找不到那就是-1.就添加
let result = state.tags.findIndex(item => item.url === val.url)
result === -1 ? state.tags.push(val) : ''
if(state.tags.length > 10) {
state.tags = state.tags.slice(state.tags.length - 10, state.tags.length)
}
},
//关闭标签
removeTag(state, val) {
//同上,找角标,然后用角标的位置对应删除一位。splice:这是数组的删除方法
let result = state.tags.findIndex(item => item.name === val.name)
state.tags.splice(result, 1)
},
//关闭所有tagsview标签
removeTags(state) {
//清空数组
state.tags = []
},
el-tree-select
<template>
<div>
<el-form :model="form" :rules="rules" ref="form" style="width: 500px;">
<el-form-item prop="org" ref="treeSelect">
<!-- popoverClass="myClass" 自定义弹出层样式,注意style中不能加 scoped -->
<el-tree-select
v-model="form.org"
:treeParams="treeParams"
popoverClass="myClass"
/>
</el-form-item>
<el-form-item prop="name">
<el-select v-model="form.name" clearable>
<el-option label="哈哈" value="haha" />
<el-option label="呵呵" value="hehe" />
</el-select>
</el-form-item>
</el-form>
<el-button @click="reset">清除</el-button>
</div>
</template>
<script>
export default {
data() {
return {
treeParams: {
data: [
{
label: "1",
value: "1",
children: [
{ label: "11", value: "11" },
{
label: "12",
value: "12",
children: [
{ label: "111", value: "111" },
{ label: "112", value: "112" },
],
},
],
},
{ label: "2", value: "2" },
{ label: "3", value: "3" },
],
props: {
children: "children",
label: "label",
value: "value",
},
defaultExpandedKeys: [], // 默认展开节点
clickParent: true // 可以点击父节点,默认 false
},
form: {
org: "",
name: "",
},
rules: {
org: [{ required: true, message: "请选择", trigger: "change" }],
name: [{ required: true, message: "请选择", trigger: "change" }],
},
};
},
mounted() {
setTimeout(() => {
this.form.org = '12'
this.treeParams.defaultExpandedKeys = [this.form.org]
}, 100);
},
methods: {
reset() {
// 重置清空的时候必须先赋空值,再使用 resetFields() 函数,否则会报红
this.form.org = "";
this.$nextTick(() => {
this.$refs.form.resetFields();
});
},
},
};
</script>
<style lang="less">
.myClass {
padding: unset; // 去掉原始边距
.el-tree-node__content {
padding: 5px 0; // 设置 node 间距
}
.is-current { // 设置选中字体颜色
color: #409eff;
.el-tree-node__children {
color: #606266; // 设置选中节点的子节点字体颜色
};
}
overflow: hidden; // 隐藏原始滚动条
}
</style>