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