条件组件vue2

0 阅读4分钟

image.png

<template>
    <el-form ref="conditionForm" :model="formData" :rules="validateRules" label-width="0px" class="addCondition-main">
        <el-button type="primary" @click="addGroup">添加条件组</el-button>

        <div class="filter-container">
            <!-- 条件组列表:遍历 formData.filterGroups(数组) -->
            <div v-for="(group, groupIndex) in formData.filterGroups" :key="groupIndex" class="filter-group">
                <!-- 条件组头部 -->
                <div class="group-header">
                    <span class="group-title">过滤条件组{{ groupIndex + 1 }}</span>
                    <el-button type="text" icon="el-icon-error" class="group-delete-btn"
                        @click="deleteGroup(groupIndex)" size="mini"></el-button>
                </div>

                <!-- 条件项区域 -->
                <div class="condition-row">
                    <!-- 条件项列表 -->
                    <div v-for="(condition, condIndex) in group.conditions" :key="condIndex" class="condition-item">
                        <!-- 字段下拉框(修复 prop 路径:基于 formData 对象) -->
                        <el-form-item :prop="`filterGroups[${groupIndex}].conditions[${condIndex}].field`"
                            :rules="validateRules.field">
                            <el-select v-model="condition.field" placeholder="請選擇字段名稱" class="field-select" size="mini"
                                @change="changeField(groupIndex, condIndex, condition.field)">
                                <el-option :label="item.tabelFieldName || item.tabelField" :value="item.tabelField"
                                    v-for="(item, index) in selectData" :key="item.tabelField"></el-option>
                            </el-select>
                        </el-form-item>

                        <!-- 操作符下拉框(prop 路径不变,因为基于 formData 的子属性) -->
                        <el-form-item :prop="`filterGroups[${groupIndex}].conditions[${condIndex}].operator`"
                            :rules="validateRules.operator">
                            <el-select v-model="condition.operator" placeholder="請選擇條件" class="operator-select"
                                size="mini" @change="handleOperatorChange(condition.operator, groupIndex, condIndex)">
                                <el-option v-for="dict in dict.type.ind_operator" :key="dict.value" :label="dict.label"
                                    :value="dict.value" />
                            </el-select>
                        </el-form-item>

                        <!-- 输入框(prop 路径不变) -->
                        <el-form-item :prop="`filterGroups[${groupIndex}].conditions[${condIndex}].value`"
                            :rules="validateRules.value">
                            <el-input v-model="condition.value" placeholder="請輸入內容" class="value-input" size="mini"
                                prefix-icon="el-icon-search" :maxlength="getMaxLength(condition.operator)"
                                :disabled="isValueDisabled(condition.operator)" :type="getType(condition.operator)"
                                v-if="condition.operator !== 'between'"></el-input>
                            <!-- 选择介于,但是不是日期 -->
                            <NumberRangeInput
                                v-if="condition.operator === 'between' && (!fieldTypeArray.includes(condition.fieldType))"
                                v-model="condition.value" input-width="200px" :clearable="false" ref="priceRangeRef">
                            </NumberRangeInput>

                            <!-- 选择日期和介于时候 -->
                            <DateRangePicker
                                v-if="condition.operator === 'between' && fieldTypeArray.includes(condition.fieldType)"
                                v-model="condition.value"></DateRangePicker>


                        </el-form-item>

                        <!-- 删除条件项按钮 -->
                        <el-button type="text" icon="el-icon-remove" @click="deleteCondition(groupIndex, condIndex)"
                            size="mini" class="condition-delete-btn" v-if="group.conditions.length > 1"></el-button>
                    </div>

                    <!-- 添加条件项按钮 -->
                    <el-button type="text" icon="el-icon-circle-plus" @click="addCondition(groupIndex)" size="mini"
                        class="add-condition-btn" v-if="group.conditions.length < 10"></el-button>

                    <!-- 条件组内逻辑符 -->
                    <el-form-item v-if="group.conditions.length > 1" :prop="`filterGroups[${groupIndex}].logic`"
                        :rules="validateRules.logic">
                        <el-select v-model="group.logic" class="group-logic-select" size="mini"
                            v-if="group.conditions.length > 1">
                            <el-option v-for="dict in dict.type.ind_groups_logic" :key="dict.value" :label="dict.label"
                                :value="dict.value" />
                        </el-select>
                    </el-form-item>
                </div>
            </div>
            <!-- 组间逻辑符(绑定到 formData.groupsLogic) -->
            <el-form-item v-if="formData.filterGroups.length > 1" prop="groupsLogic" :rules="validateRules.groupsLogic"
                class="groups-logic-select">
                <el-select v-model="formData.groupsLogic" size="mini" v-if="formData.filterGroups.length > 1">
                    <el-option v-for="dict in dict.type.ind_groups_logic" :key="dict.value" :label="dict.label"
                        :value="dict.value" />
                </el-select>
            </el-form-item>
        </div>
    </el-form>
