基于VUE3公共组件封装之--表单

我们在开发一些后台管理系统时,总是会写很多的列表查询页面,如果不封装组件,就会无限的复制粘贴,而且页面很冗余,正常情况下,我们都是要把组件进行二次封装,来达到我们想要效果。这里我分享一下我近期封装的关于列表的组件封装。 vue3+element-plus

表单组件

src\components\TableSeach

ts 复制代码
<template>
    <div class="table-search-root">
        <el-form
            ref="searchForm"
            class="table-search__form"
            :model="formData"
            size="default"
            label-width="128px"
            v-bind="formProps"
        >
            <!-- 查询表单模块 -->
            <el-row class="table-search__row">
                <el-col
                    v-for="(field, index) in fields"
                    :key="field.prop"
                    class="table-search__col"
                    :class="{ 'form__item--hidden': shouldCollapse(index) }"
                    :xl="6"
                    :lg="8"
                    :md="12"
                    :sm="24"
                >
                    <el-form-item :label="field.label" :prop="field.prop">
                        <slot v-if="$slots[field.prop]" :name="field.prop"></slot>

                        <component
                            v-else
                            :is="field.type"
                            v-model.trim="formData[field.prop]"
                            :placeholder="defaultPlaceHolder(field)"
                            v-bind="getFormFieldProps(field.type, field.props)"
                        >
                            <template v-if="field.type === FieldsType.SELECT">
                                <el-option
                                    v-for="option in field.options"
                                    :key="option.value"
                                    :label="option.label"
                                    :value="option.value"
                                />
                            </template>
                        </component>
                    </el-form-item>
                </el-col>

                <!-- 操作按钮模块 -->
                <div class="table-search__btns">
                    <el-button type="primary" :loading="props.loading" @click="handleSearch">
                        查询
                    </el-button>
                    <el-button :loading="props.loading" @click="handleReset"> 重置 </el-button>
                    <span
                        v-if="showCollapseBtn"
                        class="table-search__btn--filter ml-28"
                        @click="toggleCollapse"
                    >
                        {{ isCollapse ? '展开' : '收起' }}
                        <el-icon color="#c0c4cc" v-show="isCollapse"><CaretBottom /></el-icon>
                        <el-icon color="#c0c4cc" v-show="!isCollapse"><CaretTop /></el-icon>
                    </span>
                </div>
            </el-row>
        </el-form>
    </div>
</template>
<script lang="ts">
// 解决组件不渲染问题---使用的是自动引入方式,不知为何有bug,暂时只能用这种方式解决,有好办法的也可以分享一下 
import {
    ElInput,
    ElInputNumber,
    ElSelect,
    ElTimePicker,
    ElTimeSelect,
    ElDatePicker
} from 'element-plus';
export default {
    components: {
        ElInput,
        ElInputNumber,
        ElSelect,
        ElTimePicker,
        ElDatePicker,
        ElTimeSelect
    }
};
</script>
<script setup lang="ts">
import { type PropType, ref, toRefs, computed } from 'vue';
import defaultFieldsProps from './defaultFieldsProps';
const FieldsType = {
    INPUT: 'el-input',
    SELECT: 'el-select'
};
type filed = {
    prop: string;
    label?: string;
    type?: string;
    options?: any[];
    [propName: string]: any;
};
interface size {
    width: number;
    quantity: number;
}
// 不同尺寸所对应的页面宽度和每行 ElFormItem 个数
const DifferentSizeData = [
    { width: 1900, quantity: 4 }, // xl
    { width: 1200, quantity: 3 }, // large
    { width: 992, quantity: 2 }, // middle
    { width: 768, quantity: 1 }, // small
    { width: 0, quantity: 1 } // less than small
];
const props = defineProps({
    modelValue: {
        type: Object,
        default: () => ({})
    },
    formProps: {
        type: Object,
        default: () => ({})
    },
    fields: {
        type: Array as PropType<filed[]>,
        default: () => []
    },
    defaultCollapse: {
        type: Boolean,
        default: false
    },
    loading: {
        type: Boolean,
        default: false
    }
});
const formData: { [key: string]: any } = defineModel();
const emit = defineEmits(['search', 'reset']);
const { defaultCollapse, fields, formProps } = toRefs(props);
const isCollapse = ref(true);
isCollapse.value = defaultCollapse.value;
const searchForm = ref<any>(null);
const showCollapseBtn = computed(() => {
    const quantity = getPerLineItemQuantity();
    return fields.value.length >= quantity;
});
const getFormFieldProps = (fieldType: any, props: any) => {
    const defaultProps: { [key: string]: any } = defaultFieldsProps;
    return { ...defaultProps[fieldType], ...props };
};
const shouldCollapse = (index: any) => {
    const quantity = getPerLineItemQuantity();
    return index > quantity - 2 && isCollapse.value;
};
const getPerLineItemQuantity = () => {
    const documentScrollWidth = document.documentElement.scrollWidth;
    const size = DifferentSizeData.find((item) => documentScrollWidth >= item.width);

    return (size as size).quantity;
};
const defaultPlaceHolder = (field: any) => {
    const newLabel = field.label.replace(':', '').replace(':', '');
    return field.type === FieldsType.SELECT ? `请选择${newLabel}` : `请输入${newLabel}`;
};
const toggleCollapse = () => {
    isCollapse.value = !isCollapse.value;
};
const handleSearch = () => {
    emit('search', formData.value);
};
const handleReset = () => {
    searchForm.value.resetFields();
    emit('reset', formData.value);
};
const handleResetForm = () => {
    searchForm.value.resetFields();
};
defineExpose({
    handleResetForm
});
</script>
<style lang="scss" scoped>
/**  查询表单模块样式  **/
.table-search__form {
    display: flex;
    flex-wrap: wrap;
}

