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递归组件遍历不同类型不同级别的题目问题写好了。
如果喜欢欢迎点赞留言,关注本人维护的公众号---- 程序员蜗牛,有免费学习资料赠送//