11 changed files with 5438 additions and 83 deletions
@ -0,0 +1,121 @@ |
|||
<!-- |
|||
@ 作者: 秦东 |
|||
@ 时间: 2025-03-07 08:45:16 |
|||
@ 备注: Quill 富文本 |
|||
--> |
|||
<script lang='ts' setup> |
|||
import { ref } from 'vue'; |
|||
// 引入富文本编辑器与样式 |
|||
import { Quill, QuillEditor } from '@vueup/vue-quill' |
|||
import '@vueup/vue-quill/dist/vue-quill.snow.css' |
|||
// 引入缩放图片的插件 |
|||
import BlotFormatter from 'quill-blot-formatter' |
|||
Quill.register('modules/blotFormatter', BlotFormatter) |
|||
// 全局引入样式 |
|||
import 'quill/dist/quill.core.css'; |
|||
import 'quill/dist/quill.snow.css'; |
|||
const props = withDefaults( |
|||
defineProps<{ |
|||
modelValue: string |
|||
placeholder?: string |
|||
width?: string |
|||
height?: string |
|||
blobUrl?: string |
|||
imgUrl?: string |
|||
config?: any |
|||
type?: number |
|||
}>(), |
|||
{ |
|||
modelValue: '', |
|||
placeholder: '请输入内容', |
|||
width: '100%', |
|||
height: '300px', |
|||
blobUrl: '', |
|||
imgUrl: '', |
|||
type:1, |
|||
config: () => { |
|||
return {} |
|||
} |
|||
} |
|||
) |
|||
const emits = defineEmits<{ |
|||
(e: 'update:modelValue', value: string): void |
|||
}>() |
|||
|
|||
// const content = ref(props.modelValue); |
|||
|
|||
const content = computed({ |
|||
get() { |
|||
return props.modelValue |
|||
}, |
|||
set(newVal: any) { |
|||
emits('update:modelValue', newVal) |
|||
} |
|||
}) |
|||
|
|||
|
|||
const toolbarOptions = [ |
|||
['bold', 'italic', 'underline'], |
|||
[{ 'header': [1, 2, 3, false] }], |
|||
['image', 'code-block'] |
|||
]; |
|||
|
|||
const handleEditorReady = (editor) => { |
|||
console.log('Editor实例:', editor); |
|||
}; |
|||
|
|||
watch( |
|||
() => props.modelValue, |
|||
(n: any) => { |
|||
if (n && n !== content.value) { |
|||
content.value = n |
|||
} |
|||
} |
|||
) |
|||
</script> |
|||
<template><div class="butBox" @click="openVideo">按钮</div> |
|||
<div v-if="type==1 || type == 5" class="edioBox"> |
|||
<QuillEditor |
|||
v-model:content="content" |
|||
contentType="html" |
|||
:toolbar="toolbarOptions" |
|||
@ready="handleEditorReady" |
|||
|
|||
/> |
|||
</div> |
|||
<div v-else class="edioBoxHtml" v-html="content"></div> |
|||
</template> |
|||
<style lang='scss' scoped> |
|||
.butBox{ |
|||
width:150px; |
|||
height: 50px; |
|||
background-color: #FB041A; |
|||
display:none; |
|||
} |
|||
.edioBoxHtml{ |
|||
width: 100%; |
|||
} |
|||
.edioBox{ |
|||
width: 100%; |
|||
height: 350px; |
|||
.ql-toolbar{ |
|||
.ql-snow{ |
|||
height: 300px; |
|||
} |
|||
} |
|||
:deep .ql-toolbar.ql-snow + .ql-container.ql-snow{ |
|||
height: 300px; |
|||
} |
|||
} |
|||
.ql-editor.ql-fullscreen { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 9999; |
|||
} |
|||
.ql-container { |
|||
user-select: auto !important; |
|||
-webkit-user-select: auto !important; |
|||
} |
|||
|
|||
</style> |
|||
@ -0,0 +1,280 @@ |
|||
<!-- |
|||
@ 作者: 秦东 |
|||
@ 时间: 2025-03-07 09:23:53 |
|||
@ 备注: |
|||
--> |
|||
<script lang='ts' setup> |
|||
// 引入富文本编辑器与样式 |
|||
import { Quill, QuillEditor } from '@vueup/vue-quill' |
|||
import '@vueup/vue-quill/dist/vue-quill.snow.css' |
|||
import { newUploadFileApi } from '@/api/common/public' |
|||
// 引入缩放图片的插件 |
|||
import BlotFormatter from 'quill-blot-formatter' |
|||
Quill.register('modules/blotFormatter', BlotFormatter) |
|||
|
|||
const props = withDefaults( |
|||
defineProps<{ |
|||
modelValue: string |
|||
placeholder?: string |
|||
width?: string |
|||
height?: string |
|||
blobUrl?: string |
|||
imgUrl?: string |
|||
config?: any |
|||
type?: number |
|||
}>(), |
|||
{ |
|||
modelValue: '', |
|||
placeholder: '请输入内容', |
|||
width: '100%', |
|||
height: '300px', |
|||
blobUrl: '', |
|||
imgUrl: '', |
|||
type:1, |
|||
config: () => { |
|||
return {} |
|||
} |
|||
} |
|||
) |
|||
const emits = defineEmits<{ |
|||
(e: 'update:modelValue', value: string): void |
|||
}>() |
|||
|
|||
// const content = ref(props.modelValue); |
|||
|
|||
const content = computed({ |
|||
get() { |
|||
return props.modelValue |
|||
}, |
|||
set(newVal: any) { |
|||
emits('update:modelValue', newVal) |
|||
} |
|||
}) |
|||
const editorRef = ref(null) |
|||
// 处理富文本图片上传 |
|||
const imageHandler = () => { |
|||
// 创建一个文件输入元素 |
|||
const input = document.createElement('input'); |
|||
input.setAttribute('type', 'file'); |
|||
input.setAttribute('accept', 'image/*'); |
|||
// 模拟点击,打开文件选择对话框 |
|||
input.click(); |
|||
|
|||
// 当用户选择文件后触发的事件 |
|||
input.onchange = async () => { |
|||
// 获取用户选择的文件 |
|||
const file = input.files ? input.files[0] : null; |
|||
if (file) { |
|||
// 创建一个 FormData 对象,用于文件上传 |
|||
const formData = new FormData(); |
|||
formData.append('file', file); |
|||
formData.append('type',"1") |
|||
try { |
|||
newUploadFileApi(formData) |
|||
.then(res =>{ |
|||
// console.log("上传图片",res.data) |
|||
// resolve(res.data.url) |
|||
const quill = toRaw(editorRef.value).getQuill() |
|||
if (quill) { |
|||
// 获取当前光标位置 |
|||
const range = quill.getSelection(true); |
|||
// 在当前光标位置插入上传的图片 |
|||
quill.insertEmbed(range.index, 'image',res.data.url); |
|||
} |
|||
}) |
|||
.catch(()=>{ |
|||
// reject('上传出错,请查看你上传的文件是否符合要求!') |
|||
alert('上传出错,请查看你上传的文件是否符合要求!') |
|||
}) |
|||
} catch (error) { |
|||
alert('图片上传失败') |
|||
} |
|||
|
|||
// try { |
|||
// /** |
|||
// * @todo 可以选中图片,然后把file文件给后端,后端给存到文件服务器,然后返回一个线上地址 |
|||
// * 这里的abc替换成你的请求接口方法,也可以使用 axios 发送 POST 请求 |
|||
// * */ |
|||
// // todo |
|||
// // 使用 axios 发送 POST 请求,将文件上传到服务器,这里的abc替换成你的请求接口方法 |
|||
// // 可以选中图片,然后把file文件给后端,后端给存到文件服务器,然后返回一个线上地址 |
|||
// const res = await abc(formData); |
|||
|
|||
// // 确保获取到 Quill 编辑器实例 |
|||
// const quill = toRaw(editorRef.value).getQuill() |
|||
// if (quill) { |
|||
// // 获取当前光标位置 |
|||
// const range = quill.getSelection(true); |
|||
// // 在当前光标位置插入上传的图片 |
|||
// quill.insertEmbed(range.index, 'image', res.data); |
|||
// } |
|||
|
|||
// } catch (error) { |
|||
// alert('图片上传失败') |
|||
// } |
|||
} |
|||
}; |
|||
} |
|||
|
|||
|
|||
|
|||
// toolbar标题,划过富文本头部提示信息 |
|||
const titleConfig = [ |
|||
{ Choice: '.ql-insertMetric', title: '跳转配置' }, |
|||
{ Choice: '.ql-bold', title: '加粗' }, |
|||
{ Choice: '.ql-italic', title: '斜体' }, |
|||
{ Choice: '.ql-underline', title: '下划线' }, |
|||
{ Choice: '.ql-header', title: '段落格式' }, |
|||
{ Choice: '.ql-strike', title: '删除线' }, |
|||
{ Choice: '.ql-blockquote', title: '块引用' }, |
|||
{ Choice: '.ql-code', title: '插入代码' }, |
|||
{ Choice: '.ql-code-block', title: '插入代码段' }, |
|||
{ Choice: '.ql-font', title: '字体' }, |
|||
{ Choice: '.ql-size', title: '字体大小' }, |
|||
{ Choice: '.ql-list[value="ordered"]', title: '编号列表' }, |
|||
{ Choice: '.ql-list[value="bullet"]', title: '项目列表' }, |
|||
{ Choice: '.ql-direction', title: '文本方向' }, |
|||
{ Choice: '.ql-header[value="1"]', title: 'h1' }, |
|||
{ Choice: '.ql-header[value="2"]', title: 'h2' }, |
|||
{ Choice: '.ql-align', title: '对齐方式' }, |
|||
{ Choice: '.ql-color', title: '字体颜色' }, |
|||
{ Choice: '.ql-background', title: '背景颜色' }, |
|||
{ Choice: '.ql-image', title: '图像' }, |
|||
{ Choice: '.ql-video', title: '视频' }, |
|||
{ Choice: '.ql-link', title: '添加链接' }, |
|||
{ Choice: '.ql-formula', title: '插入公式' }, |
|||
{ Choice: '.ql-clean', title: '清除字体格式' }, |
|||
{ Choice: '.ql-script[value="sub"]', title: '下标' }, |
|||
{ Choice: '.ql-script[value="super"]', title: '上标' }, |
|||
{ Choice: '.ql-indent[value="-1"]', title: '向左缩进' }, |
|||
{ Choice: '.ql-indent[value="+1"]', title: '向右缩进' }, |
|||
{ Choice: '.ql-header .ql-picker-label', title: '标题大小' }, |
|||
{ Choice: '.ql-header .ql-picker-item[data-value="1"]', title: '标题一' }, |
|||
{ Choice: '.ql-header .ql-picker-item[data-value="2"]', title: '标题二' }, |
|||
{ Choice: '.ql-header .ql-picker-item[data-value="3"]', title: '标题三' }, |
|||
{ Choice: '.ql-header .ql-picker-item[data-value="4"]', title: '标题四' }, |
|||
{ Choice: '.ql-header .ql-picker-item[data-value="5"]', title: '标题五' }, |
|||
{ Choice: '.ql-header .ql-picker-item[data-value="6"]', title: '标题六' }, |
|||
{ Choice: '.ql-header .ql-picker-item:last-child', title: '标准' }, |
|||
{ Choice: '.ql-size .ql-picker-item[data-value="small"]', title: '小号' }, |
|||
{ Choice: '.ql-size .ql-picker-item[data-value="large"]', title: '大号' }, |
|||
{ Choice: '.ql-size .ql-picker-item[data-value="huge"]', title: '超大号' }, |
|||
{ Choice: '.ql-size .ql-picker-item:nth-child(2)', title: '标准' }, |
|||
{ Choice: '.ql-align .ql-picker-item:first-child', title: '居左对齐' }, |
|||
{ Choice: '.ql-align .ql-picker-item[data-value="center"]', title: '居中对齐' }, |
|||
{ Choice: '.ql-align .ql-picker-item[data-value="right"]', title: '居右对齐' }, |
|||
{ Choice: '.ql-align .ql-picker-item[data-value="justify"]', title: '两端对齐' } |
|||
] |
|||
// 富文本配置 |
|||
const options = ref({ |
|||
theme: 'snow', // 使用snow主题 |
|||
modules: { |
|||
// 富文本头部栏的功能配置 |
|||
toolbar: { |
|||
container: [ |
|||
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线 |
|||
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色 |
|||
[{ align: [] }], // 对齐方式 |
|||
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小 |
|||
[{ font: [] }], // 字体种类 |
|||
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 |
|||
[{ direction: 'ltl' }], // 文本方向 |
|||
[{ direction: 'rtl' }], // 文本方向 |
|||
[{ indent: '-1' }, { indent: '+1' }], // 缩进 |
|||
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表 |
|||
[{ script: 'sub' }, { script: 'super' }], // 上标/下标 |
|||
['blockquote', 'code-block'], // 引用 代码块 |
|||
['clean'], // 清除文本格式 |
|||
['link', 'image', 'video'], // 链接、图片、视频 |
|||
], |
|||
handlers: { |
|||
image: imageHandler, // 点击图片触发事件 |
|||
}, |
|||
}, |
|||
// 图片缩放 |
|||
blotFormatter: { |
|||
// 可以在这里设置缩放样式 |
|||
// overlay: { |
|||
// style: { |
|||
// border: '2px solid red', |
|||
// } |
|||
// }, |
|||
toolbar: { |
|||
mainClassName: 'blot-formatter__toolbar' |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
// !!!如果方法报错,把imageHandler 方法放到options的上面 |
|||
|
|||
// 给富文本框工具栏加上鼠标悬浮中文提示 |
|||
const initTitle = () => { |
|||
for (let item of titleConfig) { |
|||
// .editor 是富文本编辑器的类名 |
|||
let tip = document.querySelector('.editor ' + item.Choice); |
|||
if (tip) { |
|||
tip.setAttribute('title', item.title); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 自定义粘贴事件 |
|||
const customPaste=(e)=>{ |
|||
// 获取当前最新时间 改名啥的可以用 |
|||
let newTime = new Date().getTime() |
|||
|
|||
const clipboardData = e.clipboardData // 粘贴信息 |
|||
const types = clipboardData.types // 当前文件类型 |
|||
if (types.includes('Files')) { |
|||
e.preventDefault(); |
|||
e.clipboardData.files.forEach(file=>{ |
|||
// 在这里可以拿到粘贴后的图片与文件信息 |
|||
// 在这里做操作 |
|||
}) |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
onMounted(()=>{ |
|||
nextTick(()=>{ |
|||
// 给富文本框工具栏加上鼠标悬浮中文提示 |
|||
initTitle() |
|||
}) |
|||
// 给富文本增加粘贴事件 |
|||
editorRef.value.getQuill().root.addEventListener('customPaste', customPaste, false) |
|||
}) |
|||
watch( |
|||
() => props.modelValue, |
|||
(n: any) => { |
|||
if (n && n !== content.value) { |
|||
content.value = n |
|||
} |
|||
} |
|||
) |
|||
</script> |
|||
<template><div class="butBox" @click="openVideo">按钮</div> |
|||
<div class="editor"> |
|||
<!-- 这两个都是获取值的必要条件: v-model:content contentType="html" --> |
|||
<quill-editor |
|||
ref="editorRef" |
|||
v-model:content="content" |
|||
:options="options" |
|||
contentType="html" |
|||
></quill-editor> |
|||
</div> |
|||
</template> |
|||
<style lang='scss' scoped> |
|||
.editor{ |
|||
width: 100%; |
|||
:deep(.ql-editor) { |
|||
min-height: 200px; |
|||
} |
|||
} |
|||
.butBox{ |
|||
width:150px; |
|||
height: 50px; |
|||
background-color: #FB041A; |
|||
display:none; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue