fix: 调整工作台文件预览全屏交互
This commit is contained in:
parent
94e16802d1
commit
fa5a2fa1ae
@ -53,6 +53,7 @@
|
|||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
||||||
|
- 调整前端工作台文件预览交互:恢复默认侧边/弹窗预览,并新增显式“全屏预览”入口;全屏模式下由预览内容直接覆盖整页,仅保留右上角悬浮关闭按钮
|
||||||
- 兼容旧版已安装的内置 `reporter` 技能记录:`update_builtin_skill` 现在会识别由 `system` 或 `builtin-system` 管理的历史记录,避免更新时误报“技能 `reporter` 不是内置 skill”
|
- 兼容旧版已安装的内置 `reporter` 技能记录:`update_builtin_skill` 现在会识别由 `system` 或 `builtin-system` 管理的历史记录,避免更新时误报“技能 `reporter` 不是内置 skill”
|
||||||
- 调整沙盒 user-data 目录隔离策略:`workspace` 改为共享目录 `saves/threads/shared/workspace`,`uploads/outputs` 继续保持 thread 级隔离;同时更新 thread artifact 权限校验、viewer 文件系统列举逻辑,以及对应的 router/E2E 测试
|
- 调整沙盒 user-data 目录隔离策略:`workspace` 改为共享目录 `saves/threads/shared/workspace`,`uploads/outputs` 继续保持 thread 级隔离;同时更新 thread artifact 权限校验、viewer 文件系统列举逻辑,以及对应的 router/E2E 测试
|
||||||
- 重构聊天接口请求模型:流式与非流式聊天统一使用 `query + agent_config_id` 请求体,并移除路径中的 `agent_id`;同时修复非流式接口实际误走流式执行链路的问题,改为调用 `invoke_messages` 一次性执行,并补充对应测试
|
- 重构聊天接口请求模型:流式与非流式聊天统一使用 `query + agent_config_id` 请求体,并移除路径中的 `agent_id`;同时修复非流式接口实际误走流式执行链路的问题,改为调用 `invoke_messages` 一次性执行,并补充对应测试
|
||||||
|
|||||||
@ -136,6 +136,9 @@
|
|||||||
<button class="modal-action-btn" @click="downloadFile(currentFile)" title="下载">
|
<button class="modal-action-btn" @click="downloadFile(currentFile)" title="下载">
|
||||||
<Download :size="18" />
|
<Download :size="18" />
|
||||||
</button>
|
</button>
|
||||||
|
<button class="modal-action-btn" @click="openFullscreenPreview" title="全屏预览">
|
||||||
|
<Maximize2 :size="18" />
|
||||||
|
</button>
|
||||||
<button class="modal-action-btn" @click="closePreview" title="关闭预览">
|
<button class="modal-action-btn" @click="closePreview" title="关闭预览">
|
||||||
<X :size="18" />
|
<X :size="18" />
|
||||||
</button>
|
</button>
|
||||||
@ -207,7 +210,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 文件内容 Modal -->
|
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:open="modalVisible"
|
v-model:open="modalVisible"
|
||||||
width="800px"
|
width="800px"
|
||||||
@ -246,13 +248,21 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
v-if="currentFile"
|
||||||
class="modal-action-btn"
|
class="modal-action-btn"
|
||||||
@click="downloadFile(currentFile)"
|
@click="downloadFile(currentFile)"
|
||||||
v-if="currentFile"
|
|
||||||
title="下载"
|
title="下载"
|
||||||
>
|
>
|
||||||
<Download :size="18" />
|
<Download :size="18" />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="currentFile"
|
||||||
|
class="modal-action-btn"
|
||||||
|
@click="openFullscreenPreview"
|
||||||
|
title="全屏预览"
|
||||||
|
>
|
||||||
|
<Maximize2 :size="18" />
|
||||||
|
</button>
|
||||||
<button class="modal-action-btn" @click="closePreview" title="关闭">
|
<button class="modal-action-btn" @click="closePreview" title="关闭">
|
||||||
<X :size="18" />
|
<X :size="18" />
|
||||||
</button>
|
</button>
|
||||||
@ -290,9 +300,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<pre v-if="Array.isArray(currentFile?.content)">{{
|
<pre v-if="Array.isArray(currentFile?.content)">{{ formatContent(currentFile.content) }}</pre>
|
||||||
formatContent(currentFile.content)
|
|
||||||
}}</pre>
|
|
||||||
<pre
|
<pre
|
||||||
v-else-if="isCodePreview && typeof currentFile?.content === 'string'"
|
v-else-if="isCodePreview && typeof currentFile?.content === 'string'"
|
||||||
:class="['file-content-pre', 'code-highlight', codeThemeClass]"
|
:class="['file-content-pre', 'code-highlight', codeThemeClass]"
|
||||||
@ -304,6 +312,88 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
<Teleport to="body">
|
||||||
|
<div v-if="fullscreenPreviewVisible && currentFile" class="fullscreen-preview-overlay">
|
||||||
|
<div class="fullscreen-preview-actions">
|
||||||
|
<div v-if="isHtmlFile" class="preview-mode-switch fullscreen-preview-switch">
|
||||||
|
<button
|
||||||
|
class="preview-mode-btn"
|
||||||
|
:class="{ active: htmlPreviewMode === 'render' }"
|
||||||
|
@click="htmlPreviewMode = 'render'"
|
||||||
|
title="预览"
|
||||||
|
>
|
||||||
|
<Globe :size="16" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="preview-mode-btn"
|
||||||
|
:class="{ active: htmlPreviewMode === 'source' }"
|
||||||
|
@click="htmlPreviewMode = 'source'"
|
||||||
|
title="源码"
|
||||||
|
>
|
||||||
|
<Code2 :size="16" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="modal-action-btn fullscreen-action-btn"
|
||||||
|
@click="closeFullscreenPreview"
|
||||||
|
title="关闭"
|
||||||
|
>
|
||||||
|
<X :size="18" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="fullscreen-preview-content">
|
||||||
|
<div class="file-content fullscreen-file-content">
|
||||||
|
<template v-if="currentFile?.previewType === 'image' && currentFile?.previewUrl">
|
||||||
|
<div class="image-preview-wrapper fullscreen-image-preview-wrapper">
|
||||||
|
<img :src="currentFile.previewUrl" :alt="currentFilePath" class="image-preview" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="currentFile?.previewType === 'pdf' && currentFile?.previewUrl">
|
||||||
|
<iframe
|
||||||
|
:src="currentFile.previewUrl"
|
||||||
|
class="pdf-preview fullscreen-embed-preview"
|
||||||
|
:title="currentFilePath"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="isHtmlFile && htmlPreviewMode === 'render'">
|
||||||
|
<iframe
|
||||||
|
class="html-preview fullscreen-embed-preview"
|
||||||
|
:srcdoc="formatContent(currentFile?.content)"
|
||||||
|
:title="currentFilePath"
|
||||||
|
sandbox=""
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="isMarkdown">
|
||||||
|
<MdPreview
|
||||||
|
class="flat-md-preview"
|
||||||
|
:modelValue="formatContent(currentFile?.content)"
|
||||||
|
:theme="theme"
|
||||||
|
previewTheme="github"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="currentFile?.supported === false">
|
||||||
|
<div class="unsupported-preview fullscreen-unsupported-preview">
|
||||||
|
{{ currentFile?.message || '当前文件暂不支持预览,请下载后查看' }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<pre v-if="Array.isArray(currentFile?.content)">{{
|
||||||
|
formatContent(currentFile.content)
|
||||||
|
}}</pre>
|
||||||
|
<pre
|
||||||
|
v-else-if="isCodePreview && typeof currentFile?.content === 'string'"
|
||||||
|
:class="['file-content-pre', 'code-highlight', codeThemeClass]"
|
||||||
|
><code class="hljs" v-html="highlightedCodeContent"></code></pre>
|
||||||
|
<pre v-else-if="typeof currentFile?.content === 'string'" class="file-content-pre">{{
|
||||||
|
currentFile.content
|
||||||
|
}}</pre>
|
||||||
|
<pre v-else>{{ JSON.stringify(currentFile, null, 2) }}</pre>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -316,6 +406,7 @@ import {
|
|||||||
Download,
|
Download,
|
||||||
FolderCode,
|
FolderCode,
|
||||||
Globe,
|
Globe,
|
||||||
|
Maximize2,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
X
|
X
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
@ -380,6 +471,7 @@ const INLINE_PREVIEW_MIN_WIDTH = 920
|
|||||||
const panelRef = ref(null)
|
const panelRef = ref(null)
|
||||||
const activeTab = ref('files')
|
const activeTab = ref('files')
|
||||||
const modalVisible = ref(false)
|
const modalVisible = ref(false)
|
||||||
|
const fullscreenPreviewVisible = ref(false)
|
||||||
const currentFile = ref(null)
|
const currentFile = ref(null)
|
||||||
const currentFilePath = ref('')
|
const currentFilePath = ref('')
|
||||||
const htmlPreviewMode = ref('render')
|
const htmlPreviewMode = ref('render')
|
||||||
@ -717,6 +809,7 @@ const onFileSelect = async (nextSelectedKeys, { node }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closePreview = () => {
|
const closePreview = () => {
|
||||||
|
fullscreenPreviewVisible.value = false
|
||||||
revokeCurrentPreviewUrl()
|
revokeCurrentPreviewUrl()
|
||||||
modalVisible.value = false
|
modalVisible.value = false
|
||||||
currentFile.value = null
|
currentFile.value = null
|
||||||
@ -725,6 +818,15 @@ const closePreview = () => {
|
|||||||
selectedKeys.value = []
|
selectedKeys.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openFullscreenPreview = () => {
|
||||||
|
if (!currentFile.value) return
|
||||||
|
fullscreenPreviewVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeFullscreenPreview = () => {
|
||||||
|
fullscreenPreviewVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const downloadFile = async (fileItem) => {
|
const downloadFile = async (fileItem) => {
|
||||||
if (!props.threadId || !fileItem?.path) return
|
if (!props.threadId || !fileItem?.path) return
|
||||||
|
|
||||||
@ -847,6 +949,7 @@ onUnmounted(() => {
|
|||||||
window.removeEventListener('pointercancel', stopResize)
|
window.removeEventListener('pointercancel', stopResize)
|
||||||
document.body.style.cursor = ''
|
document.body.style.cursor = ''
|
||||||
document.body.style.userSelect = ''
|
document.body.style.userSelect = ''
|
||||||
|
document.body.style.overflow = ''
|
||||||
revokeCurrentPreviewUrl()
|
revokeCurrentPreviewUrl()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -869,6 +972,14 @@ watch(useInlinePreview, (isInline) => {
|
|||||||
|
|
||||||
modalVisible.value = !isInline
|
modalVisible.value = !isInline
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch([modalVisible, fullscreenPreviewVisible], ([modalOpen, fullscreenOpen]) => {
|
||||||
|
document.body.style.overflow = fullscreenOpen ? 'hidden' : ''
|
||||||
|
|
||||||
|
if (fullscreenOpen && modalOpen) {
|
||||||
|
modalVisible.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
@ -1305,6 +1416,66 @@ watch(useInlinePreview, (isInline) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullscreen-preview-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1200;
|
||||||
|
background: var(--gray-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-preview-actions {
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-preview-switch {
|
||||||
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-action-btn {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 1px solid var(--gray-200);
|
||||||
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.14);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-preview-content {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-file-content {
|
||||||
|
height: 100vh;
|
||||||
|
max-height: none;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-image-preview-wrapper {
|
||||||
|
min-height: calc(100vh - 48px);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-embed-preview {
|
||||||
|
min-height: calc(100vh - 48px);
|
||||||
|
height: calc(100vh - 48px);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-unsupported-preview {
|
||||||
|
min-height: calc(100vh - 48px);
|
||||||
|
}
|
||||||
|
|
||||||
.file-content-pre.code-highlight {
|
.file-content-pre.code-highlight {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--gray-25);
|
background: var(--gray-25);
|
||||||
@ -1321,6 +1492,10 @@ watch(useInlinePreview, (isInline) => {
|
|||||||
min-height: calc(80vh - 40px);
|
min-height: calc(80vh - 40px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullscreen-file-content .file-content-pre.code-highlight code {
|
||||||
|
min-height: calc(100vh - 48px);
|
||||||
|
}
|
||||||
|
|
||||||
.image-preview-wrapper {
|
.image-preview-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -1335,6 +1510,10 @@ watch(useInlinePreview, (isInline) => {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullscreen-file-content .image-preview {
|
||||||
|
max-height: calc(100vh - 48px);
|
||||||
|
}
|
||||||
|
|
||||||
.pdf-preview {
|
.pdf-preview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: calc(80vh - 40px);
|
min-height: calc(80vh - 40px);
|
||||||
@ -1443,7 +1622,6 @@ watch(useInlinePreview, (isInline) => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal 样式优化 - shadcn 风格 */
|
|
||||||
:deep(.ant-modal) {
|
:deep(.ant-modal) {
|
||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user