vue: 動態表單實現(單問題線)
這個是工作中的一個需求,當然需求比較淺,適當的擴展實現了一下,寫個博客記錄一下當下的邏輯思想
需求如上圖,簡單來說就是一個select選擇不同的答案,後面可以會多出不同的題目。然後就是問題塊的概念,一堆問題,這些問題同時間出現且沒有先後順序,則爲一個問題塊,一個問題塊中的一個select的選擇,可能觸發另一個問題塊的出現。
需求需要解決的點:
- 邏輯結構不定,爲後端提供
- 層級不定,由邏輯結構決定
- 填到最下面的葉子結點後,獲取全部用戶所填有效的問題結果
- 有創建和編輯功能,利用答案,反鋪該答案所對應的表單
解決思路:
創建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);
}
}
}
最後:最終這種的解決方案並不滿意,若有其他思路,請指教,謝謝