vue仿饿了么写的一个简单的form组件

2,027 阅读5分钟

为什么写这篇博客

不久前,朋友说掘金社区有很多精彩的文章可以看(然而我来了后却发现,大佬们的文章我大部分看不下去【太难了】,反而是掘金的沸点比较有趣,很多搞笑的程序员使我近期迷上了逛社区),但是,光看不发总觉得像是在白嫖。所以呢,趁着最近公司的任务比较轻松,我就正好把近期看的一些文章以及练手的小demo拿出来记录一番,好让各位大佬嘲笑我的才疏学浅吧,而且也证明了我并非白嫖猿士,如果写的不对的或者有更好写法的地方,还要请各位看官能够细细提点......小女子自然是感激不尽

开始动手

工欲善其事必先利其器,这次用到vue,所以让我们先把vue环境搭建起来吧(前提是电脑中已经安装了node和npm) node下载地址,下载之后,点安装,然后一直下一步直到完成。

<!--将下载源切换到淘宝,下载速度会快一点-->
npm config set registry https://registry.npm.taobao.org

<!--安装vue-cli-->
npm i -g vue-cli

<!--创建并进入对应目录-->
mkdir mycomponent && cd mycomponent

<!--用vue初始化项目-->
vue init webpack

<!--安装依赖-->
npm i

<!--开始运行-->
npm run dev

到这里,请打开浏览器,输入localhost:8080,能看到一个vue的欢迎页面,那么恭喜你,可以继续看下去了。 为了不破坏vue官方提供的优美的欢迎页,我们利用路由来新增页面。所以请你回到编辑器,进入路由目录下的主文件src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

// 引入我们的新页面
import myForm from '@/components/myForm'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    },
    // 定义新的路由项
    {
      path: '/myForm',
      name: 'myForm',
      component: myForm
    }
  ]
})

好了,新页面的组件已经引入了,而且呢他的路由也写好了,但是,这个页面的组件我们还没有创建噢,这时候请在components目录下创建一个文件名称为myForm.vue

<template>
  <div>
    <h3>我是表单组件页面</h3>
  </div>
</template>

<script>
export default {
  name: 'MyForm'
}
</script>

<style scoped>

</style>

这时候,测试下路由是否生效,以及我们的页面是否能显示,需要在地址栏输入:http://localhost:8080/#/myform
你的生没生效我不知道,反正我的是成功了,如果你能看到下图这个页面,那我们就继续往下吧!


开始进入正题了。
因为代码过程需要使用到async-validator校验器,所以需要安装一下,可以单独安装async-validator,也可以直接安装element-ui(包含了async-validator)

<!--方法1,安装集成了async-validator的element-ui  新手推荐,因为这个框架有很多可以学习的地方,我本人是新手,我用的也是这种-->
npm install element-ui --save-dev

<!--方法2,单独安装 新手不推荐,老手推荐-->
npm install async-validator --save-dev

接下来的过程表述起来对我来说太困难啦,为了赶紧下班走人,于是乎我这边就直接贴代码啦!!!!!!!!!!!!! 各位大神见怪莫怪呀,如果有疑问的地方可以问我,我懂得话肯定告诉你哟!
根目录下的main.js文件

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App'
import router from './router'
Vue.config.productionTip = false
Vue.use(ElementUI)
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

// 引入我们的新页面myform
import MyForm from '@/components/myForm'
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    },
    // 定义新的路由项
    {
      path: '/MyForm',
      name: 'MyForm',
      component: MyForm
    }
  ]
})

src/App.vue

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

src/components/MyForm.vue

<template>
  <div>
    <h3>我是表单组件页面</h3>
    <my-form :model="ruleForm" :rules="rules" ref="myFrom">
      <my-form-item :label="label.username" prop="name">
        <my-input v-model="ruleForm.name"></my-input>
      </my-form-item>
      <my-form-item :label="label.password" prop="pwd">
        <my-input v-model="ruleForm.pwd" type="password"></my-input>
      </my-form-item>
      <my-form-item>
        <el-button type="primary" @click="submitForm">登录</el-button>
      </my-form-item>
    </my-form>
    <div>{{ruleForm}}</div>
  </div>
</template>

