vue: 動態表單實現(單問題線)

vue: 動態表單實現(單問題線)

這個是工作中的一個需求,當然需求比較淺,適當的擴展實現了一下,寫個博客記錄一下當下的邏輯思想

需求描述
需求如上圖,簡單來說就是一個select選擇不同的答案,後面可以會多出不同的題目。然後就是問題塊的概念,一堆問題,這些問題同時間出現且沒有先後順序,則爲一個問題塊,一個問題塊中的一個select的選擇,可能觸發另一個問題塊的出現。

需求需要解決的點:

  1. 邏輯結構不定,爲後端提供
  2. 層級不定,由邏輯結構決定
  3. 填到最下面的葉子結點後,獲取全部用戶所填有效的問題結果
  4. 有創建和編輯功能,利用答案,反鋪該答案所對應的表單

解決思路:
創建3個組件:

  • index組件:該表單的入口
  • level組件:一個問題塊
  • end組件:一個表單的最後,也就是提交步驟

鋪頁面:
在頁面中向後端請求獲取邏輯結構,將邏輯結構放入index組件,index組件拿到邏輯結構交給level組件,有level組件進行鋪第一個根問題塊,若問題選擇,激活了另一個問題塊,則level組件引用本身,循環該問題塊,直至遇到‘end’標識符位置,展示提交按鈕。

獲取答案:
點擊end組件的提交按鈕,初始化ans的答案空對象,向該組件的父組件進行傳值,傳值包括ans答案及該組件的邏輯結構,父組件拿到值,將自己的答案添加到ans對象中去,並將自組件傳來的邏輯結構放到自己邏輯結構中的child字段中去,再將ans對象和自己更新完的邏輯結構傳向自己的父組件,反覆操作,直到達到index組件,整理答案,傳值頁面,有頁面展示和向後臺傳遞ans對象。而更新完的邏輯結構也需交給後臺保存,以供前臺之後修改時恢復表單。
在這裏插入圖片描述
邏輯結構樣例:

         limitRule: [{
                type: 'select',
                name: 'wr1,
                label: '問題塊根-問題1',
                ans: null,
                child: null,
                content: [{
                    value: 'wr11',
                    label: ''問題塊根-問題1-答案1',
                    children: [{
                        type: 'select',
                        name: 'w11',
                        label: '問題塊1-問題1',
                        ans: null,
                        child: null,
                        content: [{
                            value: 'w11',
                            label: '問題塊1-問題1-答案1',
                            children: [{
                                type: 'end'
                            }],
                        }, {
                            value: 'w11',
                            label: '問題塊1-問題1-答案2',
                            children: [{
                                type: 'select',
                                name: 'wa1',
                                label: '問題塊a-問題1',
                                ans: null,
                                child: null,
                                content: [{
                                    value: 'wa11',
                                    label: '問題塊a-問題1-答案1',
                                    children: null,
                                },{
                                    value: 'wa11',
                                    label: '問題塊a-問題1-答案2',
                                    children: null,
                                }]
                            }, {
                                type: 'select',
                                name: 'wa2',
                                label: '問題塊a-問題2',
                                ans: null,
                                child: null,
                                content: [{
                                    value: '問題塊a-問題2-答案1',
                                    label: 'wa21',
                                    children: null,
                                },{
                                    value: '問題塊a-問題2-答案1',
                                    label: 'wa22',
                                    children: null,
                                }]
                            }, {
                                type: 'end'
                            }],
                        }]
                    }]
                }, {
                    value: 'wr12',
                    label: '問題塊根-問題1-答案2',
                    children: [{
                        type: 'select',
                        name: 'wb1',
                        label: '問題塊b-問題1',
                        ans: null,
                        child: null,
                        content: [{
                            value: 'wb11',
                            label: '問題塊b-問題1-答案1',
                            children: [{
                                type: 'textarea',
                                name: 'wb111',
                                label: '問題塊b1-問題1',
                                ans: null,
                                child: null,
                            }, {
                                type: 'end'
                            }],
                        }, {
                            value: 'wb12',
                            label: '問題塊b-問題1-答案2',
                            children: [{
                                type: 'daterange',
                                label: '問題塊b2-問題1',
                                name: 'wb121',
                                ans: null,
                                child: null,
                            }],
                        }, {
                            value: 'wb13',
                            label: '問題塊b-問題1-答案3',
                            children: [{
                                type: 'end'
                            }],
                        }]
                    }]
                }]
            }],

邏輯結構說明:

對象屬性 描述
type 問題類型
label 頁面展示文字
value 出現在select中,選項對應的值
name 之後ans對象中該答案所對應的鍵
ans 之後ans對象中該答案所對應的值
children 若選擇該答案是所需要出現的問題塊
child 該問題當前的子問題塊

