05 Vue进阶高级插件(组件)开发

高级插件开发是不用在使用到组件的每个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;
    },

tabs效果图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值