vue中如何使用遞歸組件他嫡,多層不同題目番官,如何使用遞歸重組樹形數(shù)據(jù)結構

vue官方文檔給出,遞歸組件介紹


image.png

但是如何使用钢属,以及實際應用的案例網(wǎng)上很少出現(xiàn)徘熔,今天介紹一下工作中遇到的問題,以及使用方法

1淆党,應用遞歸組件遍歷樹形題目


image.png

需求:有多種類型的題目酷师,有多級題目。分別根據(jù)類型顯示不同題目
2, 后端給的結構

{
    "data":{
        "cause_id":1,
        "id":3,
        "name":"相識結婚",
        "node_id":1,
        "points":[
            {
                "id":19,
                "level":1,
                "module_id":3,
                "must":false,
                "name":"二人相識方式",
                "reasons":[
                    {
                        "has_sub_reason":true,
                        "id":19,
                        "level":1,
                        "name":"經(jīng)人介紹",
                        "point_id":19,
                        "status":1,
                        "type_op":1
                    },
                    {
                        "has_sub_reason":false,
                        "id":20,
                        "level":1,
                        "name":"自由戀愛",
                        "point_id":19,
                        "status":1,
                        "type_op":1
                    },
                    {
                        "has_sub_reason":false,
                        "id":21,
                        "level":1,
                        "name":"婚戀網(wǎng)站",
                        "point_id":19,
                        "status":1,
                        "type_op":1
                    }
                ],
                "status":1,
                "type":1,
                "type_op":0
            },
            {
                "id":20,
                "level":1,
                "module_id":3,
                "must":false,
                "name":"請問您是否存在受脅迫結婚的情形宁否?",
                "reasons":[
                    {
                        "has_sub_reason":true,
                        "id":22,
                        "level":1,
                        "name":"是",
                        "point_id":20,
                        "status":1,
                        "type_op":1
                    },
                    {
                        "has_sub_reason":false,
                        "id":23,
                        "level":1,
                        "name":"否",
                        "point_id":20,
                        "status":1,
                        "type_op":1
                    }
                ],
                "status":1,
                "type":1,
                "type_op":0
            },
            {
                "id":21,
                "level":1,
                "module_id":3,
                "must":false,
                "name":"雙方之間是否進行了結婚登記窒升?",
                "reasons":[
                    {
                        "child_node":[
                            {
                                "id":1,
                                "level":2,
                                "must":false,
                                "name":"雙方是再婚嗎?",
                                "parent_reasons_id":24,
                                "reasons":[
                                    {
                                        "has_sub_reason":true,
                                        "id":1,
                                        "level":2,
                                        "name":"原告再婚",
                                        "point_id":1,
                                        "status":1,
                                        "type_op":1
                                    },
                                    {
                                        "has_sub_reason":true,
                                        "id":2,
                                        "level":2,
                                        "name":"被告再婚",
                                        "point_id":1,
                                        "status":1,
                                        "type_op":1
                                    },
                                    {
                                        "has_sub_reason":false,
                                        "id":3,
                                        "level":2,
                                        "name":"雙方系再婚",
                                        "point_id":1,
                                        "status":1,
                                        "type_op":1
                                    }
                                ],
                                "status":1,
                                "type":1,
                                "type_op":0
                            },
                            {
                                "id":2,
                                "level":2,
                                "must":false,
                                "name":"結婚登記日期為慕匠?",
                                "parent_reasons_id":24,
                                "reasons":[
                                    {
                                        "description":"請選擇結婚登記日期為饱须?",
                                        "has_sub_reason":false,
                                        "id":4,
                                        "level":2,
                                        "name":"結婚登記日期為?",
                                        "point_id":2,
                                        "status":1,
                                        "type_op":1
                                    }
                                ],
                                "status":1,
                                "type":3,
                                "type_op":0
                            }
                        ],
                        "description":"",
                        "has_sub_reason":true,
                        "id":24,
                        "level":1,
                        "name":"是",
                        "point_id":21,
                        "status":1,
                        "type_op":1
                    },
                    {
                        "has_sub_reason":false,
                        "id":25,
                        "level":1,
                        "name":"否",
                        "point_id":21,
                        "status":1,
                        "type_op":1
                    }
                ],
                "status":1,
                "type":1,
                "type_op":0
            }
        ],
        "status":1
    },
    "message":"",
    "status":200,
    "success":true
}

