好家伙,
在写项目的时候,我发现自己的平台的组件写的实在是太难看了,于是想去gitee上偷点东西,于是我们本期的受害者出现了
gitee项目地址
https://gitee.com/jjxliu306/ng-form-elementplus-sample.git
组件库以及引擎完全开源,非常牛逼的项目,非常牛逼的作者
项目名:ng-form-element
整体的布局,组件样式,编辑器模板,组件拖动时的过渡动画,都写的非常漂亮,(比我写的好看多了)
于是我决定,扒一下他的内裤,学习(抄袭)一下
0.项目解析
官方文档:NG-FORM
于是我们快速定位到引擎部分
1.开始分析
我们知道,低开的引擎做的事无非是
** * 把数据***
*** 变成视图***
我们先来找数据
数据部分
//packages/index.vue
data() {
return {
selectItem: {},
arrow: false,
i18nkey: getUUID(),
formTemplate: this.template || {
list: [
],
config: {
labelPosition: 'left',
labelWidth: 100,
size: 'mini',
outputHidden: true, // 是否输出隐藏字段的值 默认打开,所有字段都输出
hideRequiredMark: false,
syncLabelRequired: false,
labelSuffix: '' , // 标签后缀
customStyle: ''
}
},
}
},
props: {
template: {
type: Object,
default: () => {
return {
list: [],
config: {
labelPosition: 'top',
labelWidth: 80,
size: 'mini',
outputHidden: true, // 是否输出隐藏字段的值 默认打开,所有字段都输出
hideRequiredMark: false,
syncLabelRequired: false,
labelSuffix: '' , // 标签后缀
customStyle: ''
}
}
}
},
然后来找视图部分
//packages/index.vue
<ContainerPanel
:formTemplate="formTemplate"
@handleSelectItem="handleSelectItem"
:selectItem="selectItem"
:arrow="arrow"
>
</ContainerPanel>
import ContainerPanel from './panel-container/index.vue'
-
:formTemplate
:传模板数据的 -
@handleSelectItem
:这是一个事件处理器,当组件内的handleSelectItem
方法被触发时,会执行传入的回调函数。handleSelectItem
是组件内部定义的事件处理函数名。 -
:selectItem
:这是绑定的数据属性,用于传递一个"被选中的数据" -
:arrow
:暂时没看出来干嘛的
//packages/panel-container/index.vue
<el-form
:label-width="formTemplate.config.labelWidth + 'px'"
class="ng-form"
:label-position="formTemplate.config.labelPosition"
:hide-required-asterisk="formTemplate.config.hideRequiredMark"
:label-suffix="formTemplate.config.labelSuffix"
ref="form"
:style="formTemplate.config.customStyle"
:size="formTemplate.config.size"
>
<el-row :gutter="20" class="row">
<draggable
tag="div"
class="draggable-box"
v-bind="{
group: 'form-draggable',
ghostClass: 'moving',
animation: 180,
handle: '.drag-move'
}"
:force-fallback="true"
v-model="formTemplate.list"
@add="dragEnd($event, formTemplate.list)"
>
<transition-group tag="div" name="list" class="items-main">
<Node
:class="{'drag-move' : record.drag_ == undefined || record.drag_ }"
v-for="record in formTemplate.list"
:key="record.key"
:record="record"
:isDrag="true"
:config="formTemplate.config"
:selectItem="selectItem"
@handleSelectItem="handleSelectItem"
@handleCopy="handleCopy(record)"
@handleDetele="handleDetele(record)"
>
</Node>
</transition-group>
</draggable>
</el-row>
</el-form>
import Item from '../items/index.vue'
-
<transition-group>
是 Vue.js 的内置过渡组件,用于给列表添加过渡效果。 -
el-form的作用我们后面说
//packages/form-design/items/index.vue
<template>
<ItemNode
v-if="isLayout"
:record="record"
:disabled="disabled"
:preview="preview"
:isDragPanel="isDragPanel"
:prop-prepend="propPrepend"
:selectItem="selectItem"
:style="{'display': recordVisible ? '' : 'none'}"
:models="models"
@handleSelectItem="handleSelectItem"
>
<!-- 递归传递插槽!!! -->
<template v-for="slot in Object.keys($slots)" :slot="slot">
<slot :name="slot" :record="record"/>
</template>
</ItemNode>
<el-form-item
v-else
:label="label"
:style="{'display': recordVisible ? '' : 'none'}"
:rules="recordRules"
:prop="recordProps"
:key="record.key"
:required="recordRequired"
:id="record.model"
:name="record.model"
:label-width="labelWidth"
>
<ItemNode
:record="record"
:disabled="disabled"
:preview="preview"
:isDragPanel="isDragPanel"
:selectItem="selectItem"
:prop-prepend="propPrepend"
:models="models"
@handleSelectItem="handleSelectItem"
>
<!-- 递归传递插槽!!! -->
<template v-for="slot in Object.keys($slots)" :slot="slot">
<slot :name="slot" :record="record"/>
</template>
</ItemNode>
</el-form-item>
</template>
import ItemNode from './node.vue'
** 1.v-if="isLayout":是否为预览模式**
//packages/form-design/items/node.vue
<template>
<component
:record="record"
:style="{
margin: record.margin && record.margin.length > 0 ? record.margin.join('px ') + 'px' : '0px',
borderRadius: (record.itemBorderRadius ? record.itemBorderRadius : 0) + 'px',
backgroundColor: record.backgroundColor ? record.backgroundColor : '',
}"
:disabled="disabled"
:preview="preview"
:isDragPanel="isDragPanel"
:selectItem="selectItem"
:prop-prepend="propPrepend"
:models.sync="models"
@handleSelectItem="handleSelectItem"
@handleFocus="handleFocus"
@handleBlur="handleBlur"
:is="customComponent">
<!-- 递归传递插槽!!! -->
<template v-for="slot in Object.keys($slots)" :slot="slot">
<slot :name="slot" :record="record"/>
</template>
</component>
</template>
ok终于到了最后一层
最终的关键就是这么行代码
:is="customComponent"
:动态绑定组件名称,根据customComponent
的值来渲染不同的组件。
customComponent() {
// 判断是否自定义组件
if(this.customComponents && this.customComponents.length > 0) {
const cs = this.customComponents.filter(t=> t.type == this.record.type)
if(cs && cs.length > 0) {
return cs[0].component
}
}
const selectItemType = this.record.type
// 将数组映射成json
if(this.items && this.items.length > 0) {
for(let i = 0 ; i < this.items.length ; i++) {
const itemList = this.items[i]
if(itemList.list && itemList.list.length > 0) {
const fs = itemList.list.filter(t=>t.type == selectItemType)
if(fs && fs.length > 0) {
return fs[0].component
}
}
}
}
return null
},
2.数据格式
在这个项目上随便做的一个表格并导出数据
{
"list": [
{
"type": "input",
"options": {
"defaultValue": "",
"type": "text",
"prepend": "",
"append": "",
"placeholder": "请输入",
"maxLength": 0,
"clearable": false,
"hidden": false,
"disabled": false
},
"label": "输入框",
"labelWidth": -1,
"width": "100%",
"span": 24,
"model": "input_17156872001522",
"key": "input_17156872001522",
"rules": [
{
"required": false,
"message": "必填项",
"trigger": [
"blur"
]
}
],
"dynamicLabel": false
},
{
"type": "radio",
"options": {
"defaultValue": "",
"placeholder": "请输入",
"dynamic": 0,
"options": [
{
"value": "1",
"label": "选项1"
},
{
"value": "2",
"label": "选项2"
}
],
"methodType": "get",
"dynamicPostData": "",
"remoteFunc": "",
"dataPath": "",
"remoteValue": "",
"remoteLabel": "",
"dictType": "",
"disableItemScript": "",
"hidden": false,
"disabled": false,
"linkage": false,
"linkData": []
},
"label": "单选框",
"labelWidth": -1,
"width": "100%",
"span": 24,
"model": "radio_17156872321432",
"key": "radio_17156872321432",
"rules": [
{
"required": false,
"message": "必填项",
"trigger": [
"blur"
]
}
],
"dynamicLabel": false
},
{
"type": "button",
"event_": false,
"listen_": false,
"options": {
"size": "mini",
"type": "primary",
"align": "left",
"control": "",
"eventName": "",
"script": "",
"plain": false,
"circle": false,
"round": false,
"disabled": false
},
"label": "按钮",
"labelWidth": 0,
"width": "100%",
"span": 24,
"model": "button_17156901763582",
"key": "button_17156901763582",
"dynamicLabel": false
},
{
"type": "rate",
"options": {
"max": 5,
"defaultValue": 0,
"allowHalf": false,
"hidden": false,
"disabled": false
},
"label": "评分",
"labelWidth": -1,
"width": "100%",
"span": 24,
"model": "rate_17156901773022",
"key": "rate_17156901773022",
"rules": [
{
"required": false,
"message": "必填项",
"trigger": [
"blur"
]
}
],
"dynamicLabel": false
}
],
"config": {
"labelPosition": "top",
"labelWidth": 80,
"size": "mini",
"outputHidden": true,
"hideRequiredMark": false,
"syncLabelRequired": false,
"labelSuffix": "",
"customStyle": ""
}
}
type
:表示该组件的类型,该对象的类型为 "rate",用于评分。options
:表示该组件的选项,包括:label
:表示该组件的标签文本,值为 "评分"。labelWidth
:表示该组件的标签宽度,值为 -1,表示使用系统默认值。width
:表示该组件的宽度,值为 "100%"。span
:表示该组件所占的栅格数,值为 24。model
:表示该组件的 v-model 绑定值的变量名,值为 "rate\_17156901773022"。key
:表示该组件的唯一标识,值为 "rate\_17156901773022"。rules
:表示该组件的校验规则,包括:dynamicLabel
:表示该组件的标签是否动态显示,值为 false。
3.总结
在翻了许许多多的低开项目后,发现,
巨大多数的低开项目要么引擎核心闭源,要么物料组件库闭源
而这个项目,所有的东西都开源了,真真正正的开源,真的牛bi
非常值得自学的一个低开项目