</template>

<script>
// 1-等於--这个后面的输入内容栏位是必输入的, 限制长度20个字
// 2-不等於--这个后面的输入内容栏位是必输入的, 限制长度20个字
// 3-大於--这个后面的输入内容栏位是必输入的, 限制只能数字输入
// 4-大於等於--这个后面的输入内容栏位是必输入的, 限制只能数字输入
// 5-小於--这个后面的输入内容栏位是必输入的, 限制只能数字输入
// 6-小於等於--这个后面的输入内容栏位是必输入的, 限制只能数字输入
// 7-包含--这个后面的输入内容栏位是必输入的, 限制长度20个字
// 8-不包含--这个后面的输入内容栏位是必输入的, 限制长度20个字
// 9-爲空--这个后面的输入内容栏位是不能输入的,置灰处理
// 10-不爲空--这个后面的输入内容栏位是不能输入的,置灰处理
// 11-在範圍内--这个后面的输入内容栏位是必输入的, 限制长度20个字
// 12-不在範圍内--这个后面的输入内容栏位是必输入的, 限制长度20个字
// 13-介於--这个后面的输入内容栏位是必输入的, 限制长度20个字
import NumberRangeInput from '@/components/NumberRangeInput/NumberRangeInput.vue';
import DateRangePicker from '@/components/DateRangePicker/DateRangePicker.vue';
export default {
    dicts: ['ind_operator', 'ind_groups_logic'],
    name: 'addCondition',
    inject: ['grandchildMethods', 'parentChildMethods'], // 接收父组件提供的方法容器
    props: {
        selectData: {
            type: Array,
            default: () => []
        },
        // 可选:父组件传入初始条件组数据(用于回显)
        initFilterGroups: {
            type: Object,
            default: () => { }
        }
    },
    watch: {
        selectData: {
            handler(val) {
                if (val) {
                    console.log('selectData', val)
                }
            },
            immediate: true,
            deep: true
        },
        // 监听初始数据,回显到 formData 中
        initFilterGroups: {
            handler(val) {
                console.log("initFilterGroups===>", Object.keys(val))
                if (!this.isEmptyObjStrict(val)) {
                    console.log("initFilterGroups", val)
                    this.formData = JSON.parse(JSON.stringify(val))
                } else if (this.$route.query.formType === 'edit') {
                    this.formData = {
                        filterGroups: [],
                        groupsLogic: ''
                    }
                }
            },
            immediate: true,
            deep: true
        }
    },
    data() {
        // 修复3:自定义条件验证 - 仅当字段显示时校验必填
        const validateGroupLogic = (rule, value, callback) => {
            // 获取当前验证的prop路径(比如 filterGroups[0].logic)
            const prop = rule.field;
            // 解析groupIndex
            const groupIndex = prop.match(/filterGroups\[(\d+)\].logic/)[1];
            const group = this.formData.filterGroups[groupIndex];
            console.log("組group", group)
            // 只有当组内条件>1(字段显示)时,才校验必填
            if (group.conditions.length > 1 && !value) {
                callback(new Error('请选择關係'));
            } else {
                callback();
            }
        };

        const validateGroupsLogic = (rule, value, callback) => {
            // 只有当条件组数量>1(字段显示)时,才校验必填
            if (this.formData.filterGroups.length > 1 && !value) {
                callback(new Error('请选择關係'));
            } else {
                callback();
            }
        };
        return {
            formData: {
                filterGroups: [
                    {
                        logic: '', // 组内条件的逻辑关系(并且/或者)
                        conditions: [
                            { field: '', operator: '', value: '', fieldType: '' } // 默认条件项
                        ]
                    }
                ],
                groupsLogic: '' // 组与组之间的逻辑关系
            },
            // 表单验证规则
            validateRules: {
                field: [
                    { required: true, message: '请选择字段名称', trigger: 'change' }
                ],
                operator: [
                    { required: true, message: '请选择操作符', trigger: 'change' }
                ],
                value: [
                    {
                        validator: this.validateValue, // 动态验证value的核心函数
                        trigger: ['blur', 'change']
                    }
                ],
                logic: [
                    { validator: validateGroupLogic, trigger: 'change' } // 替换为条件验证
                ],
                groupsLogic: [
                    { validator: validateGroupsLogic, trigger: 'change' } // 替换为条件验证
                ],
            },
            fieldTypeArray: ['DATE', 'TIME', 'DATETIME', 'TIMESTAMP'] //日期類型
        }
    },
    components: { NumberRangeInput, DateRangePicker },
    created() { },
    mounted() {
        this.grandchildMethods.validateFun = this.validateFun
        this.grandchildMethods.resetForm = this.resetForm
        this.parentChildMethods.resetForm = this.resetForm
    },
    methods: {

        // 添加新的条件组(修改:操作 formData.filterGroups)
        addGroup() {
            if (this.formData.filterGroups.length == 10) {
                this.$message.warning('至多只能添加10條')
                return
            }
            this.formData.filterGroups.push({
                logic: '',
                conditions: [
                    { field: '', operator: '', value: '', fieldType: '' }
                ]
            })
            // console.log("addCondition", this.formData.filterGroups)
        },

        // 删除条件组(修改:操作 formData.filterGroups)
        deleteGroup(index) {
            // if (this.formData.filterGroups.length <= 1) {
            //     this.$message.warning('至少保留一个条件组')
            //     return
            // }
            this.formData.filterGroups.splice(index, 1)
            this.clearValidate()
        },

        // 给指定组添加条件项(修改:操作 formData.filterGroups)
        addCondition(groupIndex) {
            this.formData.filterGroups[groupIndex].conditions.push({
                field: '', operator: '', value: '', fieldType: ''
            })
            console.log("addCondition", this.formData.filterGroups)
        },

        // 删除指定组的条件项(修改:操作 formData.filterGroups)
        deleteCondition(groupIndex, condIndex) {
            this.formData.filterGroups[groupIndex].conditions.splice(condIndex, 1)
            this.clearValidate()
        },

        /**
         * 判断是否为广义空对象(无任何自有属性)
         * @param {*} obj 待判断值
         * @returns {boolean} 是否为空对象
         */
        isEmptyObjStrict(obj) {
            if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
                return false;
            }
            // 检测所有自有属性(包括Symbol、不可枚举)
            return Object.keys(obj).length === 0;
        },
        // 表单验证(返回完整的 formData)
        validateFun() {
            console.log("校驗條件")


            return new Promise((resolve, reject) => {
                if (!this.$refs.conditionForm) {
                    reject('表单实例未找到')
                    return
                }
                this.$refs.conditionForm.validate((valid, errorFields) => {
                    if (valid) {
                        // 验证通过:返回完整数据
                        if (this.formData.filterGroups.length !== 0) {
                            resolve(JSON.parse(JSON.stringify(this.formData)))
                        } else {
                            resolve()
                        }


                    } else {
                        reject(errorFields)
                    }
                })
            })
        },

        // 重置表单(重置 formData)
        resetForm() {
            this.formData = {
                filterGroups: [
                    {
                        logic: '',
                        conditions: [
                            { field: '', operator: '', value: '', fieldType: '' }
                        ]
                    }
                ],
                groupsLogic: ''
            }
            this.clearValidate()
        },

        // 清除所有验证提示
        clearValidate() {
            if (this.$refs.conditionForm) {
                this.$refs.conditionForm.clearValidate()
            }
        },

        // 单独校验某个条件项
        validateField(groupIndex, condIndex, field) {
            const prop = `filterGroups[${groupIndex}].conditions[${condIndex}].${field}`
            this.$refs.conditionForm.validateField(prop, (error) => {
                if (error) {
                    console.log(`校验失败:${error}`)
                }
            })
        },
        // 操作符变更时触发,重新校验对应value字段
        handleOperatorChange(val, groupIndex, condIndex) {
            let installVal = ['isnull', 'isnotnull']
            if (installVal.includes(val)) {
                this.formData.filterGroups[groupIndex].conditions[condIndex].value = ''
            }

            this.validateField(groupIndex, condIndex, 'value');
        },
        // 根据操作符生成动态校验规则
        validateValue(rule, value, callback) {
            console.log("value", value)
            // 1. 从prop路径解析groupIndex和condIndex
            const prop = rule.field;
            const match = prop.match(/filterGroups\[(\d+)\].conditions\[(\d+)\].value/);
            if (!match) {
                callback();
                return;
            }
            const [, groupIndex, condIndex] = match;
            const condition = this.formData.filterGroups[groupIndex].conditions[condIndex];
            const { operator } = condition;

            // 2. 定义操作符规则映射
            const numberOperators = ['gt', 'ge', 'lt', 'le']; //'大於', '大於等於', '小於', '小於等於'
            const disabledOperators = ['isnull', 'isnotnull'];

            // 3. 为空/不為空时,直接通过(输入框已置灰,无需校验)
            if (disabledOperators.includes(operator)) {
                callback();
                return;
            }

            // 4. 必输校验
            if ((!disabledOperators.includes(operator)) && (!value || value.trim() === '')) {
                callback(new Error('請輸入值'));
                return;
            }

            // 5. 数字格式校验
            if (numberOperators.includes(operator) && value && !/^-?[0-9]+(\.[0-9]+)?$/.test(value.trim())) {
                callback(new Error('僅允許輸入數字'));
                return;
            }

            // 6. 长度校验(最大50字符)
            if (value && value.length > 50) {
                callback(new Error('輸入內容不能超過50個字符'));
                return;
            }
            // 7. 所有规则通过
            callback();
        },
        // 根据操作符获取输入框最大长度  除了'為空', '不為空',其餘限制50
        getMaxLength(operator) {
            const noLimitOperators = ['isnull', 'isnotnull'];
            if (noLimitOperators.includes(operator)) return null;
            return 50;
        },
        // 判断输入框是否需要置灰 :'為空', '不為空'
        isValueDisabled(operator) {
            return ['isnull', 'isnotnull'].includes(operator);
        },
        //控制輸入框類型:'大於', '大於等於', '小於', '小於等於',
        getType(operator) {
            const numberOperators = ['gt', 'ge', 'lt', 'le',];
            if (numberOperators.includes(operator)) {
                return "number"
            } else {
                return "text"
            }
        },

        // 選擇添加拉下事件
        changeField(groupIndex, condIndex, field) {
            const selectData = this.selectData
            let obj = selectData.find(item => item.tabelField === field)
            //    console.log(obj)
            this.formData.filterGroups[groupIndex].conditions[condIndex].fieldType = obj.fieldType
            console.log(" this.formData.filterGroups", this.formData)
        },



    }
}
</script>

