element-plus+vue3动态添加删除组件(组件嵌套)

一、需求描述

       有一个添加打分点按钮,点击添加会新增一个打分点内容,默认只有一个打分点的时候不显示删除按钮,如果有多个打分点,就都显示删除按钮。在分类内容里面有一个添加分类,与打分点类似。分类内容里面包含打分点内容。即可以动态新增删除多个分类,分类里面也可以动态新增多个打分点。文字描述有点抽象,看下图好理解一些:

 二、思路

将打分点内容和分类内容封装成组件,然后在分类组件中引用打分点组件(组件嵌套)。打分点和分类序号采取v-for之后的index索引+1显示就好了,这样不管是删除3个打分点中间的,还是哪个,索引也能对应得上。点击删除打分点,在打分点数组中将数组列表根据索引删除,分类也是。添加的话,直接创建一个对象,将他们push到对应的数组列表中。在打分点填好的数据,要传回上一级组件,将数据逐级向上传,得到最终的数据列表。

三、预期数据结构(数据结构模拟,具体按接口的数据结构来使用)

const KindsList = ref([
  {
    kindname: "",
    id: Math.floor(Math.random() * 1000),
    scoringpoints: [
      {
        id: Math.floor(Math.random() * 1000),
        potiontitle: "",
      },
      // 其他节点
    ],
  },
  //其它分类
]);

四、保存所填的信息,不至于删除处于3个中间的数据,而导致后面的数据丢失,也就是未让组件之间传递的数据保留

<el-input
            v-model="props.Kindsinfo.kindname"
            placeholder="点击输入标题"
            clearable
            @change="changeData"
            style="width: 550px;margin-left: 10px;"/>

props.Kindsinfo.kindname采用这种方式,使用v-model将传过来的数据进行双向绑定。

五、将所填的数据向上回传

//子组件中自定义事件
  const emits = defineEmits(["KindListData"]);
    const changeData = () => {
    emits("KindListData",props.Kindsinfo);
  }

在上一级组件接受到传过来的数据,进行更新。

// 更新分类列表数据
const KindListData = (Kindsinfo) => {
  console.log(Kindsinfo.kindname + "id:" + Kindsinfo.id);
  KindsList.value.forEach((item, index) => {
    if (item.id === Kindsinfo.id) {
      KindsList.value[index] = Kindsinfo;
      console.log(KindsList.value[index]);
    }
  });
};

六、删除操作

首先先提示是否删除,确认删除之后,在确认的方法后面进行删除操作,将要删除的索引传到上级组件,由上一级组件进行把对应索引的数据从数组中删除。

<div class="btn">
        <div class="add-btn" @click="addNewPoints">添加打分点</div>
        <div class="delete-btn">
          <el-popconfirm
          title="是否要删除该分类?"
          @confirm="confirmEvent"
          @cancel="cancelEvent"
        >
          <template #reference>
            <el-button type="danger" round v-show="listLength>1">删除此分类</el-button>
          </template>
        </el-popconfirm>
        </div>
      </div>
// 告诉父组件删除该分类
  //子组件中自定义事件
  const emits = defineEmits(["deleteKind","KindListData"]);
  // 确认删除该分类
  const confirmEvent = () => {
    console.log("确认删除该分类!");
    emits("deleteKind", props.title);
  };
// 删除分类
const deleteKind = (index) => {
  // 使用findIndex方法找到具有特定ID的对象的索引
  // let index = kindsList.value.findIndex((obj) => obj.id === id);
  // 如果找到了具有特定ID的对象,则将其从数组中移除
  if (index !== -1) {
    KindsList.value.splice(index, 1);
  }
};

七、添加操作

创建一个新的对像,将它往数组中push一个新的对象就好。

// 添加分类
const addNewKind = () => {
  const newKind = {
    id: Math.floor(Math.random() * 1000),
    scoringpoints: [
      {
        id: Math.floor(Math.random() * 1000),
        potiontitle: "",
      },
    ],
  };
  KindsList.value.push(newKind);
};

八、点击保存获取最终的数据

获取到最终的数据,用于接下来的操作,传递给接口啥的。反正这一步是必须的,获取的最终数据要进行保存,请求接口。

// 保存按钮
const save = () => {
  console.log(KindsList.value);
};

 

 

九、包含分类组件和打分点组件的页面完整代码

<template>
  <div class="container">
    <Commonhead :title="title"></Commonhead>
    <div class="main">
      <div class="head">巡检配置</div>
      <div class="form">
        <!-- 路线 -->
        <div class="kind">
          <Classify
            v-for="(item, index) in KindsList"
            :key="index"
            :pointsList="KindsList[index].scoringpoints"
            :title="index"
            :Kindsinfo="KindsList[index]"
            @KindListData="KindListData"
            :id="KindsList[index].id"
            :listLength="KindsList.length"
            @deleteKind="deleteKind"
          ></Classify>
          <div class="addkind-btn" @click="addNewKind">添加分类</div>
        </div>
      </div>
      <div class="save-btn" @click="save">保存</div>
    </div>
  </div>
</template>

