2021-09-17 扩展一年前写的表单生成器

<template>
    <a-form layout="horizontal" class="ant-advanced-search-form" :labelCol="labelCol" :wrapperCol="wrapperCol" :form="formData" ref="formData">   
        <a-row>
            <a-col :span="showToggleBtn ? 22 : 24">    
                <a-row :gutter="4">
                    <a-col :key="'formDara_'+i" v-for="(item,i) in conf.slice(0,hideNumSide)" :span="item.span || 8" :class="'item_' +i">
                        <slot  v-if="item.slot" :name="item.slot"  :data="item" :index="i" :conf="conf"/>
                        <template v-else>
                            <a-form-item  :label-col="labelCol" :wrapper-col="wrapperCol" :labelAlign="item.labelAlign">
                                <!-- 输入框 -->
                                <div class="form-label" :class="[i === 0 ? 'first-label' :'']">
                                    {{item.label}}
                                </div>
                                <a-input  v-if="item.type === 'input'" v-model="formData[item.prop]"  :placeholder="item.placeholder"
                                    :disabled="item.disabled" @pressEnter="keyEnter" @change="onchange(item.type,item.prop,formData[item.prop])">
                                    <a-icon slot="prefix" v-if="item.prefix" :type="item.prefix" @click.native="keyEnter"/>
                                    <a-icon slot="suffix" v-if="item.suffix" :type="item.suffix" @click.native="keyEnter" :class="item.suffix.includes('search') ? 'suffix-point' :''"/>
                                    <div :slot="st.name" v-for="(st,sti) in item.slotList" :key="sti" >
                                        <slot :name="st.name" :item="item"/>
                                    </div> 
                                </a-input>
                                <!-- 下拉选项 -->
                                <a-select  :disabled="item.disabled" v-else-if="item.type === 'select'"  :placeholder="item.placeholder" @select="selectChange" v-model="formData[item.prop]" allowClear @change="onchange(item.type,item.prop,formData[item.prop])">
                                    <!-- 内部插槽数组 -->
                                    <div :slot="st.name" v-for="(st,sti) in item.slotList || []" :key="sti" >
                                        <slot :name="st.name" :item="item"/>
                                    </div> 
                                    <a-select-option  :value="optionsItem.value" v-for="(optionsItem,oi) in C_selectOptions[item.prop] || item.selectOptions || []" :key="'select_'+ oi" :disabled="optionsItem.disabled"
                                        @search="search">
                                        {{optionsItem.label}}
                                    </a-select-option>
                                </a-select>
                                <!-- 日期控件 -->
                                <a-range-picker :locale="locale" class="ant-form-item-children_date" :separator="item.separator || '至'" v-model="formData[item.prop]" @change="onchange('date',item.prop,formData[item.prop])" :placeholder="item.placeholder || []" v-else-if="item.type === 'date-d'" >
                                    <span slot="suffixIcon">
                                        <slot :name="item.suffixIcon">
                                            <i class="iconfont icon-common-date ant-calendar-picker-icon"/>
                                        </slot>
                                    </span>
                                </a-range-picker>
                                <a-range-picker :locale="locale" class="ant-form-item-children_date" v-model="formData[item.prop]" @change="onchange('date',item.prop,formData[item.prop])" :placeholder="item.placeholder || []" v-else-if="item.type === 'date-r'" 
                                :ranges="{'明 天':[moment().add(1,'day'),moment().add(1,'day')], '未来7天': [moment().add(1,'day'),moment().add(1,'week')],
                                '未来15天': [moment().add(1,'day'),moment().add(15,'day')],'未来30天': [moment().add(1,'day'),moment().add(30,'day')],
                                '未来60天': [moment().add(1,'day'),moment().add(60,'day')],'未来90天': [moment().add(1,'day'),moment().add(90,'day')]}" />
                                <a-date-picker  v-else-if="item.type === 'date-dd'"   :separator="item.separator || '至'"
                                        @change="onchange('date',item.prop,formData[item.prop])" :placeholder="item.placeholder" v-model="formData[ite.prop]" v-bind="item.binding" style="width:100%" :format="item.dateFormatList">
                                        <span slot="suffixIcon">
                                            <slot :name="item.suffixIcon">
                                                <i class="iconfont icon-common-date ant-calendar-picker-icon"/>
                                            </slot>
                                        </span>
                                </a-date-picker>
                                <a-input-group v-else-if="item.type === 'inputGroup'" compact :size="item.size" class="inputgroup">
                                        <template v-for="(ranger, rangeri) in item.groups">
                                                <a-input v-if="rangeri !== 0"
                                                    style=" width: 30px; border-left: 0; pointer-events: none; backgroundColor: #fff"
                                                    :placeholder="ranger.separator || '~'"
                                                    disabled
                                                    :key="'group_'+i+'_'+rangeri+'place'"
                                                />
                                                <a-input :key="'group_'+i+'_'+rangeri" v-model="formData[item.prop][rangeri]"  :placeholder="ranger.placeholder" :disabled="ranger.disabled" @pressEnter="keyEnter" @change="onchangeGroup(item)" class="inputgroup-input">
                                                    <a-icon slot="prefix" v-if="rangeri.prefix" :type="rangeri.prefix" @click.native="keyEnter"/>
                                                    <a-icon slot="suffix" v-if="rangeri.suffix" :type="rangeri.suffix" @click.native="keyEnter" :class="rangeri.suffix.includes('search') ? 'suffix-point' :''"/>
                                                    <div :slot="st.name" v-for="(st,sti) in ranger.slotList" :key="sti" >
                                                        <slot :name="st.name" :item="item"/>
                                                    </div> 
                                                </a-input>
                                        </template>
                                </a-input-group>
                                <slot :name="item.slotInner" v-else-if="item.slotInner" :data="item" :index="i" :conf="formConfs"/>
                            </a-form-item>
                        </template>
                    </a-col>
                </a-row>            
            </a-col>
            <a-col :span="2" v-if="showToggleBtn || conf.length > hideNumSide" >
                <slot name="rightSpan">
                    <a-form-item class="changeBtn">
                        <YtButton :type="changeClickIcon ? 'default':'primary'" height="36px" :color="changeClickIcon ? '#62727E':'white'"
                           border-color="#CFD9E1" @click="changeIcon">更多筛选 <a-icon :type="changeClickIcon ? 'down-circle' : 'up-circle'" theme="filled" :style="{color:changeClickIcon ? '#62727E':'white' }"/></YtButton>
                    </a-form-item>
                </slot>
            </a-col>
        </a-row>
        <!-- 第二轮卡片 -->   
        <template v-if="!changeClickIcon">
            <a-row :gutter="4" class="max-lg-3" >
                <a-col :key="'formDara_'+(i+hideNumSide)" v-for="(item,i) in conf.slice(hideNumSide)" :span="item.span || 8">
                    <slot  v-if="item.slot" :name="item.slot"  :data="item" :index="i" :conf="conf"/>
                    <template v-else>
                        <a-form-item  :label-col="labelCol" :wrapper-col="wrapperCol" :labelAlign="item.labelAlign">
                            <!-- 输入框 -->
                            <div class="form-label">
                                {{item.label}}
                            </div>
                            <a-input  v-if="item.type === 'input'" v-model="formData[item.prop]"  :placeholder="item.placeholder"
                                :disabled="item.disabled" @pressEnter="keyEnter" @change="onchange(item.type,item.prop,formData[item.prop])">
                                <a-icon slot="prefix" v-if="item.prefix" :type="item.prefix" />
                                <a-icon slot="suffix" v-if="item.suffix" :type="item.suffix" />
                                <div :slot="st.name" v-for="(st,sti) in item.slotList" :key="sti" >
                                    <slot :name="st.name" :item="item"/>
                                </div> 
                            </a-input>
                            <!-- 下拉选项 -->
                            <a-select  :disabled="item.disabled" v-else-if="item.type === 'select'"  :placeholder="item.placeholder" @select="selectChange" v-model="formData[item.prop]" allowClear @change="onchange(item.type,item.prop,formData[item.prop])">
                                <!-- 内部插槽数组 -->
                                <div :slot="st.name" v-for="(st,sti) in item.slotList || []" :key="sti" >
                                    <slot :name="st.name" :item="item"/>
                                </div> 
                                <a-select-option  :value="optionsItem.value" v-for="(optionsItem,oi) in C_selectOptions[item.prop] || item.selectOptions || []" :key="'select_'+ oi" :disabled="optionsItem.disabled"
                                    @search="search">
                                    {{optionsItem.label}}
                                </a-select-option>
                            </a-select>
                            <!-- 日期控件 -->
                            <a-range-picker :locale="locale" class="ant-form-item-children_date" v-model="formData[item.prop]" @change="onchange('date',item.prop,formData[item.prop])" :placeholder="item.placeholder || []" v-else-if="item.type === 'date-d'" />
                            <a-range-picker :locale="locale" class="ant-form-item-children_date" v-model="formData[item.prop]" @change="onchange('date',item.prop,formData[item.prop])" :placeholder="item.placeholder || []" v-else-if="item.type === 'date-r'" 
                            :ranges="{'明 天':[moment().add(1,'day'),moment().add(1,'day')], '未来7天': [moment().add(1,'day'),moment().add(1,'week')],
                            '未来15天': [moment().add(1,'day'),moment().add(15,'day')],'未来30天': [moment().add(1,'day'),moment().add(30,'day')],
                            '未来60天': [moment().add(1,'day'),moment().add(60,'day')],'未来90天': [moment().add(1,'day'),moment().add(90,'day')]}" />
                            <a-date-picker  v-else-if="item.type === 'date-dd'"  
                                    @change="onchange('date',item.prop,formData[item.prop])" :placeholder="item.placeholder" v-model="formData[ite.prop]" v-bind="item.binding" style="width:100%" :format="item.dateFormatList">
                                    <span slot="suffixIcon">
                                        <slot :name="item.suffixIcon"/>
                                    </span>
                            </a-date-picker>
                            <a-input-group v-else-if="item.type === 'inputGroup'" compact :size="item.size" class="inputgroup">
                                        <template v-for="(ranger, rangeri) in item.groups">
                                                <a-input v-if="rangeri !== 0"
                                                    style=" width: 30px; border-left: 0; pointer-events: none; backgroundColor: #fff"
                                                    :placeholder="ranger.separator || '~'"
                                                    disabled
                                                    :key="'group_'+i+'_'+rangeri+'place'"
                                                />
                                                <a-input :key="'group_'+i+'_'+rangeri" v-model="formData[item.prop][rangeri]"  :placeholder="ranger.placeholder" :disabled="ranger.disabled" @pressEnter="keyEnter" @change="onchangeGroup(item)" class="inputgroup-input">
                                                    <a-icon slot="prefix" v-if="rangeri.prefix" :type="rangeri.prefix" @click.native="keyEnter"/>
                                                    <a-icon slot="suffix" v-if="rangeri.suffix" :type="rangeri.suffix" @click.native="keyEnter" :class="rangeri.suffix.includes('search') ? 'suffix-point' :''"/>
                                                    <div :slot="st.name" v-for="(st,sti) in ranger.slotList" :key="sti" >
                                                        <slot :name="st.name" :item="item"/>
                                                    </div> 
                                                </a-input>
                                        </template>
                            </a-input-group>
                            <slot :name="item.slotInner" v-else-if="item.slotInner" :data="item" :index="i" :conf="formConfs"/>
                        </a-form-item>
                    </template>
                </a-col>
            </a-row>
            <div class="stand-height"></div>
        </template>

        <!-- 后置插槽 -->
        <slot name="action"/>
    </a-form>
