You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js中解决所有下拉菜单同时展开问题的方法

问题描述

我开发了一款用于管理待办事项(todo)列表的应用,支持列表的编辑与删除操作。我希望为每个列表添加下拉菜单,以跳转到列表更新或删除页面,但遇到问题:点击下拉图标时,所有列表的下拉菜单会同时展开。附上我的Vue.js代码及仪表盘截图,恳请帮助解决该问题。

DashboardView.vue 代码

<template>
  <div class="dashboard">
    <Navbar class="navbar__dash" />
    <h1 class="dashboard__welcome">Welcome {{ name }}</h1>
    <div class="listview">
      <div class="no__lists" v-if="len_list === 0">
        <h2 class="no_lists">There are No lists here tap to add</h2>
      </div>
      <div class="has__lists" v-else>
        <div class="all__lists" v-for="(item, index) in items" :key="index">
          <div class="list">
            <div class="title">
              <div class="title__options">
                <h1 class="list_name">{{ item.list_name }}</h1>
                <div class="dropdown">
                  <button @click="toggleMenu">
                    <fa class="dropdown_icon" icon="fa-solid fa-arrow-down" />
                  </button>
                  <div v-if="showMenu" class="menu">
                    <div
                      class="menu-item"
                      v-for="(item, index) in links_list"
                      :key="index"
                      @click="itemClicked"
                    >
                      {{ item.title }}
                    </div>
                  </div>
                </div>
                <!-- V-menu -->
                <!--menu ends-->
              </div>
            </div>
            <div class="body">
              <div class="no_tasks" v-if="item.no_of_tasks === 0">
                <h3>There are no tasks added yet</h3>
              </div>
              <div class="has_tasks" v-else>
                <h4>Here are your tasks</h4>
              </div>
            </div>
            <div class="footer">
              Number of Completed tasks: {{ item.no_completed }}/{{
                item.no_of_tasks
              }}
            </div>
          </div>
        </div>
      </div>
      <router-link :to="`/createList/${id}`">
        <fa class="plus__icon" icon="fa-solid fa-plus" />
      </router-link>
    </div>
  </div>
</template>

<script>
import axios from 'axios';

import Navbar from '../components/NavBar.vue';
export default {
  name: 'DashboardView',
  data() {
    return {
      name: localStorage['name'],
      len_list: 0,
      items: [],
      id: localStorage['id'],
      links_list: [{ title: 'Click Me' }, { title: 'Click Me' }],
      showMenu: false,
    };
  },
  methods: {
    getTrackers() {
      const path = 'http://localhost:5000/dashboard/' + localStorage['id'];
      axios
        .get(path)
        .then((res) => {
          this.items = res.data.list;
          console.log(res.data.list);
          this.len_list = res.data.list[0]['len_list'];
        })

        .catch((err) => {
          console.log(err);
        });

      console.log(this.items);
    },
    toggleMenu() {
      this.showMenu = !this.showMenu;
    },
    itemClicked() {
      this.toggleMenu();
    },
  },
  components: {
    Navbar,
  },
  created() {
    this.getTrackers();
  },
};
</script>

<style>
.dashboard {
  width: 100%;
  min-height: 100vh;
  color: #ffd700;
  background-image: url('../assets/wood-texture.jpg');
  overflow-x: auto;
  overflow-y: hidden;
}
.dashboard__welcome {
  margin-top: 2rem;
  font-weight: 600;
}
.plus__icon {
  margin-left: 58rem;
  width: 32px;
  height: 32px;
  margin-top: 1rem;
  color: #ffd700;
  padding: 1rem;
  border: 1px solid #ffd700;
  border-radius: 50%;
}
.no_lists {
  text-align: center;
  margin-top: 15rem;
}

