feat(editor): 添加图片预览功能并支持缩放
- 在编辑器点击图片时显示全屏预览弹窗 - 支持通过鼠标滚轮缩放预览图片(0.1-5倍) - 使用Teleport将弹窗渲染到body避免样式冲突 - 添加淡入动画和关闭按钮提升用户体验
This commit is contained in:
parent
84af4bd798
commit
3373abe563
|
|
@ -1,12 +1,29 @@
|
|||
<template>
|
||||
<div class="live-editor">
|
||||
<div class="live-editor" @click="handleEditorClick">
|
||||
<EditorToolbar @action="handleToolbarAction" />
|
||||
<codemirror v-model="code" placeholder="请输入 Markdown 内容..." :style="{ height: '0', flex: 1, fontSize: 'var(--editor-font-size)', color: 'var(--primary-color)' }" :autofocus="true" :indent-with-tab="true" :tab-size="2" :extensions="extensions" @ready="handleReady" @change="handleChange" />
|
||||
|
||||
<!-- 图片预览弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="previewImage.visible" class="image-preview-modal" @click="closePreview" @wheel.prevent="handlePreviewWheel">
|
||||
<div class="preview-controls">
|
||||
<span class="close-btn" @click.stop="closePreview" title="关闭">×</span>
|
||||
</div>
|
||||
<div class="preview-content" @click.stop>
|
||||
<img
|
||||
:src="previewImage.url"
|
||||
:style="{ transform: `scale(${previewImage.scale})` }"
|
||||
class="preview-image"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onUnmounted, ref, watch, shallowRef } from "vue";
|
||||
import { computed, onUnmounted, ref, watch, shallowRef, reactive } from "vue";
|
||||
import { Codemirror } from "vue-codemirror";
|
||||
import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
|
||||
import { languages } from "@codemirror/language-data";
|
||||
|
|
@ -306,6 +323,34 @@ const executeAction = (view, action, payload) => {
|
|||
}
|
||||
};
|
||||
|
||||
// 图片预览相关逻辑
|
||||
const previewImage = reactive({
|
||||
visible: false,
|
||||
url: "",
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
});
|
||||
|
||||
const handleEditorClick = (e) => {
|
||||
if (e.target.classList.contains("cm-md-image-preview")) {
|
||||
previewImage.url = e.target.src;
|
||||
previewImage.scale = 1;
|
||||
previewImage.rotate = 0;
|
||||
previewImage.visible = true;
|
||||
}
|
||||
};
|
||||
|
||||
const closePreview = () => {
|
||||
previewImage.visible = false;
|
||||
};
|
||||
|
||||
const handlePreviewWheel = (e) => {
|
||||
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
||||
const newScale = previewImage.scale + delta;
|
||||
// 限制缩放范围 0.1 - 5
|
||||
previewImage.scale = Math.min(Math.max(0.1, newScale), 5);
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
if (typeof detachPaste === "function") detachPaste();
|
||||
if (removeActionListener) removeActionListener();
|
||||
|
|
@ -333,3 +378,70 @@ onUnmounted(() => {
|
|||
color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 图片预览弹窗样式 - 全局生效以支持 Teleport */
|
||||
.image-preview-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.preview-controls {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
right: 40px;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 48px;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
transition: all 0.2s;
|
||||
display: block;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #fff;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
object-fit: contain;
|
||||
transition: transform 0.1s ease-out;
|
||||
cursor: grab;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.preview-image:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue