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

Vue3+Quasar点击q-expansion-item仅隐藏当前项头部预览方法

问题原因

代码中使用单个全局isOpen响应式变量控制所有q-expansion-item的头部预览显隐,所有项的预览判断都绑定了这同一个变量。任意一个展开项触发更新事件时都会修改这个全局变量,最终导致所有项的预览同步隐藏。

修复方案

优先选择方案1实现,代码改动量最小,不需要额外维护展开状态。

方案1:使用组件插槽自带的展开状态(推荐)

q-expansion-item的header是作用域插槽,会直接暴露当前项自身的opened布尔状态,代表当前项是否处于展开状态,直接用这个状态控制当前项的预览显隐即可,不需要额外监听事件、维护全局状态。
核心修改点:

  • 给header插槽添加作用域参数<template #header="{ opened }">获取当前项的展开状态
  • 预览标签的v-if判断改为v-if="!opened",仅当前项折叠时显示预览
  • 删除多余的全局isOpen变量、handleOpenClose方法、@update:model-value事件绑定

修复后的完整代码:

<template>
  <q-card flat class="full-width" style="max-width: 1200px">
    <q-card-section>
      <q-list bordered class="rounded-borders">
        <div v-for="(thread, index) in threads" :key="thread.threadId">
          <q-expansion-item
            clickable
            :content-inset-level="1"
          >
            <!-- 从作用域插槽拿到当前项自身的展开状态 -->
            <template #header="{ opened }">
              <q-item-section top>
                <q-item-label lines="1">
                  <span class="text-weight-medium q-mr-sm">
                    {{ thread.author.name }}
                  </span>
                  <span class="text-grey-8 text-sm">
                    {{
                      date.formatDate(
                        new Date(thread.createdAt.seconds * 1000),
                        'Do MMMM hh:mmA'
                      )
                    }}
                  </span>
                </q-item-label>
                <!-- 仅当前项未展开时显示预览 -->
                <q-item-label v-if="!opened" caption lines="1">
                  {{ stripHTML(thread.content) }}
                </q-item-label>
              </q-item-section>
            </template>
            <template #default>
              <q-card>
                <!-- eslint-disable-next-line vue/no-v-html -->
                <q-card-section v-html="thread.content" />
              </q-card>
            </template>
          </q-expansion-item>
          <q-separator v-if="threads && index != threads?.length - 1" />
        </div>
      </q-list>
    </q-card-section>
  </q-card>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { date } from 'quasar';

// 模拟数据
const threads = ref([
  {
    threadId: '6bXb0tfCZWoNVfFEztIS',
    ticketId: '9rgz013ahc2Aqx9C2UxG',
    author: {
      name: 'Ben Bob',
      firstName: 'Ben',
      lastName: 'Bob',
      photoURL: '__vue_devtool_undefined__',
      type: 'END_USER',
    },
    createdAt: { seconds: 1656573148, nanoseconds: 38000000 },
    content: 'The first thread',
  },
  {
    threadId: 'MTM4qvzcRKQ2eZ2crpQB',
    ticketId: '9rgz013ahc2Aqx9C2UxG',
    author: {
      name: 'Ben Bob',
      firstName: 'Ben',
      lastName: 'Bob',
      photoURL: '__vue_devtool_undefined__',
      type: 'END_USER',
    },
    createdAt: { seconds: 1656573666, nanoseconds: 250000000 },
    content: 'The second thread',
  },
  {
    threadId: 'Q9xf9PFmTzs6X4LYSXXh',
    ticketId: '9rgz013ahc2Aqx9C2UxG',
    author: {
      name: 'Ben Bob',
      firstName: 'Ben',
      lastName: 'Bob',
      photoURL: null,
      type: 'END_USER',
    },
    createdAt: { seconds: 1656573990, nanoseconds: 262000000 },
    content: 'The third thread',
  },
]);

const stripHTML = (html: string) => {
  let doc = new DOMParser().parseFromString(html, 'text/html');
  doc.body
    .querySelectorAll('br, li, div')
    .forEach((br) => br.after(doc.createTextNode(' ')));
  return doc.body.textContent || '';
};
</script>

方案2:手动维护展开项状态(适合自定义展开逻辑场景)

如果后续需要实现默认展开指定项、手风琴模式(同一时间仅允许展开一个项)这类自定义逻辑,可以手动维护已展开项的ID集合,判断当前线程ID是否在集合中即可控制预览显隐。
以手风琴单开模式为例,核心代码改动:

// 存储当前展开的线程ID,null代表无展开项
const openedThreadId = ref<string | null>(null)

const handleOpenClose = (isShowing: boolean, threadId: string) => {
  openedThreadId.value = isShowing ? threadId : null
}

模板部分对应修改:

<q-expansion-item
  clickable
  :content-inset-level="1"
  @update:model-value="(val) => handleOpenClose(val, thread.threadId)"
>
  <template #header>
    <q-item-section top>
      <!-- 头部作者、时间逻辑保持不变 -->
      <q-item-label v-if="openedThreadId !== thread.threadId" caption lines="1">
        {{ stripHTML(thread.content) }}
      </q-item-label>
    </q-item-section>
  </template>
  <!-- 内容部分逻辑保持不变 -->
</q-expansion-item>

如果需要支持同时展开多个项,把openedThreadId换成Set类型存储所有已展开的ID即可,展开时添加对应ID,折叠时删除对应ID,判断时检查当前ID是否在Set中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 00:45:44