</template>
<script>
/*ant 中文*/
import locale from 'ant-design-vue/es/date-picker/locale/zh_CN';
import moment from 'moment';
    export default {
        name:'YtFormSearch',// 表单收缩卡片
        props:{
            test:Boolean,
            // 表单配置选项
            /* type: t 输入框,D 返回时间,s 下拉选项 d 单个时间 inputgroup 输入框组合
                label:显示文字 , prop:对应表单的字段名称,suffix:后置图标,prefix:前置图标,disabled:禁用 
                slot:外部插槽  slotInner: 内部插槽【from-item】中的
                slotList:[{name:'原生插槽字段'}]
                group:[ {prop: 表单字段名称,placeholder: 提示语, disabled: 禁用 , separator:  间隔符 }]   
            */
            formConfs:{
                type:Array,
                default:()=>([])
            },
            value:Object,
            //设置某表单数据
            setFormValue:Function,
            changeShowNum:Number,
            hideNumSide:{ // 切换显示的边界索引
                type:Number,
                default:3
            },
            labelCol:{
                type:Object,
                default:()=>({
                    span:7
                })
            },
            wrapperCol:{
                default:()=>({
                    span:17
                })
            },
            // 异步获取下拉选项
            selectOptions:{
                type:Object,
                default:()=>({})
            },
            showToggleBtn:{type:Boolean,default:true},//显示按钮
            cache: String,  // vuex需要缓存 名称
            storageCache: String , // 本地缓存名称
            completeCache: Function  // 完成缓存加载后 调用事件函数
        },
        data(){
            return {
                mode: {
                    'd':'default',
                    'default':'default',
                    'm' : 'multiple',
                    'multiple' : 'multiple',
                    't' : 'tags',
                    'tags' : 'tags',
                    'c' : 'combobox',
                    'combobox':'combobox'
                },
                formData:{...this.value},
                locale,
                changeClickIcon:true
            }
        },
        computed:{
            conf(){
                return (this.formConfs|| []).map( v => ({
                    ...v,
                    placeholder:v.placeholder || ( (v.type.includes('input') ? '请输入' : '请选择') + v.label),
                    mode:this.mode[v.mode] || 'default'
                }))
            },
            C_selectOptions(){
                return this.selectOptions
            }
        },
        watch:{
            'value':{
                handler(cval){
                    this.value && (this.formData = JSON.parse(JSON.stringify(this.value)))
                },
                deep:true,
            }
        },
        mounted(){
            if( this.cache && this.$store.state?.YtSearchFrom?.[this.cache] ){
                Object.assign(this.formData, this.$store.state.YtSearchFrom[this.cache])
                this.$emit('input',this.formData)
                this.completeCache && this.completeCache()
            }
            if(this.storageCache && !!sessionStorage.getItem('YtSearchFrom_' + this.storageCache)){
                Object.assign(this.formData, JSON.parse(sessionStorage.getItem('YtSearchFrom_' + this.storageCache)))
                this.$emit('input',this.formData)
                this.completeCache && this.completeCache()
            }
            this.test && console.log(this.formData , this.formConfs)
        },
        methods:{
            moment,
            setFormData(prop,value = ''){
                if(this.setFormValue) return  this.setFormValue(this.formData)
                this.formData[prop] = value                
            },
            //回车查询
            keyEnter(){
                this.test && console.log('回车=》',this.formData)
                this.$emit('keyEnter',this.formData)
            },
            // 输入框值变化时
            onchange(type,propName,value){
                this.test && console.log('change=》',propName,value)
                this.onchange.duled && clearTimeout(this.onchange.duled)
                this.onchange.duled = setTimeout( _ => {
                    this.caching()  // 缓存机制
                    this.$emit('input',this.formData)
                    this.$emit('onchange',{type,propName,value})
                    this.$emit('onchange'+type,{type,propName,value})
                }, 800)

            },
            search(value){
                // 输入后值变化时
                this.$emit('search',value)
            },
            //
            selectChange(value,options){
                this.test && console.log('selectChange=》',value,this.formData)
                this.$emit('selectChange',{value,formData:this.formData})
            },
            // 清空所有查询条件
            clearAllCase(){
                Object.keys(this.formData).forEach(v => {
                      Array.isArray(this.formData[v]) ? this.formData[v] = [] : this.formData[v] = undefined
                 })
                this.$emit('input',this.formData)
                this.$emit('clear',this.formData)
            },
            //切换按钮
            changeIcon(){
                this.changeClickIcon = !this.changeClickIcon
                this.$emit('toggleBtn')
            },
            // 组合输入框事件
            onchangeGroup(item){
                const {type, prop } = item
                const isFlag = this.formData[prop].every(v => v !== '' &&  v !== undefined)
                item.onError = !isFlag
                if(isFlag) this.onchange(type, prop, this.formData[prop]) // 都写入数据才能
                this.$emit('onchangeGroup',type, prop, this.formData[prop],item)
                this.test && console.log(isFlag, this.formData[prop], 'onchangeGroup')
            },
            // 缓存机制
            caching(){
                const {cache, storageCache , $store:{ state }} = this
                if( cache ){
                    // 存在VUEX 缓存集合
                    const data = JSON.parse(JSON.stringify(this.formData))
                    if(state.YtSearchFrom && Object.prototype.toString.call(state.YtSearchFrom).slice(8, -1) === 'object' ){
                        state.YtSearchFrom[cache] = data
                    } else {
                        state.YtSearchFrom = {
                            [cache]: data
                        }
                    }
                }
                // 存入本地缓存
                if(storageCache){
                    sessionStorage.setItem('YtSearchFrom_'+ storageCache, JSON.stringify(this.formData))
                    
                }
                this.test && console.log(sessionStorage.getItem('YtSearchFrom_'+ storageCache), 'state', state.YtSearchFrom)
            }           
        }
    }
