背景:公司项目的每一个页面基本上都有el-form。而且不同的el-form里面有部分el-form-item是相同的,于是就想把这部分相同的el-form-item封装成一个el-form即组件d2-page-form。为什么要封装成一个el-form?因为需要配置rules。d2-page-form是页面原来的el-form的子组件。在封装组件A的过程中,又想到了把el-form二次封装,这样就可以满足页面中不再写el-form、el-form-item的html代码了,直接封装一个组件,给组件传入对应的属性就可以了。
组件d2-page-form代码:
<template>
<div>
<el-form :inline="inline" :label-width="labelWidth" :model="data" :class="[formClass]" :rules="rules" ref="form">
<template v-if="isCustomDept">
<el-form-item label="自定义部门1" prop="customDeptId1">
<el-select class="width-200" size='mini' v-model="data.customDeptId1" placeholder="请选择" clearable filterable
allow-create default-first-option :disabled="disabledCustomDept" v-loading="loading1">
<el-option v-for="item in customDept.departmentData1" :key="item.deptId" :label="item.deptName"
:value="item.deptId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="自定义部门2" prop="customDeptId2">
<el-select class="width-200" size='mini' v-model="data.customDeptId2" placeholder="请选择" clearable filterable
allow-create default-first-option :disabled="disabledCustomDept" v-loading="loading2">
<el-option v-for="item in customDept.departmentData2" :key="item.deptId" :label="item.deptName"
:value="item.deptId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="自定义部门3" prop="customDeptId3">
<el-select class="width-200" size='mini' v-model="data.customDeptId3" placeholder="请选择" clearable filterable
allow-create default-first-option :disabled="disabledCustomDept" v-loading="loading3">
<el-option v-for="item in customDept.departmentData3" :key="item.deptId" :label="item.deptName"
:value="item.deptId">
</el-option>
</el-select>
</el-form-item>
</template>
<template v-if="getConfigList.length">
<el-form-item v-for="(item, index) in getConfigList" :key="index" :prop="item.value" :label="item.label"
:class="item.className">
<!-- solt -->
<template v-if="item.type === 'slot'">
<slot :name="'form-' + item.value" />
</template>
<!-- 普通输入框 -->
<el-input v-if="item.type === 'input' || item.type === 'password'" v-model="data[item.value]"
:type="item.type" clearable :disabled="item.disabled" :placeholder="getPlaceholder(item)"
@focus="handleEvent(item.event)" />
<!-- 文本输入框 -->
<el-input v-if="item.type === 'textarea'" v-model.trim="data[item.value]" :type="item.type"
:maxlength="item.maxlength" show-word-limit clearable :disabled="item.disabled"
:placeholder="getPlaceholder(item)" :autosize="item.autosize || {minRows: 2, maxRows: 10}"
@focus="handleEvent(item.event)" />
<!-- 计数器 -->
<el-input-number v-if="item.type === 'inputNumber'" v-model="data[item.value]" size="small" :min="item.min"
:max="item.max" controls-position="right" @change="handleEvent(item.event)" />
<!-- 选择框 -->
<el-select v-if="item.type === 'select'" v-model="data[item.value]" :disabled="item.disabled" clearable
:filterable="item.filterable" :placeholder="getPlaceholder(item)"
@change="handleEvent(item.event, data[item.value])">
<el-option v-for="(childItem, childIndex) in listTypeInfo[item.list]" :key="childIndex"
:label="childItem.key" :value="childItem.value" />
</el-select>
<!-- 单选框 -->
<el-radio-group v-if="item.type === 'radio'" v-model="data[item.value]" :disabled="item.disabled"
:placeholder="getPlaceholder(item)" @change="handleEvent(item.event, data[item.value])">
<el-radio-button v-for="(childItem, childIndex) in listTypeInfo[item.list]" :key="childIndex"
:label="childItem.value">{{childItem.key}}</el-radio-button>
</el-radio-group>
<!-- 日期选择框 -->
<el-date-picker v-if="item.type === 'date'" v-model="data[item.value]" :type="item.dateType"
:picker-options="datePickerOptions[item.pickerOptions]" clearable :disabled="item.disabled"
:default-time="item.defaultTime" :placeholder="getPlaceholder(item)" @focus="handleEvent(item.event)"
start-placeholder="请选择开始日期" end-placeholder="请选择结束日期" />
<!-- 信息展示框 -->
<el-tag v-if="item.type === 'tag'">
{{ $fn.getDataName({dataList: listTypeInfo[item.list], value: 'value', label: 'key', data: data[item.value]}) || '-' }}
</el-tag>
</el-form-item>
</template>
<slot></slot>
</el-form>
<el-divider v-if="inline&&getConfigList.length"></el-divider>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'd2-custom-dept-form',
props: {
labelWidth: {
type: String,
default: '100px'
},
rules: {
type: Object
},
inline: {
type: Boolean,
default: true
},
// 自定义类名
className: {
type: String
},
// 表单数据
data: {
type: Object
},
// 相关的列表
listTypeInfo: {
type: Object
},
refObj: {
type: Object
},
// 相关字段
fieldList: {
type: Array,
default () {
return []
}
},
// 是否是自定义部门
isCustomDept: {
type: Boolean,
default: true
},
// 自定义部门是否能选
disabledCustomDept: {
type: Boolean,
default: false
},
// 是否是内嵌表单
embed: {
type: Boolean,
default: true
}
},
data () {
return {
datePickerOptions: {
// 不能选择当前之前的时间
disabledCurrentBefore: {
disabledDate (time) {
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 1)
return time.getTime() < start.getTime()
}
}
},
loading1: false,
loading2: false,
loading3: false,
customDept: {}
}
},
computed: {
...mapGetters(['appId']),
getConfigList () {
if (this.fieldList.length) {
return this.fieldList.filter(item => !item.hasOwnProperty('show') || (item.hasOwnProperty('show') && item.show))
} else {
return []
}
},
formClass () {
if (this.inline) {
if (this.embed) {
return ''
} else {
return 'search-form-box'
}
} else {
return ''
}
}
},
methods: {
// 得到placeholder的显示
getPlaceholder (row) {
let placeholder
if (row.type === 'input' || row.type === 'textarea') {
placeholder = '请输入' + row.label
} else if (row.type === 'select' || row.type === 'time' || row.type === 'date') {
placeholder = '请选择' + row.label
} else {
placeholder = row.label
}
return placeholder
},
// 绑定的相关事件
handleEvent (e) {
this.$emit('handleEvent', e)
},
getDepartment () {
this.loading1 = true
this.loading2 = true
this.loading3 = true
this.$http.post('/cl-applets/cms/content/department/search', {
customDeptLevel: 'CUSTOM_DEPT_1',
appId: this.appId
}).then((res) => {
this.customDept.departmentData1 = res.data.filter(item => item.deptId)
this.loading1 = false
this.saveDept('customDept.departmentData1', this.customDept.departmentData1)
})
this.$http.post('/cl-applets/cms/content/department/search', {
customDeptLevel: 'CUSTOM_DEPT_2',
appId: this.appId
}).then((res) => {
this.customDept.departmentData2 = res.data.filter(item => item.deptId)
this.loading2 = false
this.saveDept('customDept.departmentData2', this.customDept.departmentData2)
})
this.$http.post('/cl-applets/cms/content/department/search', {
customDeptLevel: 'CUSTOM_DEPT_3',
appId: this.appId
}).then((res) => {
this.customDept.departmentData3 = res.data.filter(item => item.deptId)
this.loading3 = false
this.saveDept('customDept.departmentData3', this.customDept.departmentData3)
})
},
saveDept (path, value) {
this.$store.dispatch('d2admin/db/set', {
dbName: 'sys',
path: path,
value: value,
user: true
}, {
root: true
})
},
async initDept () {
if (this.isCustomDept) {
this.customDept = await this.$store.dispatch('d2admin/db/get', {
dbName: 'sys',
path: 'customDept',
defaultValue: {},
user: true
}, {
root: true
})
if (!this.customDept || !this.customDept.departmentData1) {
this.getDepartment()
}
}
}
},
watch: {
appId () {
if (this.isCustomDept) {
this.getDepartment()
}
},
data: {
handler: function (val) {
// 将form实例返回到父级
this.$emit('update:refObj', this.$refs.form)
},
deep: true // 深度监听
}
},
mounted () {
this.initDept()
// 将form实例返回到父级
this.$emit('update:refObj', this.$refs.form)
}
}
</script>
<style scoped>
.search-form-box {
background: #f5f7fa;
padding: 20px;
}
</style>
关于组件d2-page-form在页面的使用:
<template>
<d2-container class="page" v-loading="loading">
<template slot="header">
<el-button type="primary" plain icon="el-icon-plus" @click="addmember">新建口令</el-button>
</template>
<d2-page-form :data="searchForm.data" :ref-obj.sync="searchForm.ref" :fieldList="searchForm.fieldList"
:embed="false">
<slot>
<el-form-item>
<el-button type="primary" @click="searchInput">筛选</el-button>
</el-form-item>
</slot>
</d2-page-form>
<d2-table :columns="columns" :table-data="list" :pagination="pagination" @page-size-change="handleOnPageSizeChange"
@page-current-change="handleOnCurrntChange">
<template slot-scope="{ scope }" slot="cz">
<el-button type="text" @click="handleLook(scope.row)">查看</el-button>
<el-button type="text" @click="handleEdit(scope.row)" class="d2-ml">编辑</el-button>
</template>
<template slot="status" slot-scope="{ scope }">
<span v-if="scope.row.status === 'NOT_STARTED'" style="color: #409eff">即将开场</span>
<span v-else-if="scope.row.status === 'OPENED'" style="color: #13ce66">进行中</span>
<span v-else-if="scope.row.status === 'FINISHED'" style="color: #ff4949">已结束</span>
</template>
</d2-table>
<el-dialog :title="isEdit === false ? '新建口令' : '编辑口令'" center :visible.sync="dialogVisible" width="50%"
:before-close="beforeClose">
<d2-page-form :data="commandForm.data" :ref-obj.sync="commandForm.ref" :rules="commandForm.rules" :inline="false"
:fieldList="commandForm.fieldList" :list-type-info="commandForm.listTypeInfo" labelWidth="25%">
</d2-page-form>
<span slot="footer">
<el-button type="primary" @click="submit">确定</el-button>
<el-button @click="reset">取消</el-button>
</span>
</el-dialog>
</d2-container>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'command',
data () {
var validatorStartDate = (rule, value, callback) => {
if (!value) {
callback(new Error('请选择开始时间'))
} else {
if (this.$moment(new Date()).isAfter(this.$moment(value))) {
callback(new Error('开始时间不能早于当前时间'))
} else {
callback()
}
}
}
var validatorEndDate = (rule, value, callback) => {
if (!value) {
callback(new Error('请选择结束时间'))
} else {
if (this.$moment(this.commandForm.data.startTime).isAfter(this.$moment(value))) {
callback(new Error('结束时间不能早于开始时间'))
} else {
callback()
}
}
}
return {
loading: false,
dialogVisible: false,
columns: [
{
label: '口令时间范围',
key: 'timeRange',
width: 170
}, {
label: '自定义部门1',
key: 'customDeptId1',
width: 100
}, {
label: '口令名称',
key: 'name'
}, {
label: '口令状态',
key: 'status'
}, {
label: '口令描述',
key: 'description'
}, {
label: '判分模式',
key: 'scoreModeDisplay'
}, {
label: '允许刷分次数',
key: 'joinCount',
width: 120
}, {
label: '允许参与人数',
key: 'joinUserCount',
width: 140
}, {
label: '允许单个用户的练习次数',
key: 'singlePersonPracticeCount',
width: 180
}, {
label: '参与人数',
key: 'joinUv'
}, {
label: '每人最大参与次数',
key: 'singlePersonJoinCount',
width: 140
}, {
label: '排序',
key: 'sort'
}, {
label: '操作',
key: 'cz',
fixed: 'right',
width: 100
}
],
list: [],
commandForm: {
ref: null,
data: {},
fieldList: [{
label: '判分规则', value: 'scoreMode', type: 'radio', list: 'scoreModeList'
}, {
label: '开始时间', value: 'startTime', type: 'date', dateType: 'datetime', pickerOptions: 'disabledCurrentBefore'
}, {
label: '结束时间', value: 'endTime', type: 'date', dateType: 'datetime', pickerOptions: 'disabledCurrentBefore'
}, {
label: '口令名称', value: 'name', type: 'input', className: 'width-300'
}, {
label: '口令内容', value: 'content', type: 'textarea', className: 'width-300', maxlength: 350
}, {
label: '允许刷分次数', value: 'joinCount', type: 'inputNumber', min: 0
}, {
label: '允许参与人数', value: 'joinUserCount', type: 'inputNumber', min: 0
}, {
label: '允许单个用户的练习次数', value: 'singlePersonPracticeCount', type: 'inputNumber', min: 0
}, {
label: '每人最大参与次数', value: 'singlePersonJoinCount', type: 'inputNumber', min: 0
}, {
label: '排序', value: 'sort', type: 'inputNumber', min: 0
}],
rules: {
startTime: [{ required: true, validator: validatorStartDate, trigger: 'change' }],
endTime: [{ required: true, validator: validatorEndDate, trigger: 'change' }],
name: [{ required: true, message: '请输入口令名称', trigger: 'blur' }],
content: [{ required: true, message: '请输入口令内容', trigger: 'blur' }],
chainDeptIdList: [{ required: true, message: '请选择连锁', trigger: 'blur' }],
scoreMode: [{ required: true, message: '请选择判分规则', trigger: 'blur' }],
customDeptId1: [{ required: true, message: '请选择自定义部门1', trigger: 'blur' }]
},
listTypeInfo: {
scoreModeList: [{ key: '接口识别', value: 'AI' }, { key: '自适应识别', value: 'DEFAULT' }]
}
},
isClicked: false,
pagination: {
auto: false,
total: 0,
pageSize: 20,
current: 1
},
isEdit: false,
searchForm: {
ref: null,
data: {},
fieldList: [{
label: '口令名称', value: 'name', type: 'input', className: 'width-300'
}, {
label: '口令时间', value: 'time', type: 'date', dateType: 'datetimerange', defaultTime: ['00:00:00', '23:59:59']
}]
}
}
},
computed: {
...mapGetters(['appId'])
},
methods: {
getList (searchData) {
this.dialogVisible = false
this.loading = true
let data = {
pageAsc: false,
pageCurrent: this.pagination.current,
pageSearchCount: true,
pageSize: this.pagination.pageSize,
appId: this.appId
}
if (searchData) {
data = Object.assign(searchData, data)
}
this.$http.post('/cl-applets/word/search', data).then((res) => {
this.loading = false
this.pagination.total = Number(res.data.total)
if (res.data.records) {
this.list = res.data.records.map(item => {
item.scoreModeDisplay = item.scoreMode === 'AI' ? '接口识别' : '自适应识别'
item.timeRange = `${item.startTime}-${item.endTime}`
return item
})
} else {
this.list = []
}
}).catch((res) => {
this.loading = false
})
},
handleOnPageSizeChange (val) {
this.pagination.pageSize = val
this.pagination.current = 1
this.getList()
},
handleOnCurrntChange (val) {
this.pagination.current = val
this.getList()
},
searchInput () {
this.pagination.current = 1
let searchData = { ...this.searchForm.data }
if (searchData.time) {
searchData.beginDateTime = this.$moment(searchData.time[0]).format(
'YYYY-MM-DD HH:mm:ss'
)
searchData.endDateTime = this.$moment(searchData.time[1]).format(
'YYYY-MM-DD HH:mm:ss'
)
}
delete searchData.time
this.getList(searchData)
},
addmember () {
this.isEdit = false
this.isClicked = false
this.dialogVisible = true
this.commandForm.data = {}
},
beforeClose () {
this.dialogVisible = false
},
reset () {
this.dialogVisible = false
},
handleLook (row) {
this.$router.push({
path: `/careflow/content/command/detail/${row.id}?data=${JSON.stringify(row)}`
})
},
handleEdit (row) {
this.isEdit = true
this.dialogVisible = true
this.commandForm.data = { ...row }
this.isClicked = false
},
submit () {
if (this.isClicked) {
return
}
this.commandForm.ref.validate((valid) => {
if (valid) {
this.dialogVisible = false
this.loading = true
let data = { ...this.commandForm.data }
data.appId = this.appId
data.startTime = this.$moment(this.commandForm.data.startTime).format('YYYY-MM-DD HH:mm:ss')
data.endTime = this.$moment(this.commandForm.data.endTime).format('YYYY-MM-DD HH:mm:ss')
if (this.isEdit) {
delete data.scoreModeDisplay
delete data.timeRange
}
this.$http.post('/cl-applets/word', data).then((res) => {
this.loading = false
if (res.code === '200' || res.code === 200) {
this.$message({
message: '操作成功!',
type: 'success'
})
this.getList()
}
this.isClicked = true
}).catch((res) => {
this.loading = false
this.isClicked = true
})
}
})
}
},
mounted () {
this.getList()
}
}
</script>
<style scoped>
</style>
参考了以下博客:
组件化页面:封装el-form
配置化el-form的二次封装之思路分析附上代码可直接使用