<script setup>
import Classify from "../src/components/Classify.vue";
import Commonhead from "../src/components/Commonhead.vue";
const title = ref("项目管理 > 巡检打分表单");
const KindsList = ref([
  {
    kindname: "",
    id: Math.floor(Math.random() * 1000),
    scoringpoints: [
      {
        id: Math.floor(Math.random() * 1000),
        potiontitle: "",
      },
      // 其他节点
    ],
  },
  //其它分类
]);
// 添加分类
const addNewKind = () => {
  const newKind = {
    id: Math.floor(Math.random() * 1000),
    scoringpoints: [
      {
        id: Math.floor(Math.random() * 1000),
        potiontitle: "",
      },
    ],
  };
  KindsList.value.push(newKind);
};
// 删除分类
const deleteKind = (index) => {
  // 使用findIndex方法找到具有特定ID的对象的索引
  // let index = kindsList.value.findIndex((obj) => obj.id === id);
  // 如果找到了具有特定ID的对象,则将其从数组中移除
  if (index !== -1) {
    KindsList.value.splice(index, 1);
  }
};
// 保存按钮
const save = () => {
  console.log(KindsList.value);
};
// 更新分类列表数据
const KindListData = (Kindsinfo) => {
  console.log(Kindsinfo.kindname + "id:" + Kindsinfo.id);
  KindsList.value.forEach((item, index) => {
    if (item.id === Kindsinfo.id) {
      KindsList.value[index] = Kindsinfo;
      console.log(KindsList.value[index]);
    }
  });
};
</script>

<style scoped>
.container {
  padding: 20px;
}
.main {
  width: 1200px;
  margin: 0 auto;
}
.head {
  text-align: center;
  background-color: #f9f1f1;
  height: 40px;
  line-height: 40px;
}
.form {
  width: 800px;
  margin: 0 auto;
}
.save-btn {
  width: 100px;
  margin: 40px auto;
  height: 30px;
  background-color: #f5b90f;
  color: #fff;
  text-align: center;
  line-height: 30px;
  font-size: 14px;
  border-radius: 5px;
  cursor: pointer;
}
.addkind-btn {
  width: 100px;
  height: 30px;
  background-color: #f5b90f;
  color: #fff;
  text-align: center;
  font-size: 14px;
  border-radius: 5px;
  line-height: 30px;
  margin: 40px auto;
  cursor: pointer;
}
</style>

十、 分类组件的完整代码

<template>
    <div class="kind-con">
      <div class="name">
        <div class="name-title">分类名称:</div>
        <el-input
            v-model="props.Kindsinfo.kindname"
            placeholder="点击输入标题"
            clearable
            @change="changeData"
            style="width: 550px;margin-left: 10px;"/>
      </div>
      <Scoringpoints
        v-for="(item, index) in pointsList"
        :key="item.id"
        :title="index"
        :id="item.id"
        :listLength="pointsList.length"
        :pointsinfo="pointsList[index]"
        @deletepoint="deletepoint"
        @pointsListData="pointsListData"
      ></Scoringpoints>
      <div class="btn">
        <div class="add-btn" @click="addNewPoints">添加打分点</div>
        <div class="delete-btn">
          <el-popconfirm
          title="是否要删除该分类?"
          @confirm="confirmEvent"
          @cancel="cancelEvent"
        >
          <template #reference>
            <el-button type="danger" round v-show="listLength>1">删除此分类</el-button>
          </template>
        </el-popconfirm>
        </div>
      </div>
    </div>
  </template>
  
  <script setup>
import Scoringpoints from "./Scoringpoints.vue";
  // Props 接收打分点信息
  const props = defineProps({
    // 打分点列表
    pointsList: {
      type: Array,
      required: true,
    },
    // 分类列表的长度
    listLength:Number,
    title: Number,
    // 分类的id
    id: {
      type: null, // id可以是任何类型
      required: true, // 如果id是必需的话
    },
    Kindsinfo:Object
  });
  // 将 props 转换为可修改的引用
  const { pointsList } = toRefs(props);