<script>
import myInput from './form/input'
import myFormItem from './form/formItem'
import myForm from './form/form'
export default {
  name: 'MyForm',
  components: {
    myInput,
    myFormItem,
    myForm
  },
  data () {
    return {
      label: {
        username: '用户名',
        password: '密码'
      },
      ruleForm: {
        name: 'aa',
        pwd: ''
      },
      rules: {
        name: [
          {required: true, message: '用户名不能为空'},
          {min: 8, message: '用户名必须大于8个字符'},
          {max: 16, message: '用户名必须小于16个字符'}
        ],
        pwd: [
          {required: true, message: '密码不能为空'},
          {min: 8, message: '密码必须大于8个字符'},
          {max: 16, message: '密码必须小于16个字符'}
        ]
      }
    }
  },
  methods: {
    submitForm () {
      this.$refs.myFrom.validate(ret => {
        if (ret === false) {
          return false
        } else {
          alert('登录成功')
        }
      })
    }
  }
}
</script>

<style scoped>

</style>


src/components/form/input.vue

<template>
  <input :value="inputVal" @input="handleInputChange" :type="type">
</template>

<script>
export default {
  name: 'input',
  props: {
    value: {
      type: String,
      default: ''
    },
    type: {
      type: String,
      default: 'text'
    }
  },
  data () {
    return {
      inputVal: this.value
    }
  },
  methods: {
    // input框的值发生变化
    handleInputChange (e) {
      this.inputVal = e.target.value
      this.$emit('input', e.target.value)
      // 通知formItem组件进行校验,显示校验结果
      this.$parent.$emit('doValidate', this.inputVal)
    }
  },
  created () {
  }
}
</script>

<style scoped>
input{
  line-height: 20px;
  padding: 6px 12px;
  background-color: #f5f5f5;
  border: none;
  border-radius: 4px;
  outline: none;
}
</style>

src/components/form/formItem.vue

<template>
  <div>
    <label v-if="label">{{label}}</label>
    <div>
      <!-- input -->
      <slot></slot>
      <!-- error info -->
      <p class="error-info" v-if="validateStatus == 'error'">{{errorMessage}}</p>
    </div>
  </div>
</template>

<script>
import Schema2 from 'async-validator'
export default {
  props: {
    // 表单名称,一般是字符串的形式
    label: {
      type: String
    },
    // 校验规则,数组的形式
    // [
    //   { required: true, message: '请输入活动名称', trigger: 'blur' },
    //   { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
    // ]
    prop: {
      type: String
    }
  },
  // 注入form 获取model和rules
  inject: ['form'],
  data () {
    return {
      // 校验状态, 为'error'或者''
      validateStatus: '',
      // 校验失败后的提示信息
      errorMessage: ''
    }
  },
  created () {
    // 监听自己在子组件input里面派发的事件doValidate,监听到之后执行函数
    this.$on('doValidate', this.validateHandle)
  },
  // 挂载完毕执行
  mounted () {
    // 如果传过来的校验不为空或者说有传prop过来
    if (this.prop) {
      // 让父组件触发某个时间,并且将自身这个对象传给父组件
      this.$parent.$emit('formItemAdd', this)
    }
  },
  methods: {
    validateHandle () {
      return new Promise(resolve => {
        // 将得到的input里面的最新值进行校验(使用async-validate库)需要安装 npm install element-ui
        const descriptor = {[this.prop]: this.form.rules[this.prop]}
        // 上面已经引入了async-validate,接下将校验规则descriptor当做参数,来new一个实例,并且使用实例的validate方法
        const validator = new Schema2(descriptor)
        // 两个参数,第一个是你要校验的键值{name: value},第二个是一个回调函数(判断是否校验通过)
        validator.validate({[this.prop]: this.form.model[this.prop]}, errors => {
          if (errors == null) {
            this.validateStatus = ''
            this.errorMessage = ''
            resolve(true)
          } else {
            this.validateStatus = 'error'
            this.errorMessage = errors[0].message
            resolve(false)
          }
        })
      })
    }
  }
}
</script>

<style>
.error-info{
  color: red;
}
</style>

src/components/form/form.vue

<template>
  <form>
    <slot></slot>
  </form>
</template>

<script>
export default {
  name: 'form',
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  },
  provide () {
    return {
      form: this
    }
  },
  data () {
    return {
    }
  },
  created () {
    this.fields = []
    this.$on('formItemAdd', item => {
      this.fields.push(item)
    })
  },
  mounted () {
  },
  methods: {
    async validate (callback) {
      const tasks = this.fields.map(item => item.validateHandle())
      const results = await Promise.all(tasks)
      let ret = true
      results.forEach(val => {
        if (!val) {
          ret = false
        }
      })
      callback(ret)
    }
  }
}
</script>

<style scoped>

</style>


结语

感谢各位看官看到这里!我们下一次见