高级插件开发是不用在使用到组件的每个vue页面都注册组件这样会很麻烦,我们使用this.$notify的api方式调用
一.notifcation组件开发
目录结构如下:
notification.vue代码如下:
<!--
* @Description:
* @Version: 2.0
* @Autor: Cookie
* @Date: 2022-06-01 15:27:41
* @LastEditors: Zhang
* @LastEditTime: 2022-06-08 11:36:13
-->
<template>
<transition name="fade" @after-leave="afterLeave" @after-enter="afterEnter">
<div
class="notification"
:style="style"
v-show="visible"
@mouseenter="clearTimer"
@mouseleave="createTimer"
>
<span class="content">{{ content }}</span>
<a class="btn" @click="handleClose">{{ btn }}</a>
</div>
</transition>
</template>
<script>
export default {
name: "Notification",
props: {
content: {
type: String,
required: true
},
btn: {
type: String,
default: "关闭"
}
},
data() {
return {
visible: false
};
},
computed: {
style() {
return {};
}
},
methods: {
handleClose(e) {
e.preventDefault();
this.$emit("close"); // 即将关闭
},
afterLeave() {
// 动画结束的时候触发
this.$emit("closed"); // 已经关闭
},
afterEnter() {}, // 动画完成后的时候触发
clearTimer() {}, // 最好是声明下 可能会引起报错提示
createTimer() {}
}
};
</script>
<style scoped>
.notification {
display: inline-flex;
background-color: #303030;
color: rgba(255, 255, 255, 1);
align-items: center;
padding: 20px;
min-width: 280px;
box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
flex-wrap: wrap;
transition: all 0.3s;
}
.content {
padding: 0;
}
.btn {
color: #ff4081;
padding-left: 24px;
margin-left: auto;
cursor: pointer;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
index.js页面如下:
import Notification from './notification.vue'
import notify1 from './function'
export default (Vue) => {
Vue.component(Notification.name, Notification) //
Vue.prototype.$notify1 = notify1 // 无需在main.js
}
func-notification.js
/*
* @Description:
* @Version: 2.0
* @Autor: Cookie
* @Date: 2022-06-06 11:29:20
* @LastEditors: Zhang
* @LastEditTime: 2022-06-08 11:21:10
*/
import Notification from './notification.vue'
export default {
extends: Notification,
data() {
return {
verticalOffset: 0,
autoClose: 4000,
height: 0, //transition动画结束之前是拿不到height,需在@after-enter动画完成后 才能拿到height值
// visible: false,
}
},
created() {
// console.log(this.$data.verticalOffset)
},
mounted() {
// console.log('-------')
// console.log('data', this.$data)
// console.log(this.$data.verticalOffset)
// console.log(this.$data.verticalOffset)
// console.log('mounted', this.$root)
this.createTimer()
console.log('data', this.autoClose) //4000 继承的组件能覆盖父组件(被继承的组件)的值
console.log('data', this.visible) //true 继承的组件能获取到父组件
},
destroyed() {
this.clearTimer()
},
computed: { // 这里得computed会覆盖到 notification.vue中的computed
style() {
return {
position: 'fixed',
left: '20px',
bottom: `${this.verticalOffset}px` // 这里是拿到function.js定义的值
}
}
},
methods: {
createTimer() {
if (this.autoClose) {
this.timer = setTimeout(() => {
this.visible = false
}, this.autoClose)
}
},
clearTimer() {
if (this.timer) {
clearTimeout(this.timer)
}
},
afterEnter() { // 动画完成后才能获取高度
this.height = this.$el.offsetHeight
},
},
}
function.js
/*
* @Description:
* @Version: 2.0
* @Autor: Cookie
* @Date: 2022-06-06 11:29:20
* @LastEditors: Zhang
* @LastEditTime: 2022-06-20 16:21:19
*/
import Com from './func-notification'
import Vue from 'vue'
const NotificationConstructor = Vue.extend(Com)
let instances = []
let seed = 1
const removeInstance = (instance) => {
if (!instance) return
let index = instances.findIndex(item => instance.id === item.id)
instances.splice(index, 1)
let len = instances.length
if (len <= 1) return // 只有一个instance
const removeHeight = instance.vm.height // 获取动画完成后的高度
for (let i = index; i < len - 1; i++) { // list上面的instance才能删除
instances[i].verticalOffset =
parseInt(instances[i].verticalOffset) - removeHeight - 16
}
}
const notify = (options) => { //最终我们调用的函数this.notify()
console.log(options) //{content: '你真好', btn: '关'} options是这里面的值==》this.$notify1({content: "你真好",btn: "关"});
let { autoClose, ...rest } = options // 我们希望atuoclose是data的形式传入
let instance = new NotificationConstructor({
// propsData: options,
propsData: { ...rest },
data: { // data形式传入
autoClose: autoClose === undefined ? 3000 : autoClose
}
})
let id = seed++
instance.id = id // 给组件实例对象添加字段id, instance.vm也是类似
instance.vm = instance.$mount() // 只创建dom节点 但是没有挂载到document上
document.body.appendChild(instance.vm.$el) // 挂载到body上
instance.vm.visible = true // vue中默认是false 这里声明组件是显示的
var verticalOffset = 0
instances.forEach(item => {
verticalOffset += item.$el.offsetHeight + 16 //每个信息框相隔16
})
verticalOffset += 16 //默认第一个与底部距离16
instance.verticalOffset = verticalOffset
console.log(instance.verticalOffset)
instances.push(instance)
instance.vm.$on('closed', () => {
removeInstance(instance)
document.body.removeChild(instance.vm.$el) // 删除dom节点,一定要放在上面 不然vm对象销毁了无法调用$el
instance.vm.$destroy() // 会销毁整个vm对象 如监听的事件和方法,但不会删除dom节点
// console.log('closed')
})
instance.vm.$on('close', () => {
instance.vm.visible = false
})
return instance.vm
}
export default notify
/*
优化
1. 删除节点 v-show生成节点,及删除vm对象 (等消失的动画结束后我们再删除这样体验会好些),主要是释放内存
2. 鼠标移上去不会自动消失
*/
在main.js中引用
import Notification from './components/notifcation'
import Tabs from './components/tab'
Vue.use(Tabs)
Vue.use(Notification)
Notification使用:
<button @click="showCom" :class="{ active: true, red }">click me</button>
showCom() {
this.$notify({
content: "你真好",
btn: "关"
});
},
Notification效果图:
二. tabs组件开发
目录结构如下:
tabs.vue代码
<!--
* @Description:
* @Version: 2.0
* @Autor: Cookie
* @Date: 2022-06-13 17:02:43
* @LastEditors: Zhang
* @LastEditTime: 2022-06-14 16:34:01
-->
<script>
import TabContent from "./tab-content.vue";
export default {
name: "Tabs",
components: {
TabContent
},
provide() {
// provide不是响应式的,则监听不到value的改变,Object.defineProperty形式才能监听值得变化
// return {
// value: this.value
// };
const data = {};
Object.defineProperty(data, "value", {
get: () => this.value,
enumerable: true
});
return {
data
};
},
props: {
value: {
type: [String, Number],
required: true
}
},
data() {
return {
panes: []
};
},
render() {
console.log(this.panes);
// const contents = this.panes.map(pane => {
// return pane.$slots.default;
// }); // 返回所有content
// const contents = this.panes.map(pane => {
// return pane.active ? pane.$slots.default : null;
// }); // 只返回对应tab的值
// {this.$slots.default} jsx定义插槽的方式
/* 存在一个问题 会少一步显示如输入:abc ;输入ab
<div class="tabs">
<ul class="tabs-header">{this.$slots.default}</ul>
<div>{contents}</div>
</div>
*/
return (
<div class="tabs">
<ul class="tabs-header">{this.$slots.default}</ul>
<tab-content panes={this.panes}></tab-content>
</div>
);
},
methods: {
run() {
console.log("this is tabs runing");
},
onChange(index) {
console.log(`接收tab传得值:${index}`);
this.$emit("change", index);
}
}
};
</script>
<style lang="css" scoped>
.tabs-header {
display: flex;
list-style: none;
margin: 0;
padding: 0;
border-bottom: 2px solid #ededed;
}
</style>
tab.vue页面
<!--
* @Description:
* @Version: 2.0
* @Autor: Cookie
* @Date: 2022-06-14 10:00:42
* @LastEditors: Zhang
* @LastEditTime: 2022-06-14 16:27:46
-->
<script>
export default {
name: "Tab",
inject: ["value", "data"],
props: {
index: {
required: true,
type: [String, Number]
},
label: {
type: String,
default: "tab"
}
},
created() {
console.log("tab", this.$parent.value);
},
mounted() {
this.$parent.panes.push(this); // 把tab整个实例push到tabs中
},
computed: {
/*
<tabs>
<tab></tab>
</tabs>
由于tabs是tab的父级 所以this.$parent.value是tabs的值
若层级改变如下,this.$parent.value是panel的值明显这样处理不了我们的业务,可采用provide inject可以跨级获取值
<tabs>
<panel>
<tab></tab>
</panel>
</tabs>
*/
active() {
return this.$parent.value === this.index;
// return this.value === this.index;
// return this.data.value === this.index;
}
},
methods: {
handleClick() {
console.log(this.$parent); // this.$parent直接拿到父级得实例(方法和值)
this.$parent.run(); // this is tabs runing 能调用父级得方法
this.$parent.onChange(this.index); // 传值到tabs中,
}
},
render() {
const tab = this.$slots.label || <span>{this.label}</span>; // 优先使用插槽的label ,其次使用传进来的label
const classNames = {
tab: true,
active: this.active
};
return (
<li class={classNames} on-click={this.handleClick}>
{tab}
</li>
);
}
};
</script>
<style lang="css" scoped>
.tab {
list-style: none;
line-height: 40px;
margin-right: 30px;
position: relative;
bottom: -2px;
cursor: pointer;
}
.active {
border-bottom: 2px solid blue;
}
.tab:last-child {
margin-right: 0;
}
</style>
tab-content.vue
<!--
* @Description:
* @Version: 2.0
* @Autor: Cookie
* @Date: 2022-06-14 16:25:49
* @LastEditors: Zhang
* @LastEditTime: 2022-06-14 16:45:57
-->
<script>
export default {
props: {
panes: {
type: Array,
required: true
}
},
render() {
const contents = this.panes.map(pane => {
return pane.active ? pane.$slots.default : null;
});
console.log(contents);
return <div>{contents}</div>;
}
};
</script>
index.js
import Tabs from './tabs.vue'
import Tab from './tab.vue'
export default (Vue) => {
Vue.component(Tabs.name, Tabs)
Vue.component(Tab.name, Tab)
}
tabs使用:
<input type="text" v-model="modelInput" />
<tabs :value="tabValue" @change="handleChangeTab">
<tab index="1" label="tab1">
<p>this is content 1-{{ modelInput }}</p>
</tab>
<tab index="2">
<span slot="label" style="color:red">tab2</span>
<p>this is content 2</p>
</tab>
<tab index="3" label="tab3">
<p>this is content 3</p>
</tab>
</tabs>
handleChangeTab(value) {
console.log(arguments[0]);
this.tabValue = value;
},