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

Vue.js多组件交互异常:关闭ProjectWindow后无法打开文件夹卡片

Vue组件交互异常修复方案

问题核心原因

  1. 直接操作DOM元素导致状态混乱,脱离Vue响应式系统,关闭ProjectWindow后DOM被v-if销毁,后续获取元素失败
  2. ToolBar组件逻辑重叠,同时处理card和projectWindow的DOM操作,引发状态冲突
  3. 透明度值设置错误:opacity合法范围是0-1,而非代码中的10

解决方案

1. 重构ToolBar组件

移除所有DOM操作,改为通过事件通知父组件处理状态,让父组件完全控制显示逻辑,实现真正的组件复用。

2. 用响应式数据替代DOM操作

所有显示/隐藏、样式变化通过Vue的响应式数据控制,不再手动修改元素style。

3. 统一状态管理

由ProjectsView父组件统一管理文件夹卡片和ProjectWindow的显示、最小化状态,避免组件间状态干扰。


修改后的组件代码

ToolBar组件

<script>
export default {
  name: 'ToolBar',
  emits: ['toggle-size', 'close'],
  props: {
    isMinimized: Boolean
  }
}
</script>

<template>
  <div class="toolbar">
    <h4 @click="$emit('toggle-size')" class="bi" :class="isMinimized ? 'bi-fullscreen' : 'bi-fullscreen-exit'"></h4>
    <h4 @click="$emit('close')" class="bi bi-x-square" style="color: darkred"></h4>
  </div>
</template>

<style lang="scss">
.toolbar {
  display: flex;
  flex-direction: row;
  margin-left: auto;
  gap: 20px;
}
</style>

ProjectWindow组件

<script>
import axios from 'axios';
import { ref, onMounted } from 'vue';
import ToolBar from "@/components/ToolBar.vue";

export default {
  name: 'ProjectWindow',
  components: { ToolBar },
  props: {
    slug: String,
    isMinimized: Boolean
  },
  emits: ['close', 'toggle-size'],
  setup(props) {
    const projectData = ref({
      name: null,
      slug: null,
      description: null,
      gitHub: null,
      isHosted: null,
      hostLink: null,
    });

    onMounted(async () => {
      try {
        const response = await axios.get(`/api/v1/projects/`);
        const project = response.data.find(p => p.slug === props.slug);

        if (project) {
          projectData.value = {
            name: project.name,
            slug: project.slug,
            description: project.description.replace(/\\n/g, '<br>'),
            gitHub: project.github,
            isHosted: project.is_hosted,
            hostLink: project.host_link,
          };
        }
      } catch (error) {
        console.error(error);
      }
    });

    return {
      projectData,
    };
  }
};
</script>

<template>
  <section class="container" :class="{ minimized: isMinimized }">
    <div class="card">
      <div class="card-header">
        <h3 class="title">{{ projectData.name }}</h3>
        <ToolBar 
          :is-minimized="isMinimized"
          @toggle-size="$emit('toggle-size')"
          @close="$emit('close')"
        />
      </div>
      <div class="card-body" v-show="!isMinimized">
        <div class="description" v-html="projectData.description"></div>
      </div>
      <div class="card-footer" v-show="!isMinimized" style="display: flex; flex-direction: row; gap: 10px">
        <a :href="projectData.gitHub" target="_blank" class="btn btn-primary">GitHub</a>
        <a v-if="projectData.isHosted" :href="projectData.hostLink" target="_blank" class="btn btn-primary">Website Link</a>
      </div>
    </div>
  </section>
</template>

<style scoped>
.container {
  transition: all 0.5s ease-in-out;
}
.container.minimized {
  height: 50px;
  overflow: hidden;
  margin-top: auto;
}
</style>

ProjectsView视图

<script>
import ProjectWindow from "@/components/ProjectWindow.vue";
import ToolBar from "@/components/ToolBar.vue";

export default {
  name: 'Projects',
  components: {ToolBar, ProjectWindow},
  data() {
    return {
      slug: null,
      isFolderOpen: false,
      isFolderMinimized: false,
      isGameOpen: false,
      isGameMinimized: false
    }
  },
  methods: {
    openGame(slug) {
      this.slug = slug;
      this.isGameOpen = true;
      this.isFolderOpen = false;
    },
    toggleFolderSize() {
      this.isFolderMinimized = !this.isFolderMinimized;
    },
    closeFolder() {
      this.isFolderOpen = false;
      this.isFolderMinimized = false;
    },
    toggleGameSize() {
      this.isGameMinimized = !this.isGameMinimized;
    },
    closeGame() {
      this.isGameOpen = false;
      this.isGameMinimized = false;
    }
  }
};
</script>

<template>
  <div class="side-container">
    <i 
      class="bi" 
      :class="isFolderOpen ? 'bi-folder2-open' : 'bi-folder2'" 
      @click="isFolderOpen = !isFolderOpen"
    ></i>
  </div>

  <div v-if="isGameOpen">
    <ProjectWindow 
      :slug="slug"
      :is-minimized="isGameMinimized"
      @toggle-size="toggleGameSize"
      @close="closeGame"
    />
  </div>

  <section class="games container" v-if="isFolderOpen" :class="{ minimized: isFolderMinimized }">
    <div class="card">
      <div class="card-header">
        <h3 class="title">Games</h3>
        <ToolBar 
          :is-minimized="isFolderMinimized"
          @toggle-size="toggleFolderSize"
          @close="closeFolder"
        />
      </div>
      <div class="card-body" v-show="!isFolderMinimized">
        <div class="game">
          <label>Shoot The Crow</label>
          <i class="bi bi-bullseye" style="color: red" @click="openGame('shootthecrow')"></i>
        </div>
        <div class="game">
          <label>Guess The Number</label>
          <i class="bi bi-patch-question" style="color: green" @click="openGame('guessthenumber')"></i>
        </div>
        <div class="game">
          <label>The Pig Game</label>
          <i class="bi bi-dice-5-fill" style="color: mediumpurple" @click="openGame('piggame')"></i>
        </div>
      </div>
    </div>
  </section>
</template>

<style scoped>
.games.container {
  transition: all 0.5s ease-in-out;
}
.games.container.minimized {
  height: 50px;
  overflow: hidden;
  margin-top: auto;
}
</style>

修改关键点说明

  • ToolBar仅负责触发事件,不处理任何业务逻辑,适配不同父组件的需求
  • 所有显示状态通过响应式变量控制,完全遵循Vue数据流原则
  • 移除所有document.getElementById调用,避免DOM销毁后获取失败的问题
  • 打开ProjectWindow时自动关闭文件夹卡片,避免界面冲突
  • 最小化效果通过CSS类实现,过渡动画更流畅

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 01:25:08