vue+element-ui的列表查询条件/筛选条件组件二次封装(Vue项目)
一、需求
对于后台管理系统项目必不可少的就是 “增删改查”;而 "查",就会根据表格的列数来显示多少个查询/筛选条件;为了方便因此封装了查询条件(筛选条件)组件
二、组件功能
1、自动排列布局,每行显示4列(即4个条件) 2、内置“查询”和“重置”操作,并且按钮始终居于查询条件的最右侧 3、特定查询条件(如日期范围),可以配置占2列(span属性) 4、自动填入placeholder(日期范围除外) 5、默认输入框清空功能及下拉选择可以搜索选择功能 复制代码
三、效果图
四、参数配置
1、代码示例
<t-query-condition :opts="opts" @submit="conditionEnter" :loading="loading" /> <!-- opts:配置项 @submit:点击查询按钮 返回最终数据 loading:查询按钮loading --> 复制代码
1、配置参数(Attributes)
参数 | 说明 | 类型 | 默认值 |
---|
opts | 接收筛选器组件配置 | formOpt{} | 无 |
loading | 查询按钮 loading 状态,请求数据时需要体现 | Boolean | false |
reset | 是否显示“重置”按钮 | Boolean | true |
boolEnter | 是否敲回车查询 | Boolean | true |
labelWidth | labelWidth 宽度 | String | '100px' |
2、formOpt Attributes
参数 | 说明 | 类型 | 默认值 |
---|
label | 表单字段说明标题 | string | 无 |
comp | 组件名称,可直接指定全局注册的组件,也可引入'elmentUI'如:Button 或者'el-button' | string,component | 无 |
span | 控件占用的列宽,默认占用 1 列,最多 4 列 (独占一行) | number | 1 |
changeEvent | 默认事件为@input,可通过该属性指定其他事件(下拉选择和日期范围需要指定“change 事件”) | string | 'input' |
bind | 渲染时组件会调用 v-bind 指定设置该配置更新元素的属性(继承第三方组件属性) | object | 无 |
event | 配置组件事件,与写组件时@change 等同理 | object | 无 |
child | 子组件列表,类似 select 组件存在 options 子组件的则需要用到 | Array | 无 |
defaultVal | 默认值 | - | 无 |
3. 事件(events)
事件名 | 说明 | 回调参数 |
---|
change | 筛选器数据发生变化时触发 | Function(form: {[propName: dataIndex]: any}) |
submit | 点击筛选器查询按钮时触发 | Function(form: {[propName: dataIndex]: any}) |
reset | 点击筛选器重置按钮时触发 | Function(form: {[propName: dataIndex]: any}) |
五、具体代码
1、HTML
<template> <el-form :label-width="labelWidth" :form="form" size="small" class="t-query-condition" :style="{'grid-template-areas': gridAreas, 'grid-template-columns': `repeat(${colLength}, minmax(220px, ${100 / colLength}%))`}" > <el-form-item v-for="(opt, i) in cOpts" :key="i" :label="opt.label" :style="{gridArea: i}"> <OptComponent v-bind="opt" :opt="opt" :value="form[opt.dataIndex]" @change="change" /> </el-form-item> <el-form-item v-if="Object.keys(cOpts).length > 0" label-width="0" style="grid-area: submit_btn" :class="{'flex_end': cellLength % colLength === 0}" > <el-button type="primary" size="small" class="btn_check" @click="checkHandle" :loading="loading" >查询</el-button> <el-button v-if="reset" class="btn_reset" size="small" @click="resetHandle">重置</el-button> </el-form-item> </el-form> </template> 复制代码
2、js
<script> import OptComponent from './OptComponent' export default { name: 'TQueryCondition', components: { OptComponent }, props: { // 配置项 opts: { type: Object, default: () => ({}) }, labelWidth: { type: String, default: '100px' }, loading: { type: Boolean, default: false }, reset: { type: Boolean, default: true }, boolEnter: { type: Boolean, default: true } }, data () { return { form: Object.keys(this.opts).reduce((acc, field) => { acc[field] = this.opts[field].defaultVal || null return acc }, {}) } }, watch: { opts: { handler (opts) { this.form = this.initForm(opts, true) }, deep: true } }, computed: { cOpts () { return Object.keys(this.opts).reduce((acc, field) => { let opt = { ...this.opts[field] } opt.dataIndex = field acc[field] = opt return acc }, {}) }, gridAreas () { // grid布局按钮位置 let template = "'. . . .'" switch (this.colLength) { case 3: template = "'. . .'" break case 2: template = "'. .'" break } let areas = [template] Object.keys(this.opts).forEach(key => { // 根据控件描述注定占用多少列及顺序 let span = 1 if (this.opts[key].span > 1 || this.opts[key].span <= 2) { // 最多占用2列 span = this.opts[key].span } // 计算剩余多少未占用的位置 let count = 0 let scrstr = areas[areas.length - 1] while (scrstr.indexOf('.') !== -1) { scrstr = scrstr.replace(/\./, '') count++ } // 若剩余位置不足以放下下一个控件 if (count < span) { areas.push(template) } let i = 0 while (i < span) { areas[areas.length - 1] = areas[areas.length - 1].replace(/\./, key) i++ } }) // 若控件正好占满一行时,补充多一列放置btn if (areas[areas.length - 1].indexOf('.') === -1) { areas.push(template) } if (this.cellLength % this.colLength === 0) { // 正好占满一行 areas[areas.length - 1] = areas[areas.length - 1].replace(/\.'$/, "submit_btn'") } else { areas[areas.length - 1] = areas[areas.length - 1].replace(/\./, 'submit_btn') } return (areas + '').replace(/,/g, '') }, colLength () { // 行列数 const width = window.innerWidth let colLength = 4 if (width > 768 && width < 1280) { colLength = 3 } else if (width <= 768) { colLength = 2 } return colLength }, // 占用单元格长度 span () { let span = 1 Object.keys(this.opts).forEach(key => { span = this.opts[key].span > 4 ? 4 : this.opts[key].span || 1 }) return span }, cellLength () { // 占用单元格长度 let length = 0 Object.keys(this.opts).forEach(key => { let span = this.opts[key].span > 4 ? 4 : this.opts[key].span || 1 length += span }) return length } }, methods: { initForm (opts, keepVal = false) { return Object.keys(opts).reduce((acc, field) => { if (keepVal && this.form) { acc[field] = this.form[field] } else if (opts[field].defaultVal) { acc[field] = opts[field].defaultVal } else { acc[field] = null } return acc }, {}) }, resetHandle () { this.form = this.initForm(this.opts) this.checkHandle() }, change (v, dataIndex) { this.form[dataIndex] = v this.$emit('change', { ...this.form }) }, checkHandle () { this.$emit('submit', this.form) } }, created () { if (this.boolEnter) { var lett = this document.onkeydown = function (e) { var key = window.event.keyCode if (key === 13) { lett.checkHandle() } } } } } </script> 复制代码
3、SCSS
<style> .t-query-condition.el-form { position: relative; display: grid; gap: 2px 8px; margin-bottom: -7px; text-align: left; .el-select, .el-date-editor, .ant-calendar-picker { width: 100%; } .flex_end { grid-area: submit_btn; .el-form-item__content { display: flex; justify-content: flex-end; align-items: center; } } .el-form-item { display: flex; margin-bottom: 6px; .el-form-item__label { flex-shrink: 0; min-width: 60px; padding-left: 8px; } .el-form-item__content { flex-grow: 1; overflow: hidden; margin-left: 0 !important; } } .btn_check { position: relative; top: -1px; } .btn_reset { position: relative; top: -1px; margin-left: 8px; } } </style> 复制代码
4、OptComponent组件
<template> <component :is="comp" v-bind="{clearable:true,filterable:true,...bind}" :placeholder="getPlaceholder(opt)" v-on="cEvent" :value="value" > <OptComponent v-for="(cOpt, i) in child" :key="i" v-bind="cOpt" :value="cOpt.value" /> </component> </template> <script> export default { name: 'OptComponent', props: { dataIndex: { type: String, default: '' }, opt: { type: Object, default: () => ({}) }, bind: { type: Object, default: () => ({}) }, event: { type: Object, default: () => ({}) }, comp: { type: [String, Object], default: '' }, child: { type: Array, default: () => ([]) }, value: { type: [String, Number, Array, Date], default: '' }, changeEvent: { type: String, default: 'input' } }, computed: { cEvent () { let event = { ...this.event } let changeEvent = {} if (this.changeEvent) { changeEvent[this.changeEvent] = (v) => { event[this.changeEvent] && event[this.changeEvent](v, arguments) this.$emit('change', v, this.dataIndex, arguments) } } return { ...event, ...changeEvent } } }, methods: { // 得到placeholder的显示 getPlaceholder (row) { // console.log(111, row) let placeholder if (row.comp) { if (row.comp.includes('input')) { placeholder = '请输入' + row.label } else if (row.comp.includes('select') || row.comp.includes('date')) { placeholder = '请选择' + row.label } else { placeholder = row.label } } return placeholder } } } </script>
作者:wocwin
链接:https://juejin.cn/post/7047771278542913572