3, 前端代碼展示


image.png

首先解釋一下台谊,RadioComponent蓉媳,CheckComponent等組件設置成全局組件譬挚,form_item.type===1 為題目類型,為1 時為單選按鈕組件顯示酪呻。其他如同减宣。
這里組件用的時iview-design :prop="points.${form_index}.reasons"為設置是否必填,根據(jù)must判斷的玩荠。
此頁面所有代碼為

<style lang="less" scoped>
   .writeComplaint {
       flex: 1;
       display: flex;
       padding-bottom: 30px;

       .writeComplaintContent {
           display: flex;
           flex: 1;

           .leftMenu {
               width: 200px;
               display: flex;
           }

           .writeComplaintCard {
               margin-left: 20px;
               flex: 1;

               .submit {
                   /deep/ .ivu-form-item-content {
                       display: flex;
                       justify-content: center;
                   }
               }
           }

           .reference {
               width: 300px;
           }

           .wordContent {
               flex: 1;
               margin-left: 20px;
           }
       }
   }
</style>
<template>
   <div class="writeComplaint">
       <div v-if="isCreated" class="writeComplaintContent">
           <Card class="reference"></Card>
           <Card class="wordContent">
               <div v-for="(word_item,word_index) in word" :key="word_index">
                   <div v-if="word_item.title" class="title">{{word_item.title}}:</div>
                   <div class="content">{{word_item.content}}</div>
               </div>
           </Card>
       </div>
       <div v-else class="writeComplaintContent">
           <div class="leftMenu">
               <LeftMenu ref="menus" v-model="activeIndex" :data="modulesData" label="name"></LeftMenu>
           </div>
           <Card dis-hover class="writeComplaintCard">
               <Form :model="formdata" ref="formValidate" label-position="top">

                   <FormItem
                           v-for="(form_item,form_index) in moduleForm.points"
                           :rules="rules(form_item)"
                             :label="form_item.name"
                             :prop="`points.${form_index}.reasons`"
                             :key="form_index">
                       <RadioComponent v-if="form_item.type===1" v-model="formdata.points[form_index].reasons"
                                       :options="form_item.reasons"></RadioComponent>
                       <CheckComponent v-if="form_item.type===2" v-model="formdata.points[form_index].reasons"
                                      :options="form_item.reasons"></CheckComponent>
                       <TimeComponent v-if="form_item.type===3" v-model="formdata.points[form_index].reasons"
                                        :options="form_item.reasons"></TimeComponent>
<!--                        <SelectComponent v-if="form_item.type === 4" v-model="formdata.points[form_index].reasons"-->
<!--                                         :options="form_item.reasons"></SelectComponent>-->
                       <CityComponent v-if="form_item.type === 5" v-model="formdata.points[form_index].reasons"
                                      :options="form_item.reasons"></CityComponent>
                       <InputComponent v-if="form_item.type === 6" v-model="formdata.points[form_index].reasons"
                                       :options="form_item.reasons"></InputComponent>
                       <FormGroComponent v-if="form_item.type === 7" v-model="formdata.points[form_index].reasons"
                                         :options="form_item.reasons"></FormGroComponent>
                       <TextAreaComponent v-if="form_item.type === 8" v-model="formdata.points[form_index].reasons"
                                          :options="form_item.reasons"></TextAreaComponent>
                   </FormItem>

                   <FormItem class="submit">
                       <Button type="primary" @click="prev" v-if="activeIndex">上一步</Button>
                       <Button type="primary" @click="save('formValidate')" style="margin: 0 30px">保存</Button>
                       <Button type="primary" @click="createComplaint('formValidate')"
                               v-if="activeIndex===modulesData.length-1">生成訴狀
                       </Button>
                       <Button type="primary" @click="next('formValidate')" v-else>下一步</Button>
                   </FormItem>
               </Form>
           </Card>
       </div>
   </div>
</template>

<script type="text/javascript">
import LeftMenu from '@/components/LeftMenu.vue';
import { created, moduleDetail, modules, save } from '@/api/lawsuit.js';

