mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-09-13 07:43:43 +08:00
优化富文本编辑器:1.只读模式隐藏工具栏;2.点击图片可弹窗预览。
This commit is contained in:
parent
8284a87d36
commit
39c7d5c5f5
@ -24,6 +24,32 @@
|
||||
@text-change="(e: any) => $emit('update:modelValue', content)"
|
||||
/>
|
||||
</div>
|
||||
<!-- 图片预览对话框 -->
|
||||
<el-dialog v-model="previewDialogVisible" title="图片预览" width="80%" append-to-body>
|
||||
<div class="image-preview-container">
|
||||
<el-image
|
||||
v-if="currentPreviewImage"
|
||||
:src="currentPreviewImage"
|
||||
fit="contain"
|
||||
style="width: 100%; max-height: 70vh;"
|
||||
/>
|
||||
<div v-if="imageUrlList.length > 1" class="image-preview-nav">
|
||||
<el-button
|
||||
:disabled="currentImageIndex === 0"
|
||||
@click="prevImage"
|
||||
icon="ArrowLeft"
|
||||
circle
|
||||
/>
|
||||
<span class="image-counter">{{ currentImageIndex + 1 }} / {{ imageUrlList.length }}</span>
|
||||
<el-button
|
||||
:disabled="currentImageIndex === imageUrlList.length - 1"
|
||||
@click="nextImage"
|
||||
icon="ArrowRight"
|
||||
circle
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -32,6 +58,7 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
import { QuillEditor, Quill } from '@vueup/vue-quill';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { globalHeaders } from '@/utils/request';
|
||||
import { nextTick, onMounted, computed } from 'vue';
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
|
||||
@ -59,43 +86,53 @@ const upload = reactive<UploadOption>({
|
||||
const quillEditorRef = ref();
|
||||
const uploadRef = ref<HTMLDivElement>();
|
||||
|
||||
const options = ref<any>({
|
||||
theme: 'snow',
|
||||
bounds: document.body,
|
||||
debug: 'warn',
|
||||
modules: {
|
||||
// 工具栏配置
|
||||
toolbar: {
|
||||
container: [
|
||||
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
|
||||
['blockquote', 'code-block'], // 引用 代码块
|
||||
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
|
||||
[{ indent: '-1' }, { indent: '+1' }], // 缩进
|
||||
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
|
||||
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
|
||||
[{ align: [] }], // 对齐方式
|
||||
['clean'], // 清除文本格式
|
||||
['link', 'image', 'video'] // 链接、图片、视频
|
||||
],
|
||||
handlers: {
|
||||
image: (value: boolean) => {
|
||||
if (value) {
|
||||
// 调用element图片上传
|
||||
uploadRef.value.click();
|
||||
} else {
|
||||
Quill.format('image', true);
|
||||
// 图片预览相关
|
||||
const previewDialogVisible = ref(false);
|
||||
const imageUrlList = ref<string[]>([]);
|
||||
const currentImageIndex = ref(0);
|
||||
const currentPreviewImage = computed(() => {
|
||||
return imageUrlList.value[currentImageIndex.value] || '';
|
||||
});
|
||||
|
||||
const options = computed(() => {
|
||||
return {
|
||||
theme: 'snow',
|
||||
bounds: document.body,
|
||||
debug: 'warn',
|
||||
modules: {
|
||||
// 工具栏配置 - 只读模式下隐藏工具栏
|
||||
toolbar: props.readOnly ? false : {
|
||||
container: [
|
||||
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
|
||||
['blockquote', 'code-block'], // 引用 代码块
|
||||
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
|
||||
[{ indent: '-1' }, { indent: '+1' }], // 缩进
|
||||
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
|
||||
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
|
||||
[{ align: [] }], // 对齐方式
|
||||
['clean'], // 清除文本格式
|
||||
['link', 'image', 'video'] // 链接、图片、视频
|
||||
],
|
||||
handlers: {
|
||||
image: (value: boolean) => {
|
||||
if (value) {
|
||||
// 调用element图片上传
|
||||
uploadRef.value.click();
|
||||
} else {
|
||||
Quill.format('image', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder: '请输入内容',
|
||||
readOnly: props.readOnly
|
||||
},
|
||||
placeholder: '请输入内容',
|
||||
readOnly: props.readOnly
|
||||
};
|
||||
});
|
||||
|
||||
const styles = computed(() => {
|
||||
const style: any = {};
|
||||
let style: any = {};
|
||||
if (props.minHeight) {
|
||||
style.minHeight = `${props.minHeight}px`;
|
||||
}
|
||||
@ -111,24 +148,139 @@ watch(
|
||||
(v: string) => {
|
||||
if (v !== content.value) {
|
||||
content.value = v || '<p></p>';
|
||||
// 内容更新后,添加图片点击事件
|
||||
nextTick(() => {
|
||||
initImageClickEvent();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听内容变化,初始化图片点击事件
|
||||
watch(
|
||||
() => content.value,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
initImageClickEvent();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// 监听只读模式变化,确保在只读模式下也能预览图片
|
||||
watch(
|
||||
() => props.readOnly,
|
||||
(newVal) => {
|
||||
// 更新Quill编辑器的只读状态
|
||||
if (quillEditorRef.value) {
|
||||
const quill = toRaw(quillEditorRef.value).getQuill();
|
||||
if (quill) {
|
||||
quill.enable(!newVal);
|
||||
}
|
||||
}
|
||||
// 重新初始化图片点击事件
|
||||
nextTick(() => {
|
||||
initImageClickEvent();
|
||||
});
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 初始化图片点击事件
|
||||
const initImageClickEvent = () => {
|
||||
if (!quillEditorRef.value) return;
|
||||
|
||||
const quill = toRaw(quillEditorRef.value).getQuill();
|
||||
if (!quill || !quill.root) return;
|
||||
|
||||
const editorContainer = quill.root;
|
||||
const images = editorContainer.querySelectorAll('img');
|
||||
|
||||
// 移除之前的事件监听器(避免重复绑定)
|
||||
images.forEach((img: HTMLImageElement) => {
|
||||
const newImg = img.cloneNode(true) as HTMLImageElement;
|
||||
img.parentNode?.replaceChild(newImg, img);
|
||||
});
|
||||
|
||||
// 重新绑定事件
|
||||
const newImages = editorContainer.querySelectorAll('img');
|
||||
newImages.forEach((img: HTMLImageElement, index: number) => {
|
||||
// 添加鼠标样式
|
||||
img.style.cursor = 'pointer';
|
||||
img.style.transition = 'opacity 0.3s';
|
||||
// 确保图片可以点击(在只读模式下可能需要)
|
||||
img.style.pointerEvents = 'auto';
|
||||
|
||||
// 添加点击事件 - 使用capture模式确保事件能捕获
|
||||
const handleImageClick = (e: Event) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
// 收集所有图片URL
|
||||
const urlList: string[] = [];
|
||||
newImages.forEach((item: HTMLImageElement) => {
|
||||
if (item.src) {
|
||||
urlList.push(item.src);
|
||||
}
|
||||
});
|
||||
|
||||
if (urlList.length > 0) {
|
||||
imageUrlList.value = urlList;
|
||||
currentImageIndex.value = index;
|
||||
previewDialogVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
// 使用capture模式确保事件能捕获
|
||||
img.addEventListener('click', handleImageClick, true);
|
||||
|
||||
// 添加hover效果
|
||||
const handleMouseEnter = () => {
|
||||
img.style.opacity = '0.8';
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
img.style.opacity = '1';
|
||||
};
|
||||
|
||||
img.addEventListener('mouseenter', handleMouseEnter);
|
||||
img.addEventListener('mouseleave', handleMouseLeave);
|
||||
});
|
||||
};
|
||||
|
||||
// 上一张图片
|
||||
const prevImage = () => {
|
||||
if (currentImageIndex.value > 0) {
|
||||
currentImageIndex.value--;
|
||||
}
|
||||
};
|
||||
|
||||
// 下一张图片
|
||||
const nextImage = () => {
|
||||
if (currentImageIndex.value < imageUrlList.value.length - 1) {
|
||||
currentImageIndex.value++;
|
||||
}
|
||||
};
|
||||
|
||||
// 图片上传成功返回图片地址
|
||||
const handleUploadSuccess = (res: any) => {
|
||||
// 如果上传成功
|
||||
if (res.code === 200) {
|
||||
// 获取富文本实例
|
||||
const quill = toRaw(quillEditorRef.value).getQuill();
|
||||
let quill = toRaw(quillEditorRef.value).getQuill();
|
||||
// 获取光标位置
|
||||
const length = quill.selection.savedRange.index;
|
||||
let length = quill.selection.savedRange.index;
|
||||
// 插入图片,res为服务器返回的图片链接地址
|
||||
quill.insertEmbed(length, 'image', res.data.url);
|
||||
// 调整光标到最后
|
||||
quill.setSelection(length + 1);
|
||||
proxy?.$modal.closeLoading();
|
||||
|
||||
// 图片插入后,重新初始化图片点击事件
|
||||
nextTick(() => {
|
||||
initImageClickEvent();
|
||||
});
|
||||
} else {
|
||||
proxy?.$modal.msgError('图片插入失败');
|
||||
proxy?.$modal.closeLoading();
|
||||
@ -160,6 +312,24 @@ const handleBeforeUpload = (file: any) => {
|
||||
const handleUploadError = (err: any) => {
|
||||
proxy?.$modal.msgError('上传文件失败');
|
||||
};
|
||||
|
||||
// 组件挂载后初始化图片点击事件
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
initImageClickEvent();
|
||||
// 如果已经是只读模式,确保图片可以点击
|
||||
if (props.readOnly && quillEditorRef.value) {
|
||||
const quill = toRaw(quillEditorRef.value).getQuill();
|
||||
if (quill) {
|
||||
// 确保只读模式下图片事件不被阻止
|
||||
const editorContainer = quill.root;
|
||||
if (editorContainer) {
|
||||
editorContainer.style.pointerEvents = 'auto';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -241,4 +411,47 @@ const handleUploadError = (err: any) => {
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
|
||||
content: '等宽字体';
|
||||
}
|
||||
|
||||
/* 编辑器中的图片样式 */
|
||||
.editor :deep(.ql-editor img) {
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: auto !important; /* 确保图片可以点击 */
|
||||
}
|
||||
|
||||
.editor :deep(.ql-editor img:hover) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 只读模式下的图片样式 - 确保图片仍然可以点击 */
|
||||
.editor :deep(.ql-editor.ql-disabled) {
|
||||
pointer-events: none; /* 禁用整个编辑器的指针事件 */
|
||||
}
|
||||
|
||||
.editor :deep(.ql-editor.ql-disabled img) {
|
||||
pointer-events: auto !important; /* 但图片可以点击 */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 图片预览容器样式 */
|
||||
.image-preview-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.image-preview-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.image-counter {
|
||||
font-size: 16px;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user