fix(ui): 修复全屏对话框内容溢出问题

采用多层滚动容器设计解决全屏显示时超长内容溢出父容器边界的问题:

**核心改进:**
- 使用 Naive UI 的 NFlex 组件替代原生 div/NSpace,实现更规范的 flexbox 布局
- 建立正确的高度约束链:每层容器都设置 min-height: 0 和 overflow 控制
- 实现分层滚动策略:内层主滚动 + 外层兜底滚动

**滚动容器设计:**
1. 渲染/对比模式:MarkdownRenderer/TextDiffUI 自身提供滚动(overflow: auto)
2. 原文编辑模式:NInput textarea 会撑开容器,由 FullscreenDialog 提供兜底滚动
3. 外层约束容器:OutputDisplayFullscreen/Core 使用 overflow: hidden 防止意外滚动

**修改文件:**
- FullscreenDialog.vue: 使用 NFlex + overflow: auto 提供兜底滚动
- OutputDisplayFullscreen.vue: 使用 NFlex 包裹,修复 content 绑定为 internalContent
- OutputDisplayCore.vue: 主内容区从 NSpace 改为 NFlex,优化布局约束
- MarkdownRenderer.vue: 优化 NScrollbar 实现,移除冗余的高度和滚动样式
This commit is contained in:
linshen committed 2025-10-02 12:08:04 +08:00
1 parent c1bf0f2f8b
commit 7bf8059eb2
4 files changed
+47 -43

No files matched your search

