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])">
                                    <!-- 內(nèi)部插槽數(shù)組 -->
                                    <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])">
                                <!-- 內(nèi)部插槽數(shù)組 -->
                                <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: 內(nèi)部插槽【from-item】中的
                slotList:[{name:'原生插槽字段'}]
                group:[ {prop: 表單字段名稱身堡,placeholder: 提示語邓尤, disabled: 禁用 , separator:  間隔符 }]   
            */
            formConfs:{
                type:Array,
                default:()=>([])
            },
            value:Object,
            //設置某表單數(shù)據(jù)
            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  // 完成緩存加載后 調(diào)用事件函數(shù)
        },
        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]) // 都寫入數(shù)據(jù)才能
                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>
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汞扎,隨后出現(xiàn)的幾起案子季稳,更是在濱河造成了極大的恐慌,老刑警劉巖澈魄,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件景鼠,死亡現(xiàn)場離奇詭異,居然都是意外死亡痹扇,警方通過查閱死者的電腦和手機铛漓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲫构,“玉大人浓恶,你說我怎么就攤上這事〗岜浚” “怎么了包晰?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長炕吸。 經(jīng)常有香客問我伐憾,道長,這世上最難降的妖魔是什么赫模? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任树肃,我火速辦了婚禮,結(jié)果婚禮上嘴瓤,老公的妹妹穿的比我還像新娘扫外。我一直安慰自己,他們只是感情好廓脆,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布筛谚。 她就那樣靜靜地躺著,像睡著了一般停忿。 火紅的嫁衣襯著肌膚如雪驾讲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天席赂,我揣著相機與錄音吮铭,去河邊找鬼。 笑死颅停,一個胖子當著我的面吹牛谓晌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播癞揉,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼纸肉,長吁一口氣:“原來是場噩夢啊……” “哼溺欧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柏肪,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤姐刁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后烦味,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聂使,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年谬俄,在試婚紗的時候發(fā)現(xiàn)自己被綠了柏靶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡凤瘦,死狀恐怖宿礁,靈堂內(nèi)的尸體忽然破棺而出案铺,到底是詐尸還是另有隱情蔬芥,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布控汉,位于F島的核電站笔诵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏姑子。R本人自食惡果不足惜乎婿,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望街佑。 院中可真熱鬧谢翎,春花似錦、人聲如沸沐旨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磁携。三九已至褒侧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谊迄,已是汗流浹背闷供。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留统诺,地道東北人歪脏。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像粮呢,于是被迫代替她去往敵國和親婿失。 傳聞我的和親對象是個殘疾皇子怠硼,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容