.table-search__row {
    width: 100%;
}

.table-search__col:last-of-type {
    margin-bottom: 0;
}

:deep(.el-form-item__label) {
    width: 128px;
    white-space: nowrap;
    overflow: hidden;
    font-weight: 400;
    font-size: 14px;
    color: #282828;
}

:deep(.el-select),
:deep(.el-cascader),
:deep(.el-date-editor--daterange.el-input),
:deep(.el-date-editor--daterange.el-input__inner),
:deep(.el-date-editor--timerange.el-input),
:deep(.el-date-editor--timerange.el-input__inner),
:deep(.el-date-editor--datetimerange.el-input),
:deep(.el-date-editor--datetimerange.el-input__inner) {
    width: 100%;
}

:deep(.el-date-editor .el-range-separator) {
    width: auto;
}

.form__item--hidden {
    display: none;
}

/**  操作按钮模块样式  **/
.table-search__btns {
    margin-left: auto;
    margin-bottom: 16px;
}

.table-search__btn--filter {
    font-size: 14px;
    color: #606266;
    cursor: pointer;
}

/**  功能样式  **/
.ml-28 {
    margin-left: 28px;
}
</style>

src\components\TableSeach\defaultFieldsProps.ts

js 复制代码
export default {
  'date-picker': {
    valueFormat: 'x',
  },
  XXXXX这里写一些项目公共的想要特殊处理的参数
};

表单组件就是这样

使用效果

收起状态 展开状态

使用方式

vue 复制代码
<template>
    <TableSeach
      v-model="searchQuery"
      ref="TableSeach"
      :fields="serchFields"
      :formProps="{
          labelWidth: '120px'
      }"
      :loading="loading.tableLoading"
      @search="handleQuery"
      @reset="handleQuery"
   >
      <template #staff_ids>
          基础组件无法实现的情况下可以用插槽的方式添加其他组件用以实现
      </template>
      <template #tags>
           基础组件无法实现的情况下可以用插槽的方式添加其他组件用以实现
      </template>
  </TableSeach>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import TableSeach from '@/components/TableSeach/index.vue';

const searchQuery = ref({
    xxxxxxx:xxxxx
});
const loading = ref({
    tableLoading: false
});

onMounted(() => {
    handleQuery();
});
const handleQuery = async () => {
    xxxxxxxxxxx
};

const serchFields = [
    {
            label: 'xxxxxx',
            prop: 'time',
            type: FormType.DATE_PICKER,
            props: {
                type: 'daterange',
                startPlaceholder: '开始日期',
                endPlaceholder: '结束日期',
                disabledDate: disabledDate, 
                valueFormat: YMD
            }
        },
        {
            label: 'xxxxxxx',
            prop: 'staff_ids'
        },
        {
            label: 'xxxxx',
            prop: 'tags'
        },
        {
            label: 'xxxxxx',
            prop: 'loss',
            type: FormType.SELECT,
            options: [{ value: -1, label: '全部' }, ...dynamicList]
        },
        {
            label: 'xxxxxx',
            prop: 'del_staff_id',
            type: FormType.SELECT,
            options: staffOptions.value,
            props: {
                filterable: true,
                remote: true,
                loading: loading.value.staffLoading,
                remoteMethod: remoteStaffMethod
            }
        },
        xxxxxxxxxxxxxxxx
]
</script>

如上,除了部分参数key是需要固定以外,其余element本身自带的参数也全部可以使用props传入,特殊需求在组件不满足情况下也可以使用插槽形式更改显示

本示例只是简单写了一下,在某些情况下需要传入参数也可以将serchFields改为function接收参数

有什么问题的欢迎提出,也欢迎大佬指出不足之处

相关推荐
M_emory_11 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito14 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
Dread_lxy10 小时前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
龙猫蓝图11 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
peachSoda712 小时前
随手记:简单实现纯前端文件导出(XLSX)
前端·javascript·vue.js
Tttian62212 小时前
Vue全栈开发旅游网项目(11)-用户管理前端接口联调
前端·vue.js·django
龙猫蓝图13 小时前
vue el-date-picker 日期选择 回显后成功后无法改变的解决办法
前端·javascript·vue.js
刘志辉14 小时前
Pure Adminrelease(水滴框架配置)
vue.js
工业互联网专业14 小时前
Python毕业设计选题:基于Django+uniapp的公司订餐系统小程序
vue.js·python·小程序·django·uni-app·源码·课程设计
黄景圣15 小时前
CURD低代码程序设计
前端·vue.js·后端