export default {
 data () {
   var _this = this;
   return {
     formdata: {
       points: []
     },
     moduleForm: {},
     activeIndex: 0,
     modulesData: [],
     isSave: false,
     wordOrder: [],
     isCreated: false,
     word: [],
     points: []
   };
 },
 components: {
   LeftMenu
 },
 watch: {
   activeIndex (newvalue, oldvalue) {
     this.getModuleDetail(this.modulesData[ newvalue ])
   }
 },
 created () {
   this.getModules();
 },
 methods: {
   async getModules () {
     const { data } = await modules({
       node_id: this.$route.query.step,
       cause_id: this.$route.query.cause_id
     })
     this.modulesData = data.data;
     const [ first ] = this.modulesData;
     this.getModuleDetail(first);
   },
   async getModuleDetail (module_data) {
     const { data } = await moduleDetail({
       module_id: module_data.id
     })
     this.moduleForm = data.data;
     this.formdata.points = [];
     this.moduleForm.points.map((module_item) => {
       this.formdata.points.push({
         point_id: module_item.id,
         reasons: []
       })
       // this.points =
     })
   },
   save () {
     this.savaLoad().then((data) => {
       // this.isSave = true
     })
   },
   async createComplaint (name) {
     const invalidate = await this.validate(name)
     if (!invalidate) return
     const data = await this.savaLoad(name);
     const createData = await created({
       document_id: this.$route.query.document_id
     });
     this.isCreated = true
     const wordData = createData.data.data;
     this.wordOrder.map((item) => {
       this.word.push({
         title: wordData[ item.title ],
         content: wordData[ item.content ]
       })
     })
   },
   async next (name) {
     if (this.activeIndex < this.modulesData.length - 1) {
       if (this.isSave) {
         const invalidate = await this.validate(name)
         if (!invalidate) return
         this.activeIndex += 1
       } else {
         this.savaLoad(name).then((data) => {
           if (data) {
             this.activeIndex += 1
           }
         })
       }
     }
   },
   prev () {
     this.activeIndex -= 1
   },
   async savaLoad () {
     const invalidate = await this.validate('formValidate')
     if (!invalidate) return
     const params = Object.assign({}, {
       document_id: this.$route.query.document_id,
       module_id: this.moduleForm.id,
       points: this.formdata.points
     })
     const { data } = await save(params)
     if (data.status === 200) {
       this.$Message.success('保存成功');
       return true
     }
   },
   validate (name) {
     return new Promise((resolve) => {
       this.$refs[ name ].validate((valid) => {
         if (valid) {
           resolve(true)
         } else {
           resolve(false)
         }
       })
     })
   },
   rules (form_item) {
     return [ {
       required: form_item.must,
       message: '不能為空'
     } ]
   }
 }
};
</script>

4漆腌,只寫其中一個單選的例子,其他類似


image.png
image.png
image.png

radio組件所有代碼如下

<template>
   <div class="mock-wrapper flex-sb-top" :style="[{display: 'block'}]">
           <div v-for="(form_item, index) in options"
                :key="index"
           >
               <div>
                   <div v-if="form_item.type_op === 0" class="name-box" @click="getDownOrUp($event,index)">
                       <Icon type="ios-arrow-down" />
                       <span class="question-name">{{form_item.question}},{{form_item.name}}</span>
                   </div>
                   <div v-if="form_item.type_op === 1" class="content-box">
                       <Radio v-model="form_item.checked" :label="form_item.name" @on-change="handleChange($event,form_item)"></Radio>
                   </div>
               </div>
              <RadioComponent v-if="form_item.type === 1" :options="form_item.reasons"></RadioComponent>
              <CheckComponent v-if="form_item.type === 2" :options="form_item.reasons"></CheckComponent>
              <TimeComponent v-if="form_item.type === 3" :options="form_item.reasons"></TimeComponent>
<!--               <SelectComponent v-if="form_item.type === 4" :options="form_item"></SelectComponent>-->
              <CityComponent v-if="form_item.type === 5" :options="form_item.reasons"></CityComponent>
              <InputComponent v-if="form_item.type === 6" :options="form_item.reasons"></InputComponent>
              <FormGroComponent v-if="form_item.type === 7" :options="form_item.reasons"></FormGroComponent>
              <TextAreaComponent v-if="form_item.type === 8" :options="form_item.reasons"></TextAreaComponent>
               <div v-if="form_item.checked && form_item.child_node" class="sub-item-box">
                   <RadioComponent :options="form_item.child_node"></RadioComponent>
               </div>
           </div>
   </div>
