Vue自定义组件设计(ElementUI)

自定义ElementUI中form表单组件

如何去自定义组件呢?首先根据如何去使用组件来构建组件,然后去实现每一个组件的细节。这里我们构建一个自定义input组件,实现双向数据绑定。

项目目录结构

1、环境使用vuecli3.0构建的项目
2、实现思路
   2-1:首先通过使用elementUI的表单组件的结构,构建自定义表单组件结构。
   2-2:从最里层开始实现,首先实现input自定义组件。由于k-input使用了v-model,在k-input组件中,需实现双向数据绑定,绑定value,监听input事件并分发。v-model的实现原理就是绑定value和监听input事件。
   2-3:表单校验需要安装一个校验库npm i async-validator

image.png

Index.vue

<template>
  <div>
    <h3>Element表单</h3>
    <!-- model rules 需要放到form组件上,原因是内层的form-item都需要使用 -->
    <k-form :model="model" :rules="rules" ref="loginForm">
      <k-form-item label="用户名" prop="username">
        <k-input v-model="model.username" autocomplete="off" placeholder="请输入用户名"></k-input>
      </k-form-item>
      <k-form-item label="密码" prop="password">
        <k-input v-model="model.password" type="password" autocomplete="off" placeholder="请输入密码"></k-input>
      </k-form-item>
      <k-form-item>
        <button @click="submitForm('loginForm')">提交</button>
      </k-form-item>
    </k-form>
  </div>
</template>

<script>
import KForm from "./KForm";
import KFormItem from "./KFormItem";
import KInput from "./KInput";
export default {
  components: {
    KForm,
    KFormItem,
    KInput
  },
  data() {
    return {
      model: {username:'',password:''},
      rules:{
        username:[{required:true,message:'请输入用户名'}],
        password:[{required:true,message:'请输入密码'}],
      }
    }
  },
  methods: {
    submitForm(form) {
      this.$refs[form].validate(valid => {
        if (valid) {
          alert("请求登录");
        } else {
          alert("校验失败");
        }
      });
    }
  }
};
</script>

<style lang="scss" scoped></style>

KInput.vue

<template>
  <div>
    <!-- 将k-input组件上的属性展开放到input组件是 -->
    <input v-bind="$attrs" :value="value" @input="inputHandle">
  </div>
</template>

<script>
  export default {
    // 取消默认将属性放在div上
    inheritAttrs:false,
    props:{
      value:{
        type:String,
        default:''
      }
    },
    methods: {
      inputHandle(e) {
        // 分发k-input的input的事件,实现双向数据绑定
        this.$emit('input',e.target.value)
        // 分发校验事件 每次输入框输入了数据就就行校验
        this.$parent.$emit('validate')
      }
    },
  }
</script>

<style lang="scss" scoped>

</style>

KFormItem.vue

<template>
  <div>
    <label v-if="label">{{ label }}</label>
    <slot></slot>
    <p v-if="errorMsg">{{ errorMsg }}</p>
  </div>
</template>

<script>
import Schema from 'async-validator'
  export default {
    inject:['form'],
    props: {
      label: {
        type: String,
        default: ''
      },
      prop:{
        type:String
      }
    },
    data(){
      return {
        errorMsg:''
      }
    },
    mounted(){
      this.$on('validate',this.validate)
    },
    methods: {
      validate() {
        const value = this.form.model[this.prop]
        const rules = this.form.rules[this.prop]
        // 校验的描述对象
        const desc = {[this.prop]:rules}
        const schema = new Schema(desc)
        // 校验  return返回的是promise对象
        return schema.validate({[this.prop]:value},errors=>{
          if(errors){
            this.errorMsg = errors[0].message
          }else{
            this.errorMsg = ''
          }
        })
      }
    },
  }
</script>

<style lang="scss" scoped>

</style>

KForm.vue

<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  provide() {
    return {
      form: this
    };
  },
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  },
  methods: {
    validate(cb) {
      const tasks = this.$children
        .filter(item => item.prop)
        .map(item => item.validate());
      Promise.all(tasks)
        .then(() => cb(true))
        .catch(() => cb(false));
    }
  }
};
</script>

<style lang="scss" scoped></style>

自定义信息提示框组件

这类组件的特点在于,它们是独立于当前Vue实例之外独立存在的,通常挂载在body上,是通过JavaScript动态创建的,不需要在任何组件中声明。

目录结构

image.png

KNotice.vue

<template>
  <div class="show-box" v-if="isShow">
    <h3>{{ title }}</h3>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: false
    }
  },
  props: {
    message: {
      type: String
    },
    title: {
      type: String
    },
    duration: {
      type: Number,
      default:1000
    }
  },
  methods: {
    show() {
      this.isShow = true
      setTimeout(this.hide, this.duration);
    },
    hide(){
      this.isShow = false
      this.remove() 
    }
  },
};
</script>

<style lang="scss" scoped>
  .show-box{
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
    margin: auto;
    width: 200px;
    height: 200px;
  }
</style>

Index.vue

<template>
  <div>
    <button @click="ShowHandle">显示</button>
  </div>
</template>

<script>
import create from "../../utils/create";
import Notice from "./KNotice";
export default {
  methods: {
    ShowHandle() {
      const notice = create(Notice, {
        title: "校验成功",
        message: "你好牛逼哦",
        duration: 2000
      });
      notice.show()
    }
  }
};
</script>

<style lang="scss" scoped></style>

create.js

import Vue from "vue";
export default function create(Component, props) {
  // 创建Vue实例 先创建实例不挂载 直接将虚拟DOM挂载body上 会报错的
  const vm = new Vue({
    // 创建虚拟DOM 入参:createElement简写为h
    render(h) {
      // h函数返回的是虚拟DOM
      return h(Component, { props });
    }
  }).$mount();
  // 手动挂载  vm.$el是真实DOM
  document.body.appendChild(vm.$el)
  // 销毁
  const comp = vm.$children[0]
  comp.remove = function(){
    // 移除DOM
    document.body.removeChild(vm.$el)
    // 销毁组件
    vm.$destroy()
  }
  return comp
}

自定义树形组件

目录结构

image.png

Item.vue

<template>
  <li>
    <div @click="toggle">
      {{ model.title }}
      <span v-if="isFolder">[{{ open ? "-" : "+" }}]</span>
    </div>
    <ul v-show="open" v-if="isFolder">
      <item
        class="item"
        v-for="model in model.children"
        :model="model"
        :key="model.id"
      ></item>
    </ul>
  </li>
</template>

<script>
export default {
  name: "Item",
  props: {
    model: {
      type: Object
    }
  },
  data() {
    return {
      open: false
    };
  },
  computed: {
    isFolder() {
      return this.model.children && this.model.children.length;
    }
  },
  methods: {
    toggle() {
      if (this.isFolder) {
        this.open = !this.open;
      }
    }
  }
};
</script>

<style lang="scss" scoped></style>

index.vue

<template>
  <div>
    <ul>
      <item :model="treeData"></item>
    </ul>
  </div>
</template>

<script>
import Item from "./Item";
export default {
  components: {
    Item
  },
  data() {
    return {
      treeData: {
        title: "web全栈架构师",
        children: [
          {
            title: "java工程师",
            children: [
              {
                title: "python工程师"
              }
            ]
          },
          {
            title: "go工程师",
            children: [
              {
                title: "C#工程师"
              }
            ]
          }
        ]
      }
    };
  }
};
</script>

<style lang="scss" scoped></style>

效果图
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容