</script>
<style lang="scss" scoped>
 .ant-advanced-search-form {
     @media screen and (max-width:1528px){
      /deep/ .ant-col-8{
             width:32.22%;
         }
         
     }
     box-sizing: border-box;
    /deep/ .ant-form-item {
        display:flex;
        align-items:center;
        justify-content: center;
        margin-bottom:16px;
        .ant-form-item-label{
            label{
                color:#8595A1;
            }
        }
        .suffix-point{
            cursor: pointer;
        }
    }
    /*站位高度*/
    .stand-height{
        height:16px;
    }
    // 全局表单样式
   /deep/ .ant-form-item-control-wrapper {
        flex: 1;
        .ant-form-item-control{
            padding-right:8px;
            flex:1;
            .ant-calendar-picker{
                width:100%;
            }
            .ant-form-item-children{
                display: flex;
                .form-label{
                    max-width: 100px;
                    min-width:56px;
                    display: flex;
                    justify-content: flex-end;
                    align-items: center;
                    height: 38px;
                    line-height: 18px;
                    text-align: right;
                    padding-right:8px;
                    box-sizing: border-box;
                    & + div,& + span{
                        flex:1;
                    }
                }
                .item_1{
                    max-width:372px;
                }
                // 第一个输入框的label
                .first-label{
                    width:0;
                    padding:0;
                    min-width:0;
                }
                .inputgroup {
                    display:flex;
                    flex-wrap: nowrap;
                    .inputgroup-input{
                        &:nth-child(1+n){
                            border-left:none;
                        }
                    }
                }
            }
        }
    }
    .changeBtn /deep/ .ant-form-item-control-wrapper .ant-form-item-control{
        padding-right:0px;
        .ant-form-item-children{
            display: flex;
            justify-content: flex-end;
            align-items: center;
        }
    }
    /*特殊大于三的表单样式*/
    .max-lg-3{
        background-color:#f9fafb;
        padding:16px 16px 0 16px;
        margin:0px!important;
        /deep/ .ant-form-item-control-wrapper {
            .ant-form-item-control{
                 .ant-form-item-children{
                     .form-label{
                         width:100px;
                     }
                 }
            }
        }

    }
}
</style>
<style lang="scss">
    .ant-calendar-picker-container{
        .ant-calendar-footer-extra{
            .ant-tag-blue{
                width: 81px;
                text-align: center;
            }
            .ant-tag-blue:last-child{
                margin: 0;
            }
        }
    }
</style>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容