diff --git a/CHANGELOG.md b/CHANGELOG.md index 2252d1bf57689b628fe39bd43b1a7c6c36410c82..9690bf48d35e8493717706679b4d1dfd47e157cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - 新增代码编辑器获取编辑器元素及主题方法,并更新位置计算逻辑的注释 - markdown新增支持获取编辑器元素和编辑器主题的方法 +- 新增markdown编辑器支持在预览区可以预览单个图片 ### Changed diff --git a/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.scss b/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.scss index 988245d36595d572f1a6887e103f04326b9c3c17..9fbfb795c5fcb61e2fc4852ed05cfdbe4b23488e 100644 --- a/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.scss +++ b/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.scss @@ -15,6 +15,11 @@ $markdown: ( border-radius-manual-toolbar-item: getCssVar('border-radius', small), border-manual-toolbar-fullscreen-footer: 1px solid getCssVar(color, border), + // Other + 'img-preview-top': -32px, + 'img-preview-right': -36px, + 'img-preview-width': 32px, + 'img-preview-height': 32px, ); @@ -96,6 +101,35 @@ $markdown: ( } } } + + @include e('img-preview') { + // 图片预览样式,默认需不占位 + height: 0; + + // 图片预览态样式 + .el-image-viewer__wrapper { + width: 90%; + height: 90%; + margin: auto; + } + + .el-image-viewer__mask { + position: fixed; + width: 100vw; + height: 100vh; + cursor: zoom-out; + opacity: 0.7; + } + + // 图片预览态关闭按钮样式 + .el-image-viewer__close { + top: getCssVar('markdown', 'img-preview-top'); + right: getCssVar('markdown', 'img-preview-right'); + width: getCssVar('markdown', 'img-preview-width'); + height: getCssVar('markdown', 'img-preview-height'); + } + } + @include b(markdown-cherry) { width: getCssVar('markdown', 'width'); outline: none; @@ -116,6 +150,11 @@ $markdown: ( .cherry-previewer{ border-top: 1px solid getCssVar(color, border); + + // 调整预览区图片鼠标移入样式 + img { + cursor: zoom-in; + } } .cherry-sidebar{ diff --git a/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.tsx b/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.tsx index 7b2b101d02aff5cd67e6f833bc8106635c297e42..582426833f439a008d9041b951ea4f57c86f77cf 100644 --- a/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.tsx +++ b/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.tsx @@ -18,8 +18,9 @@ import { import { createUUID } from 'qx-util'; import Cherry from 'cherry-markdown'; import { MarkDownEditorController } from '../markdown-editor.controller'; -import './ibiz-markdown-editor.scss'; import { initCustomMenu } from './custom-menu'; +import { useImgPreviewRender } from './render-util'; +import './ibiz-markdown-editor.scss'; /** * Markdown编辑框 @@ -55,6 +56,8 @@ const IBizMarkDown: any = defineComponent({ const [AIMenu] = initCustomMenu(c); + const { onMDEditorCreated, renderImgPreview } = useImgPreviewRender(ns); + // 请求头 const uploadHeaders = ibiz.util.file.getUploadHeaders(); const headers: Ref = ref({ ...uploadHeaders }); @@ -498,6 +501,7 @@ const IBizMarkDown: any = defineComponent({ ); parentElement?.appendChild(span); c?.setMDEditor(editor); + onMDEditorCreated(editor); }); }; @@ -675,6 +679,7 @@ const IBizMarkDown: any = defineComponent({ setCherryContent, renderHeader, renderFooter, + renderImgPreview, }; }, render() { @@ -690,6 +695,7 @@ const IBizMarkDown: any = defineComponent({ > {this.renderHeader()} {this.renderFooter()} + {this.renderImgPreview()}
string }): { + renderImgPreview: () => VNode; + onMDEditorCreated: (mdeditor: IParams) => void; +} { + // 预览图片当前地址 + const imgPreviewUrl = ref(''); + // 预览图片地址列表(控制弹窗显示/隐藏) + const imgPreviewUrlList = ref([]); + // 图片预览组件 Ref + const imgPreviewRef = ref(); + // Markdown 预览区 DOM + let mdPreviewerDom: HTMLElement | null = null; + + /** + * 打开图片预览弹窗 + * @param url 图片地址 + */ + const openImgPreview = async (url: string): Promise => { + imgPreviewUrl.value = url; + // 打开预览图片模态 + imgPreviewUrlList.value = [url]; + await nextTick(); + if (imgPreviewRef.value) { + const { container } = imgPreviewRef.value.$refs; + if (container) { + container.children[0]?.click(); + } + } + }; + + /** + * 处理键盘事件(ESC 关闭预览) + * @param event 键盘事件对象 + */ + const handleKeyPress = (event: KeyboardEvent): void => { + if (event.key === 'Escape' || event.keyCode === 27) { + event.stopPropagation(); + event.preventDefault(); + + // 关闭预览弹窗 + imgPreviewUrlList.value = []; + // 解绑键盘事件 + // eslint-disable-next-line no-use-before-define + removeKeydownListener(); + } + }; + + /** + * 绑定键盘事件(预览弹窗显示时调用) + */ + const addKeydownListener = async (): Promise => { + await nextTick(); + const container = imgPreviewRef.value?.$refs.container; + if (!container) return; + + const imgViewerWrapper = container.querySelector( + '.el-image-viewer__wrapper', + ) as HTMLElement; + imgViewerWrapper?.addEventListener('keydown', handleKeyPress); + }; + + /** + * 解绑键盘事件 + */ + const removeKeydownListener = (): void => { + const container = imgPreviewRef.value?.$refs.container; + if (!container) return; + + const imgViewerWrapper = container.querySelector( + '.el-image-viewer__wrapper', + ) as HTMLElement; + imgViewerWrapper?.removeEventListener('keydown', handleKeyPress); + }; + + /** + * 处理 Markdown 预览区点击(仅响应图片点击) + * @param event 点击事件对象 + */ + const handlePreviewerImgClick = (event: MouseEvent): void => { + const imgUrl = (event?.target as IParams)?.src; + if (isElement(event?.target, 'IMG') && imgUrl) { + openImgPreview(imgUrl); + } + }; + + /** + * 渲染图片预览组件(Element Plus ElImage) + */ + const renderImgPreview = (): VNode => { + return ( + + ); + }; + + /** + * Markdown 编辑器创建完成回调 + * @param mdeditor 编辑器实例(包含预览区 DOM) + */ + const onMDEditorCreated = (mdeditor: IParams): void => { + mdPreviewerDom = mdeditor?.previewer?.previewerBubble?.previewerDom || null; + mdPreviewerDom?.addEventListener('click', handlePreviewerImgClick); + }; + + // 组件卸载时清理资源 + onBeforeUnmount(() => { + // 解绑预览区点击事件 + if (mdPreviewerDom) { + mdPreviewerDom.removeEventListener('click', handlePreviewerImgClick); + mdPreviewerDom = null; + } + // 解绑键盘事件 + removeKeydownListener(); + }); + + return { + renderImgPreview, + onMDEditorCreated, + }; +}