</template>
<script>
export default {
 name: 'RadioComponent',
 props: {
   options: {
     type: Array,
     default () {
       return []
     }
   }
 },
 methods: {
   handleChange (checked, option) {
     this.options = this.options.map(val => {
       val.checked = val.id === option.id;
       val.reason_id = val.id
       return val
     });
     this.$emit('input', this.options)
   },
   getDownOrUp (index) {
     let dom = null;
     if (index.toElement.className === 'name-box') {
       dom = index.path[ 1 ].parentNode.children[ 1 ]
     } else {
       dom = index.path[ 1 ].parentNode.parentNode.children[ 1 ]
     }
     const self = index.path[ 0 ].parentNode;
     if (dom.style.display === 'block') {
       dom.style.display = 'none';
       self.getElementsByTagName('i')[ 0 ].classList.replace('ivu-icon-ios-arrow-down', 'ivu-icon-ios-arrow-forward')
     } else {
       dom.style.display = 'block';
       self.getElementsByTagName('i')[ 0 ].classList.replace('ivu-icon-ios-arrow-forward', 'ivu-icon-ios-arrow-down')
     }
   }
 }
}
</script>
<style lang="less">
   @import "index";
</style>

其他組件類似

5阶冈,這樣就會出現(xiàn)效果闷尿,其中有一個三級城市組件給的數(shù)據(jù)不符合要求,組件需要label 和vaule 現(xiàn)在給的是name, id所以需要轉一下女坑,用到遞歸函數(shù)

 handleCity: function (tree) {
      for (let i = 0; i < tree.length; i++) {
        tree[ i ].label = tree[ i ].name;
        tree[ i ].value = tree[ i ].id;
        tree[ i ].reason_id = tree[ i ].id;
        if (tree[ i ].children && tree[ i ].children.length > 0) {
          this.handleCity(tree[ i ].children)
        }
      }
      return tree
    },

6填具,全篇有個知識點這里有個 子組件用 this.$emit('input', selectedData),
組件調用 使用 <RadioComponent v-if="form_item.type===1" v-model="formdata.points[form_index].reasons"
:options="form_item.reasons"></RadioComponent>
組件調用使用v-model 接受改變過的值
vue中子組件不能直接改變通過props 傳過來的值的會報錯


image.png

這里沒有找到好方法匆骗,只能屏蔽警告提示劳景,不影響效果

Vue.config.warnHandler = function (msg) {
  if (!msg.includes('Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.')) { // uniApp bug: https://ask.dcloud.net.cn/question/71966
    return console.warn && console.warn(msg)
  }
}

至此vue遞歸組件遍歷不同類型不同級別的題目問題寫好了。
如果喜歡歡迎點贊留言碉就,關注本人維護的公眾號---- 程序員蝸牛盟广,有免費學習資料贈送//

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓮钥,隨后出現(xiàn)的幾起案子衡蚂,更是在濱河造成了極大的恐慌,老刑警劉巖骏庸,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毛甲,死亡現(xiàn)場離奇詭異,居然都是意外死亡具被,警方通過查閱死者的電腦和手機玻募,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來一姿,“玉大人七咧,你說我怎么就攤上這事《L荆” “怎么了艾栋?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛉顽。 經(jīng)常有香客問我蝗砾,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任悼粮,我火速辦了婚禮闲勺,結果婚禮上,老公的妹妹穿的比我還像新娘扣猫。我一直安慰自己菜循,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布申尤。 她就那樣靜靜地躺著癌幕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪昧穿。 梳的紋絲不亂的頭發(fā)上序芦,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音粤咪,去河邊找鬼。 笑死渴杆,一個胖子當著我的面吹牛寥枝,可吹牛的內容都是我干的。 我是一名探鬼主播磁奖,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼囊拜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了比搭?” 一聲冷哼從身側響起冠跷,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎身诺,沒想到半個月后蜜托,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡霉赡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年橄务,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穴亏。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜂挪,死狀恐怖,靈堂內的尸體忽然破棺而出嗓化,到底是詐尸還是另有隱情棠涮,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布刺覆,位于F島的核電站严肪,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜诬垂,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一劲室、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧结窘,春花似錦很洋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至官脓,卻和暖如春协怒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卑笨。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工孕暇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赤兴。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓妖滔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親桶良。 傳聞我的和親對象是個殘疾皇子座舍,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353