vue中如何使用遞歸組件,多層不同題目,如何使用遞歸重組樹形數據結構

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


但是如何使用,以及實際應用的案例網上很少出現,今天介紹一下工作中遇到的問題,以及使用方法

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


需求:有多種類型的題目,有多級題目。分別根據類型顯示不同題目
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":"經人介紹",
                        "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":"婚戀網站",
                        "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, 前端代碼展示


首先解釋一下,RadioComponent,CheckComponent等組件設置成全局組件,form_item.type===1 爲題目類型,爲1 時爲單選按鈕組件顯示。其他如同。
這裏組件用的時iview-design :prop="points.${form_index}.reasons"爲設置是否必填,根據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,只寫其中一個單選的例子,其他類似


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,這樣就會出現效果,其中有一個三級城市組件給的數據不符合要求,組件需要label 和vaule 現在給的是name, id所以需要轉一下,用到遞歸函數

 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 傳過來的值的會報錯


這裏沒有找到好方法,只能屏蔽警告提示,不影響效果

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遞歸組件遍歷不同類型不同級別的題目問題寫好了。
如果喜歡歡迎點贊留言,關注本人維護的公衆號---- 程序員蝸牛,有免費學習資料贈送//

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