//   分类名称
  const name = ref("");
  // 添加打分点
  const addNewPoints = () => {
    const scoringpoints = {
      id: Math.floor(Math.random() * 1000), // 生成一个随机的节点ID
          potiontitle: "",
    };
    pointsList.value.push(scoringpoints);
  };
  // 告诉父组件删除该分类
  //子组件中自定义事件
  const emits = defineEmits(["deleteKind","KindListData"]);
  // 确认删除该分类
  const confirmEvent = () => {
    console.log("确认删除该分类!");
    emits("deleteKind", props.title);
  };
  // 取消删除该分类
  const cancelEvent = () => {
    console.log("取消删除该分类!");
  };
  // 删除打分点
  const deletepoint = (index) => {
    // 使用findIndex方法找到具有特定ID的对象的索引
    // let index = pointsList.value.findIndex((obj) => obj.id === id);
    // 如果找到了具有特定ID的对象,则将其从数组中移除
    if (index !== -1) {
      pointsList.value.splice(index, 1);
    }
  };
  // 更新打分点数据
  const pointsListData = ( pointsinfo ) => {
    console.log(pointsinfo.potiontitle+"id:"+pointsinfo.id);
    pointsList.value.forEach((item,index)=>{
      if(item.id === pointsinfo.id){
        pointsList.value[index] = pointsinfo
        console.log(pointsList.value[index]);
      }
    })
  }
  const changeData = () => {
    emits("KindListData",props.Kindsinfo);
  }
  </script>
  
  <style scoped>
  .kind-con {
    margin-bottom: 20px;
  }
  .route-title {
    width: 200px;
    text-align: end;
    margin-left: 10px;
    color: #f5b90f;
    font-weight: 600;
    margin-bottom: 10px;
  }
  .btn {
    display: flex;
    align-items: center;
  }
  .add-btn {
    width: 100px;
    height: 30px;
    background-color: #f5b90f;
    color: #fff;
    text-align: center;
    font-size: 14px;
    border-radius: 5px;
    line-height: 30px;
    margin-left: 235px;
    cursor: pointer;
  }
  .delete-btn {
    margin-left: 10px;
  }
  .name {
    padding-left: 20px;
    display: flex;
    font-size: 13px;
    margin: 20px 0;
  }
  .name-title {
    width: 200px;
    text-align: end;
    font-size: 13px;
  }
  .name-title::before {
    content: "*";
    left: 0;
    top: 0;
  }
  </style>
  

十一、打分点完整代码

<template>
    <div class="con">
      <div class="points">
        <!-- 节点名称 -->
        <div class="points-title">打分点{{ title + 1 }}:</div>
        <div class="points-input">
          <el-input
            v-model="props.pointsinfo.potiontitle"
            placeholder="点击输入标题"
            clearable
            @change="changeData"/>
        </div>
      </div>
      <div class="delete-btn">
        <el-popconfirm
          title="是否要删除该打分点?"
          @confirm="confirmEvent"
          @cancel="cancelEvent"
        >
          <template #reference>
            <el-button type="danger" round v-show="listLength>1">删除打分点</el-button>
          </template>
        </el-popconfirm>
      </div>
    </div>
  </template>
  
  <script setup>
  const props = defineProps({
    // 打分点序号
    title: Number,
    // 打分点列表的长度
    listLength:Number,
    // 打分点的id
    id: {
      type: null, // id可以是任何类型
      required: true, // 如果id是必需的话
    },
    //打分点信息
    pointsinfo : Object,
  });
  const Inputvalue = ref("");
  
  //子组件中自定义事件
  const emits = defineEmits(["deletepoint","pointsListData"]);
  const confirmEvent = () => {
    console.log("确认删除该打分点!");
    emits("deletepoint", props.title);
  };
  const cancelEvent = () => {
    console.log("取消删除该打分点!");
  };
  const changeData = () => {
    emits("pointsListData",props.pointsinfo);
  }
  </script>
  
  <style scoped>
  .con {
    padding: 0 20px 10px 10px;
  }
  .points {
    display: flex;
    justify-content: space-between;
  }
  .points-title {
    width: 200px;
    text-align: end;
    font-size: 13px;
  }
  .points-title::before {
    content: "*";
    left: 0;
    top: 0;
  }
  .points-input {
    width: 550px;
    flex-wrap: wrap;
    display: flex;
    align-items: center;
  }
  .delete-btn {
    margin-left: 225px;
    margin-top: 5px;
  }
  </style>
  

 前提是安装好element-plus才可以使用哦!

以上做参考!

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3 和 Element Plus 的组合中,要实现侧边栏折叠的效果,可以使用`el-menu`作为根节点,并结合`el-menu-item`和`el-submenu`来构建菜单栏。 首先,在模板中使用`el-menu`作为整个菜单的容器,并设置`collapse`属性来控制侧边栏的折叠状态。当`collapse`为`true`时,侧边栏折叠,文字会隐藏。当`collapse`为`false`时,侧边栏展开,文字会显示。 ```html <template> <el-menu :collapse="isCollapse"> <!-- 菜单项 --> <el-menu-item index="1">菜单项1</el-menu-item> <el-menu-item index="2">菜单项2</el-menu-item> <!-- 子菜单 --> <el-submenu index="3"> <template #title> 子菜单 </template> <el-menu-item index="3-1">子菜单项1</el-menu-item> <el-menu-item index="3-2">子菜单项2</el-menu-item> </el-submenu> </el-menu> </template> ``` 然后,在data中定义一个`isCollapse`变量来控制折叠状态的切换。通过点击按钮或其他交互方式,修改`isCollapse`的值来实现侧边栏的折叠和展开。 ```javascript <script> export default { data() { return { isCollapse: true, // 初始化折叠状态为true,侧边栏默认折叠 }; }, }; </script> ``` 需要注意的是,`el-menu`标签本身希望里面嵌套的是`el-menu-item`、`el-submenu`、`el-menu-item-group`其中之一。因此,我们要根据菜单的结构来合理使用这些标签。 通过上述方法,你可以实现在Vue 3 和 Element Plus 中的侧边栏折叠效果。根据`el-menu`的属性设置和交互操作来控制侧边栏的折叠和展开状态,从而实现文字的隐藏和显示效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值