.navbar__dash {
  background-color: black;
  color: white;
  max-width: inherit;
}
.has__lists {
  display: flex;
}
.list {
  padding: 10rem;
  border: 1px solid #ffd700;
  border-radius: 2rem;
  width: 20%;
  margin-left: 1rem;
  margin-top: 5rem;
  padding-bottom: 0px;
}
.title {
  background: #ffd700;
  color: black;
  width: 320px;
  text-align: center;
  margin-left: -10rem;
  margin-top: -10rem;
  height: 100px;
  border-top-left-radius: 2rem;
  border-top-right-radius: 2rem;
}
.footer {
  margin-top: 10rem;
  background: #ffd700;
  color: black;
  width: 320px;
  margin-left: -10rem;
  margin-top: 0.01rem;
  margin-bottom: 0rem;
  height: 100px;
  border-bottom-left-radius: 2rem;
  border-bottom-right-radius: 2rem;
  text-align: center;
}
.body {
  background-color: white;
  width: 320px;
  color: grey;
  margin-left: -10rem;
  text-align: center;
  height: 10rem;
}
.list_name {
  margin-top: 0.6rem;
  padding: 2px;
  font-weight: 600;
  font-size: 20px;
  border-top-left-radius: 1rem;
  border-bottom-left-radius: 1rem;
}
.list__icon {
  width: 20px;
  height: 20px;
}
.title__options {
  display: flex;
  justify-content: center;
}
.dropdown_icon {
  padding: 0.2rem;
  color: #ffd700;
  background: black;
  margin-top: 15px;
  transition: var(--transition);
  border-radius: 2rem;
}
.dropdown_icon:hover {
  background: white;
  color: black;
  height: 20px;
}
.menu {
  background: white;
  padding-left: 2rem;
  padding-right: 2rem;
  border-radius: 1rem;
}
</style>

截图

  • 仪表盘初始状态:
    仪表盘
  • 点击下拉按钮后的状态:
    点击下拉按钮后的仪表盘

解决方案

问题根源是你用了一个全局的showMenu变量控制所有下拉菜单的显示/隐藏,点击时会同时切换所有菜单的状态。要解决这个问题,需要为每个列表项单独维护菜单状态,以下是两种常用方案:

方案1:给每个列表项添加独立的菜单状态

在获取列表数据时,为每个item添加showMenu字段,初始为false:

getTrackers() {
  const path = 'http://localhost:5000/dashboard/' + localStorage['id'];
  axios
    .get(path)
    .then((res) => {
      // 给每个列表项添加独立的showMenu属性
      this.items = res.data.list.map(item => ({
        ...item,
        showMenu: false
      }));
      this.len_list = res.data.list[0]['len_list'];
    })
    .catch((err) => {
      console.log(err);
    });
}

修改模板和方法,针对当前item操作:

<!-- 点击事件传入当前列表项的索引 -->
<button @click="toggleMenu(index)">
  <fa class="dropdown_icon" icon="fa-solid fa-arrow-down" />
</button>
<!-- 使用当前item的showMenu判断是否显示 -->
<div v-if="item.showMenu" class="menu">
  <div
    class="menu-item"
    v-for="(link, linkIndex) in links_list"
    :key="linkIndex"
    @click="itemClicked(index)"
  >
    {{ link.title }}
  </div>
</div>

更新方法逻辑:

toggleMenu(index) {
  // 切换对应索引的列表项菜单状态
  this.items[index].showMenu = !this.items[index].showMenu;
},
itemClicked(index) {
  this.toggleMenu(index);
  // 这里可添加跳转逻辑,比如:
  // this.$router.push(`/editList/${this.items[index].id}`)
}

方案2:维护当前展开菜单的索引

如果不想修改列表数据,可以在data中添加一个变量记录当前展开的菜单索引:

data() {
  return {
    // ...其他数据
    activeMenuIndex: null
  };
}

修改模板和方法:

<!-- 点击事件传入当前列表项的索引 -->
<button @click="toggleMenu(index)">
  <fa class="dropdown_icon" icon="fa-solid fa-arrow-down" />
</button>
<!-- 判断当前索引是否为激活状态 -->
<div v-if="activeMenuIndex === index" class="menu">
  <div
    class="menu-item"
    v-for="(link, linkIndex) in links_list"
    :key="linkIndex"
    @click="itemClicked()"
  >
    {{ link.title }}
  </div>
</div>

更新方法逻辑:

toggleMenu(index) {
  // 点击当前展开的菜单则关闭,否则切换到该菜单
  this.activeMenuIndex = this.activeMenuIndex === index ? null : index;
},
itemClicked() {
  this.activeMenuIndex = null;
  // 这里添加跳转逻辑
}

额外优化建议

  • 避免用index作为v-for的key,尽量使用列表项的唯一ID(如item.id),提升Vue渲染效率。
  • 可以给links_list添加路径字段,比如{ title: '编辑列表', path: '/edit' },方便后续跳转逻辑的编写。

内容的提问来源于stack exchange,提问作者MAHEEDHAR A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 13:01:05