胡一菲說過,做巧克力很簡單嘛,把商場里賣的巧克力買回來,融化后加點(diǎn)葡萄干、干果,然后再造型、凝固——巧克力就做好了。
是不是超簡單?
是的,在開源世界里面就是這么簡單,我們完全可以在開源的UI庫的基礎(chǔ)上實(shí)現(xiàn)自己的想法。
那么為啥要進(jìn)行二次封裝呢?
- 便于切換UI庫。
- 可以應(yīng)對版本升級。
- 可以循環(huán)綁定控件。
- 折騰后自己用著舒服。
定義“接口”
一般UI庫都會提供一些列的控件,比如form分類下面的控件,就可以有十多個(gè),那么要封裝的話,首先就要確定接口,否則只能封裝個(gè)寂寞。
簡潔期間,定義兩個(gè)屬性,一個(gè)事件。
modelValue
綁定控件值的,類型可以是string、number、數(shù)組等。meta
統(tǒng)一存放控件需要的各種屬性,也可以包括回調(diào)函數(shù)。input
大多數(shù)情況都可以用input事件,各個(gè)的需要change等事件,不過事件比較寬松,可以靈活設(shè)定。
這里的接口并不是面向?qū)ο蟮慕涌冢矝]有用TypeScript那樣的接口,而是一種約定和協(xié)議。原則上做限定,但是實(shí)際上還是可以放飛自我。
定義共用方法
vue2.x的時(shí)候需要使用mixin的方式,到了vue3.x 可以使用更靈活的方式來實(shí)現(xiàn)共用函數(shù)的目的。
inputManage
import { ref, watch } from 'vue'
const inputManage = (props, context) => {
// 綁定控件的值
const value = ref('')
// 監(jiān)聽屬性,給 value 賦值
watch(() => props.modelValue, (v1, v2) => {
value.value = v1
})
// 向父組件提交事件
const myInput = (e) => {
context.emit('update:modelValue', e)
context.emit('input', e)
}
return {
value,
myInput
}
}
export default inputManage
- 管理類的參數(shù)
因?yàn)橐玫浇M件的屬性和emit,所以需要設(shè)置兩個(gè)參數(shù)來接收組件傳遞過來的屬性和emit。
首先用 import 導(dǎo)入需要的方法,然后定義一個(gè)用于綁定控件的值 value——ref類型的——用著方便。
然后設(shè)置一個(gè) watch 監(jiān)控屬性變化,然后給value賦值。
最后設(shè)計(jì)一個(gè)提交的函數(shù)。
基本設(shè)定就是這樣,以后可以根據(jù)需求在完善,比如增加 blur 事件的提交,表單驗(yàn)證的時(shí)候可能會用到。
為啥不把組件的屬性直接綁定到控件的value?一開始我就是直接綁定的,結(jié)果出錯了。研究之后發(fā)現(xiàn),element-plus把原生的value給覆蓋了,變成了和 v-model 一樣的屬性了,這樣就不能直接綁定組件的屬性了。所以很無奈的做了一個(gè)內(nèi)部的轉(zhuǎn)換。
config
const metaInput = {
type: Object,
default: () => {
return {
// 通用
controlId: Number, // 編號,區(qū)別同一個(gè)表單里的其他控件
colName: String, // 字段名稱
controlType: Number, // 用類型編號表示type
isClear: {
// isClear 連續(xù)添加時(shí)是否恢復(fù)默認(rèn)值
type: Boolean,
default: false
},
defaultValue: String, // 默認(rèn)值
autofocus: { // 是否自動獲得焦點(diǎn)
type: Boolean,
default: false
},
disabled: {
// 是否禁用
type: Boolean,
default: false
},
required: { // 必填
type: Boolean,
default: true
},
readonly: { // 只讀
type: Boolean,
default: false
},
pattern: String, // 用正則做驗(yàn)證。
class: {
type: String,
default: 'ant-input ant-input-sm'
},
placeholder: String,
title: String, // 提示信息
size: Number, // 字符寬度
maxlength: Number, // 最大字符數(shù)
autocomplete: { // off
type: String,
default: 'on'
},
optionKey: String, // 備選文字標(biāo)識
optionItem: Object // 備選的選項(xiàng)
}
}
}
export default {
metaInput
}
這個(gè)是控件需要的屬性的說明和驗(yàn)證。這么長的代碼,當(dāng)然要放在獨(dú)立的地方了。
逐一封裝控件
然后就是辛苦活了,一個(gè)一個(gè)封裝唄,這里舉一個(gè)例子就好,不一一貼代碼了。
text 單行文本
<!--單行文本-->
<template>
<el-input
v-model="value"
@input="myInput"
:rows="12"
:autosize="{ minRows: 3, maxRows: 6 }"
:show-word-limit="true"
:maxlength="meta.maxlength"
:placeholder="meta.placeholder"
>
</el-input>
</template>
<script>
import inputManage from '../manage/inputManage.js'
import { metaInput } from '../manage/config.js'
export default {
name: 'nf-textarea',
props: {
modelValue: String,
meta: metaInput
},
emits: ['input', 'change', 'blur', 'focus', 'clear'],
setup (props, context) {
const { value, myInput } = inputManage(props, context)
return {
value,
myInput
}
}
}
</script>
template
按照控件需要的屬性,一個(gè)一個(gè)綁好。js代碼
因?yàn)榘压灿玫牟糠侄继崛〕鋈チ耍孕枰獙懙拇a就非常少了。
定義兩個(gè)屬性,屬性的具體定義放在共用函數(shù)里面,這里就很簡潔了。
setup里面就是獲取變量和方法,然后return就可以了。
其難點(diǎn)就是如何處理控件需要的各種屬性,需要一個(gè)一個(gè)綁定好,這是一個(gè)辛苦活,需要一個(gè)一個(gè)對照。
有些屬性還需要做一些轉(zhuǎn)換。不過都搞定后,以后用著就非常方便了。
復(fù)選組
<!--多選組-->
<template>
<el-checkbox-group v-model="checks" @change="myInput">
<el-checkbox :label="3">備選項(xiàng)</el-checkbox>
<el-checkbox :label="6">備選項(xiàng)</el-checkbox>
<el-checkbox :label="9">備選項(xiàng)</el-checkbox>
</el-checkbox-group>
</template>
import inputManage from '../manage/inputManage.js'
import { metaInput } from '../manage/config.js'
import { ref } from 'vue'
export default {
name: 'nf-el-from-checkbox',
props: {
modelValue: String,
meta: metaInput
},
emits: ['input', 'change', 'blur', 'focus', 'clear'],
setup (props, context) {
const { value, myInput } = inputManage(props, context)
const checks = ref([])
return {
value,
myInput,
checks
}
}
}
這個(gè)稍微有點(diǎn)不同,因?yàn)榭丶闹挡皇莝tring,而是一個(gè)數(shù)組,所以需要單獨(dú)設(shè)置一個(gè)數(shù)組來中轉(zhuǎn)一下。
如果組件有不同的需求,稍微做點(diǎn)修改就好。實(shí)在不行重做一個(gè)也行。js就是比較隨意。。。
其他的就不一一貼了,大同小異。
表單頁面里使用控件
都封裝好了,那么如何使用呢?
首先要引用進(jìn)來,然后設(shè)置屬性就可以了,比如這樣。
<el-form ref="form" :model="model" label-width="80px">
<el-form-item label="活動名稱">
<eltext v-model="model.name" :meta="metaText"/>
</el-form-item>
<el-form-item label="活動網(wǎng)址">
<elurl v-model="model.url" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item label="年齡要求">
<elnumber v-model="model.age" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item label="年齡要求">
<elrange v-model="model.age" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item label="活動區(qū)域">
<elselect v-model="model.select" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item label="活動日期">
<eldate v-model="model.date" :meta="metaText" @input="myChange"/> -
<eltime v-model="model.time" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item label="即時(shí)配送">
<elradios v-model="model.radio" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item label="活動性質(zhì)">
<elcheckbox v-model="model.checks" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item label="特殊資源">
<elswitch v-model="model.switch" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item label="活動形式">
<elarea v-model="model.contact" :meta="metaText" @input="myChange"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">立即創(chuàng)建</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
本來寫了一個(gè)el-row版本的,但是代碼太長,直接寫el-form版的吧。
import { reactive } from 'vue'
import eltext from '@/components/nf-el-form/input-text.vue'
import elarea from '@/components/nf-el-form/textarea.vue'
import elurl from '@/components/nf-el-form/url.vue'
import elnumber from '@/components/nf-el-form/input-number.vue'
import elrange from '@/components/nf-el-form/input-range.vue'
import eldate from '@/components/nf-el-form/input-date.vue'
import eltime from '@/components/nf-el-form/input-time.vue'
import elradios from '@/components/nf-el-form/radios.vue'
import elcheckbox from '@/components/nf-el-form/checkbox.vue'
import elselect from '@/components/nf-el-form/select.vue'
import elswitch from '@/components/nf-el-form/switch.vue'
export default {
name: 'eleBase',
components: {
eltext,
elarea,
elurl,
elnumber,
elrange,
eldate,
eltime,
elradios,
elcheckbox,
elselect,
elswitch
},
setup () {
// 定義表單實(shí)體類
const model = reactive({
age: 1,
url: '',
name: '',
contact: '',
date: null,
time: null,
radio: 1,
checks: [],
select: 1,
switch: true
})
// 定義樣式
const formTitleStyle = {
'text-align': 'right',
'padding-right': '10px'
}
// 控件類型的屬性
const metaText = reactive({
controlId: 103,
colName: 'controlType',
controlType: 190,
optionList: [],
isClear: false,
disabled: false,
required: true,
pattern: '',
title: '組件類型',
placeholder: '請輸入組件類型',
maxlength: 100,
readonly: false
})
const myChange = (e) => {
// alert(e)
console.log('changele')
}
return {
model,
formTitleStyle,
metaText,
myChange
}
}
}
首先是注冊組件,然后定義組件需要的屬性,然后return就可以了。
能不能簡化?
當(dāng)然可以了,這樣的代碼對于我這么懶的人,還是太麻煩了。
那么如何簡化呢?這個(gè)有很多種方法,比如v-for,比如做一個(gè)單獨(dú)的表單組件。
總之方法有很多,具體方法,下次再說。
先看看效果圖:
看圖是不是感覺沒啥特別的,這個(gè)嘛看效果當(dāng)然沒啥特別的了,關(guān)鍵是代碼如何實(shí)現(xiàn)的,你說對不。