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
相关产品推荐
相关产品推荐

