父组件如何使用 代码中注释很多, 应该很容易理解
<template>
<div>
<wgySearch
v-model="searchDefault"
:fields="searchFields"
@reset="reset"
@submit="submit"
>
<!-- 通过 slot 自定义的组件 传啥都行 -->
<template slot="delivery">
<el-switch v-model="searchDefault.delivery" />
</template>
</wgySearch>
</div>
</template>
<script>
import wgySearch from './wgySearch';
// 这两个组件是给search组件传递的
import wgySelect from './wgySelect';
import wgyDatePicker from './wgyDatePicker';
data 中
// 搜索栏中的默认值
searchDefault: {
name: '',
region: '',
delivery: '',
date: '',
},
// 配置项
searchFields: [
{
label: '活动名称',
model: 'name',
},
{
label: '活动区域',
model: 'region',
template: {
// tpl传一个wgySelect组件,专门用来只用来传数据
tpl: wgySelect,
attrs: {
multiple: true,
list: [
{ label: 'A', value: 1 },
{ label: 'B', value: 2 },
],
},
},
},
{
label: '即时配送',
slot: 'delivery', // 这里配置项是slot, 模板中才能使用slot对应的字段
},
{
label: '活动时间',
model: 'date',
template: {
tpl: wgyDatePicker,
attrs: {
type: 'daterange',
'value-format': 'timestamp',
},
},
},
],
reset和submit这两方法是搜索和重置的时候调用的
</script>
wgySelect组件
<template>
<el-select
v-model="value1"
v-bind="$attrs"
@input="input"
>
<el-option
v-for="item in list"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<script>
export default {
name: 'WgySelect',
props: {
value: {
type: [Number, String, Array],
required: true,
},
list: {
type: Array,
required: true,
},
},
data() {
return {
value1: this.value,
};
},
methods: {
input(val) {
this.$emit('input', val);
},
},
};
</script>
wgyDatePicker组件
<template>
<el-date-picker
v-model="value1"
range-separator="至"
start-placeholder="开始日期"
v-bind="$attrs"
end-placeholder="结束日期"
@input="input"
/>
</template>
<script>
export default {
name: 'WgyDatepicker',
props: {
value: {
type: [Array, Number, String],
default: () => '',
},
},
data() {
return {
value1: this.value,
};
},
methods: {
input(val) {
this.$emit('input', val || '');
},
},
};
</script>
wgySearch 这个就是最主要的搜索组件了
<template>
<div>
<!-- 如果插槽 operatorLeft 或 operatorRight 存在则展示顶部区域 -->
<div
v-if="$slots.operatorLeft || $slots.operatorRight"
class="slot-operator clearfix"
>
<div class="pull-left">
<slot name="operatorLeft"></slot><!-- 表单的左上方区域 -->
</div>
<div class="pull-right">
<slot name="operatorRight"></slot><!-- 表单的右上方区域 -->
</div>
</div>
<div class="form-ctn">
<el-form
:ref="formAttrs.ref || 'form'"
:model="formData"
:inline="true"
:label-width="getLabelWidth"
label-position="right"
v-bind="formAttrs"
size="small"
@submit.native.prevent="submit"
@reset.native.prevent="reset"
>
<div :style="getWrapperStyle">
<el-form-item
v-for="(field, index) in innerFields"
:key="`${field.model || field.slot}${index}`"
:prop="field.prop"
:label="field.label"
:rules="field.rules"
:style="{display: (index + 1) > maxShowNum && !showAll ? 'none' : 'inline-block'}"
>
<!-- 如果有telplate可能是复杂类型,比如日期,下拉框等 -->
<wgy-template
v-if="field.template"
v-model="formData[field.model]"
:tpl="field.template.tpl"
:style="{width: `${getTempWidth(field.template.attrs)}px`}"
:attrs="field.template.attrs"
v-on="field.template.events"
/>
<!-- 没有telplate的话,就是输入框input -->
<!-- -->
<template v-else>
<!--
如果input是个插槽,就展示插槽,否则展示自己的input
绑定当前配置的model
透传itemAttrs
-->
<slot
v-if="field.slot"
:name="field.slot"
></slot>
<el-input
v-else
v-model.trim="formData[field.model]"
:placeholder="field.placeholder"
clearable
class="w180"
v-bind="field.itemAttrs"
/>
</template>
</el-form-item>
<slot name="suffixCustomitems"></slot>
<div
:class="btnClass"
:style="getBtnStyle"
>
<el-button
v-if="fields.length > maxShowNum"
type="text"
@click="showAll = !showAll"
>
<i :class="showAll ? 'el-icon-caret-top' : 'el-icon-caret-bottom'"></i>
{{ showAll ? '收起' : '展开' }}
</el-button>
<el-button
v-if="showSubmit"
type="primary"
native-type="submit"
class="search-btn"
>
{{ leftButton }}
</el-button>
<el-button
v-if="showReset"
native-type="reset"
class="reset-btn"
>
{{ rightButton }}
</el-button>
<span class="ml10">
<slot name="searchbtn"></slot>
</span>
</div>
</div>
</el-form>
</div>
<div
v-if="withDivider"
class="divider"
></div>
</div>
</template>
<script>
import wgyTemplate from './wgyTemplate';
export default {
components: {
wgyTemplate,
},
props: {
// 绑定的值
value: {
type: Object,
required: true,
},
leftButton: {
type: String,
default: () => '查询',
},
rightButton: {
type: String,
default: () => '重置',
},
// 每行元素个数
lineNumber: {
type: Number,
default: () => 4,
},
// label 的宽度
labelWidth: {
type: String,
default: () => '100px',
},
// 透传给el-form的属性配置
formAttrs: {
type: Object,
default: () => ({}),
required: false,
},
// 配置字段
fields: {
type: Array,
required: true,
},
// 是否显示搜索按钮
showSubmit: {
type: Boolean,
default: true,
},
// 是否显示重置按钮
showReset: {
type: Boolean,
default: true,
},
// 最大默认显示搜索项个数 ,超过这个数字后会收起
maxShowNum: {
type: Number,
default: 8,
},
// 按钮组class
btnClass: {
type: String,
default: '',
},
// 自动包含下方的分割线(列表页常用)
withDivider: {
type: Boolean,
default: true,
},
},
data() {
return {
// 绑定的值
formData: this.value,
// 配置字段 < 默认显示搜索项个数
showAll: this.fields.length < this.maxShowNum,
};
},
computed: {
// 处理placeholder
innerFields() {
return this.fields.map((field) => {
const { template, label } = field;
// 根据是否有template属性,生成默认的placeholder
const defaultPlaceholder = template ? `请选择${label}` : `请输入${label}`;
if (template) {
// 如果有template属性,但没有attrs属性,初始化attrs为一个空对象
template.attrs = template.attrs || {};
// 如果attrs对象没有placeholder属性,使用默认的placeholder
template.attrs.placeholder = template.attrs.placeholder || defaultPlaceholder;
} else {
// 如果没有template属性,且没有placeholder属性,使用默认的placeholder
field.placeholder = field.placeholder || defaultPlaceholder;
}
return field;
});
},
getWrapperStyle() {
// 动态改变该元素的布局
return {
display: 'flex',
flexWrap: 'wrap',
};
},
getIsSameLine() {
// 判断表单的字段数量是否小于等于每行的元素个数并且字段数量不能被每行的元素个数整除
const { fields, lineNumber } = this;
return fields.length <= lineNumber && !(fields.length % lineNumber === 0);
},
// 按钮的样式
getBtnStyle() {
return {
textAlign: this.getIsSameLine ? 'left' : 'right',
flex: 1,
minWidth: '220px',
marginBottom: '10px',
};
},
/**
* 计算表单标签的宽度
* 如果用户已经设置了 labelWidth,则直接返回用户设置的值
* 否则,根据表单字段的标签长度动态计算标签宽度
*/
getLabelWidth() {
if (this.labelWidth) {
return this.labelWidth;
}
// 获取表单字段标签的最大长度
const maxLength = Math.max(...this.fields.map((item) => item.label.length));
if (maxLength <= 4) {
return '80px';
}
if (maxLength > 4 && maxLength <= 6) {
return '90px';
}
if (maxLength > 6 && maxLength < 10) {
return '120px';
}
return '100px';
},
validate() {
return this.$refs.form.validate;
},
},
watch: {
// 外层变化实时通知内部, 内部变化实时通知外部
value: {
deep: true,
handler(newVal) {
this.formData = newVal;
},
},
formData: {
deep: true,
handler(newVal) {
this.$emit('input', newVal);
},
},
},
methods: {
// 搜索
submit() {
this.$emit('submit');
},
// 重置
reset() {
this.$emit('reset');
},
/*
获取每个输入框的宽度
如果attrs中有传入, 使用传入的宽度
如果是日期选择器,宽度是210
如果是日期时间选择器,宽度是340
其他的宽度都是180
*/
getTempWidth(attrs = {}) {
const { width, type } = attrs;
if (width) {
return width;
}
if (type === 'daterange') {
return 210;
}
if (type === 'datetimerange') {
return 340;
}
return 180;
},
},
};
</script>
<style lang="scss">
.search-box {
background: #fff;
.slot-operator {
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-bottom: 10px;
}
.form-ctn {
position: relative;
.search-btn,
.reset-btn {
min-width: 0;
}
.reset-btn {
margin-left: 8px;
}
}
.el-form-item {
margin-bottom: 16px;
white-space: nowrap;
}
.el-form-item--small .el-form-item__label {
padding-right: 5px;
}
.el-form-item--small .el-form-item__content {
min-width: 180px;
.el-date-editor--daterange .el-range-input {
min-width: 70px;
}
}
}
.divider {
box-sizing: border-box;
background-color: #fafafb;
height: 10px;
border-top: 1px solid #eeeeee;
border-bottom: 1px solid #eeeeee;
}
</style>
wgyTemplate 是wgySearch组件中的子组件
<template>
<component
:is="dynamicComponent"
:value="value"
v-bind="attrs"
v-on="listeners"
/>
</template>
<script>
export default {
name: 'WgyTemplate',
props: {
tpl: {
type: [String, Object],
required: true,
},
value: {
type: [Number, String, Object, Array],
required: true,
},
attrs: {
type: Object,
default: () => ({}),
},
},
computed: {
// 要渲染的组件
dynamicComponent() {
return this.tpl;
},
// 收集所有需要绑定到动态组件上的事件监听器
listeners() {
return {
input: this.input,
...this.$listeners,
};
},
},
methods: {
input(value) {
this.$emit('input', value);
},
},
};
</script>