@@ -1,5 +1,5 @@
<template>
<NModal
<NModal
v-model:show="localVisible"
preset="card"
:title="title"
@@ -10,15 +10,15 @@
transform-origin="center"
content-style="height: 100%; display: flex; flex-direction: column; min-height: 0;"
>
<div class="fullscreen-content">
<NFlex vertical style="height: 100%; min-height: 0; overflow: auto;">
<slot></slot>
</div>
</NFlex>
</NModal>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { NModal } from 'naive-ui'
import { NModal, NFlex } from 'naive-ui'
const props = defineProps({
modelValue: {
@@ -41,4 +41,4 @@ const localVisible = computed({
</script>
<style scoped>
/* Pure Naive UI implementation - no custom theme CSS needed */
</style>
</style>
@@ -1,9 +1,9 @@
<template>
<NScrollbar v-if="!disableInternalScroll" :style="{ height: '100%' }" :bordered="false" content-style="padding: 0; height: 100%; display: flex; flex-direction: column;">
<div ref="markdownContainer" class="markdown-content" :style="{ height: '100%',maxHeight: '100%' }">
<NScrollbar v-if="!disableInternalScroll" style="height: 100%; max-height: 100%;" :bordered="false">
<div ref="markdownContainer" class="markdown-content markdown-content--scrollable">
</div>
</NScrollbar>
<div v-else ref="markdownContainer" class="markdown-content" :style="{ height: '100%',maxHeight: '100%' }">
<div v-else ref="markdownContainer" class="markdown-content" style="height: 100%; max-height: 100%; overflow-y: auto;">
</div>
</template>
@@ -287,10 +287,11 @@ onMounted(renderMarkdown);
overflow-wrap: break-word;
hyphens: auto;
/* Pure Naive UI theme - remove custom CSS variables */
/* 填满容器并处理滚动 */
height: 100%;
overflow-y: auto;
padding: 0.75rem; /* 提供合适的内边距,与其他组件保持一致 */
}
/* 当使用 NScrollbar 时,不需要自己的滚动条 */
.markdown-content--scrollable {
/* 隐藏滚动条但保持可滚动 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
@@ -1,12 +1,12 @@
<template>
<NCard
<NCard
:bordered="false"
class="output-display-core h-full max-height: 100% "
content-style="padding: 0; height: 100%; max-height: 100%;"
content-style="padding: 0; height: 100%; max-height: 100%; display: flex; flex-direction: column; overflow: hidden;"
>
<NFlex vertical style="height: 100%;">
<NFlex vertical style="height: 100%; min-height: 0; overflow: hidden;">
<!-- 统一顶层工具栏 -->
<NFlex v-if="hasToolbar" justify="space-between" align="center">
<NFlex v-if="hasToolbar" justify="space-between" align="center" style="flex: 0 0 auto;">
<!-- 左侧:视图控制按钮组 -->
<NButtonGroup>
<NButton
@@ -103,14 +103,14 @@
</NCollapse>
</NFlex>
<!-- 主要内容区域 -->
<NFlex vertical style="flex: 1; min-height: 0; max-height: 100%;">
<NFlex vertical style="flex: 1; min-height: 0; max-height: 100%; overflow: hidden;">
<!-- 对比模式 -->
<TextDiffUI v-if="internalViewMode === 'diff' && content && originalContent"
<TextDiffUI v-if="internalViewMode === 'diff' && content && originalContent"
:originalText="originalContent"
:optimizedText="content"
:compareResult="compareResult"
class="w-full"
style="height: 100%;"
style="height: 100%; min-height: 0; overflow: auto;"
/>
<!-- 原文模式 -->
@@ -121,21 +121,21 @@
type="textarea"
:placeholder="placeholder"
:autosize="{ minRows: 10 }"
style="height: 100%;"
style="height: 100%; min-height: 0;"
/>
<!-- 渲染模式(默认) -->
<NSpace v-else
style="height: 100%;max-height: 100%;"
item-style="height: 100%;max-height: 100%;"
:align="displayContent ? 'start' : 'center'"
:justify="displayContent ? 'start' : 'center'"
<NFlex v-else
vertical
:align="displayContent ? 'stretch' : 'center'"
:justify="displayContent ? 'start' : 'center'"
style="flex: 1; min-height: 0; overflow: hidden;"
>
<MarkdownRenderer
v-if="displayContent"
:content="displayContent"
:streaming="streaming"
style="height: 100%;max-height: 100%;"
style="flex: 1; min-height: 0; overflow: auto;"
/>
<NEmpty
v-else-if="!loading && !streaming"
@@ -144,7 +144,7 @@
style="height: 100%;"
/>
<NText v-else class="ml-2">{{ placeholder || t('common.loading') }}</NText>
</NSpace>
</NFlex>
</NFlex>
</NFlex>
@@ -1,27 +1,30 @@
<template>
<FullscreenDialog v-model="internalVisible" :title="title || t('common.content')">
<OutputDisplayCore
ref="coreDisplayRef"
:content="content"
:originalContent="originalContent"
:reasoning="reasoning"
:mode="mode"
:reasoningMode="reasoningMode"
:enabledActions="coreEnabledActions"
height="100%"
:placeholder="placeholder"
:loading="loading"
:streaming="streaming"
:compareService="compareService"
@update:content="handleContentUpdate"
@copy="handleCopy"
/>
<NFlex vertical style="flex: 1; min-height: 0; overflow: hidden;">
<OutputDisplayCore
ref="coreDisplayRef"
:content="internalContent"
:originalContent="originalContent"
:reasoning="reasoning"
:mode="mode"
:reasoningMode="reasoningMode"
:enabledActions="coreEnabledActions"
height="100%"
:placeholder="placeholder"
:loading="loading"
:streaming="streaming"
:compareService="compareService"
@update:content="handleContentUpdate"
@copy="handleCopy"
/>
</NFlex>
</FullscreenDialog>
</template>
<script setup lang="ts">
import { computed, ref, watch, inject, nextTick, type Ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { NFlex } from 'naive-ui'
import FullscreenDialog from './FullscreenDialog.vue'
import OutputDisplayCore from './OutputDisplayCore.vue'
import type { AppServices } from '../types/services';
@@ -129,4 +132,4 @@ const handleCopy = (content: string, type: 'content' | 'reasoning' | 'all') => {
emit('copy', content, type)
}
</script>
</script>