大概的邏輯思想就是這樣了,在這之後還又一個單層級就可以實現該需求的邏輯方向,兩個邏輯方向不同之處(所謂的多層級和單層級是指該邏輯結構是多維還是一維):

多層級 單層級
邏輯順序可從結構中得到 邏輯順序需要明確標示
只能拿到一個從葉子結點到根的問題線上的答案 多問題線也可獲取
答案是從下往上獲取 答案獲取無邏輯

局部代碼展示:

// index組件:
<template>
    <el-form inline :form="rule" :label-width="'150px'" label-position="right" >
        <limitLevel v-if="rule" :rule="rule" :moIndex="mo_index" @submitAns="submitAns"></limitLevel>
    </el-form>
</template>
<script>
import limitLevel from './LimitLevel';
export default {
    components: {
        limitLevel
    },
    data() {
        return {
            mo_index: 0,
            
        }
    },
    props: {
        rule: {
            type: Array,
            required: true,
            default: null
        },
        moIndex: {
            type: Number,
            required: true,
            default: null
        }
    },
    methods: {
        submitAns(data, label, all_data, index) {
            let ans = null;
            if (data) {
                ans = deepClone(data);
            }
            console.log(this.rule);
            this.$emit('submitAns', data, label, all_data, this.moIndex);
        }
    }
}
</script>

// level組件:
<template>
    <div v-if="data">
        <template v-for="(item, index) in data">
            // 通過type不同選擇不同的代碼。。。不全部展示
            <div v-if="item.type == 'end'" :key="item.label">
                <limitEnd :rule="item" @submitAns="submitAns"></limitEnd>
            </div>
            <div v-if="item.type == 'select'" :key="item.label">
                <el-form-item
                :label="item.label ? item.label: ''"
                :rules="[
                    { required: true, message: '請選擇', trigger: 'change' }
                ]">
                    <el-select v-model="item.ans" placeholder="請選擇" @change="changeSelect(index)">
                        <el-option v-for="(option, option_index) in item.content" :key="option.label" :label="option.label" :value="option_index"></el-option>
                    </el-select>
                </el-form-item>
                <template v-if="item.child">
                    <limitLevel :rule="item.child" :moIndex="item.index" @submitAns="submitAns"></limitLevel>
                </template>
            </div>
        </template>
    </div>
</template>
<script>
import limitEnd from './LimitEnd'
export default {
    name: 'limitLevel',
    components: {
        limitEnd
    },
    data() {
        return {
            data: null,
            value_index: null,
            value: null,
            child: null,
        }
    },
    props: {
        rule: {
            type: Array,
            required: true,
            default: null
        },
        moIndex: {
            type: Number,
            required: true,
            default: null
        },
    },
    created() {
        this.data = deepClone(this.rule);
    },
    methods: {
        changeSelect(index) {
            this.$set(this.data[index], 'child', null);

            this.$nextTick(() => {
                let ans = this.data[index].ans;
                let option = this.data[index].content[ans];
                this.$set(this.data[index], 'child', option.children);
                this.$set(this.data[index], 'index', index);
            })
        },
        judgeAnsIsNull(item, label) {
            if (!item && item != 0) {
                this.$message({
                    type: 'error',
                    message: '請填寫' + label
                })

                return false;
            }
            return true;
        },
        submitAns(data, des, all_data, mo_index) {
            let ans = null;
            let label = '';
            
            if (mo_index || mo_index == 0) {
                this.$set(this.data[mo_index], 'child', all_data);
            }
            
            if (data) {
                ans = deepClone(data);
            } else {
                ans = {};
            }

            for(let i = 0; i < this.data.length; i++) {
                // 沒有答案,提醒填寫
                if (this.data[i].type == 'end') {
                    continue;
                }

                switch(this.data[i].type) {
                    case 'end':
                        break;
                        
                    case 'select': 
                        this.judgeAnsIsNull(this.data[i].ans, this.data[i].label);
                        let index = this.data[i].ans;
                        let option = this.data[i].content[index];
                        ans[this.data[i].name] = option.value;
                        label += ' ' + option.label;
                        break;
                        
                    default: 
                        break;
                }

            }

            this.$emit('submitAns', ans, label + ' ' +des, this.data, this.moIndex);
            
        }
    }
}
// end組件
<template>
    <div>
        <el-form-item>
            <div style="text-align:center; width: 750px;">
                <el-button size="small" type="primary" style="margin-right: 50px;" @click="submitAns">確定</el-button>
                <el-button size="small">取消</el-button>
            </div>
        </el-form-item>
    </div>
</template>
<script>

export default {
    name: 'limitEnd',
    data() {
        return {}
    },
    props: {
        rule: {
            type: Object,
            required: true,
            default: null
        }
    },
    methods: {
        submitAns() {
            let ans = null;
            let label = '';
            this.$emit('submitAns', ans, label, null, null);
        }
    }
}

最後:最終這種的解決方案並不滿意,若有其他思路,請指教,謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章