<style scoped lang="scss">
// 样式部分不变,保留之前的优化
.addCondition-main {
    .el-form-item {
        margin-bottom: 0;
    }
}

.filter-container {
    background: #fff;
    border-radius: 4px;
    padding: 15px 0;
    position: relative;
}

.filter-group {
    width: calc(100% - 80px);
    margin-bottom: 15px;
    padding: 10px;
    border: 1px solid #e6e6e6;
}

.group-header {
    display: flex;
    align-items: center;
    margin-bottom: 10px;
    position: relative;
}

.group-title {
    font-weight: 500;
    color: #333;
}

.group-delete-btn {
    position: absolute;
    top: -20px;
    right: -20px;
    font-size: 20px;
    padding: 0px !important;
}

.condition-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.condition-item {
    display: flex;
    align-items: center;
    gap: 5px;

    .el-form-item__error {
        position: absolute;
        top: 100%;
        left: 0;
        font-size: 12px;
        padding-top: 2px;
    }
}

.field-select {
    width: 100px;
}

.operator-select {
    width: 80px;
}

.value-input {
    width: 200px;
}

.condition-delete-btn {
    color: #999;
    font-size: 20px;
    padding: 0px !important;
}

.add-condition-btn {
    color: #409eff;
    font-size: 20px;
    padding: 0px !important;
}

.group-logic-select {
    width: 75px;

    ::v-deep .el-input__inner {
        padding-left: 5px;
        padding-right: 10px;
    }

    ::v-deep .el-input__suffix {
        right: 0 !important;
    }
}

.groups-logic-select {
    width: 80px;
    position: absolute;
    right: 0px;
    bottom: 31px;

    ::v-deep .el-form-item__error {
        z-index: 10;
        white-space: nowrap;
    }
}

::v-deep .el-form-item__error {
    z-index: 10;
    white-space: nowrap;
}

::v-deep .el-date-editor {
    width: 200px !important;
    height: 28px !important;
    line-height: 28px !important;
    padding:0 10px !important;

    // .el-input__icon {
    //     display: none !important;
    // }
    .el-range-separator {
        padding: 0 !important;
    }

    .el-range-input {
        width: 50%;
    }

    .el-input__inner {
        // height: 28px !important;
        // line-height: 28px !important;
    }
    .el-range__icon{
        line-height: 28px !important;
    }

}
</style>