vue 腾讯云 javascript sdk + 简单富文本组件设计+实战

<template><div><quill-editor v-model="content" ref="myQuillEditor" :options="editorOption" @change="onEditorChange"@input="handleInput"></quill-editor><!-- 链接添加对话框 --><el-dialog title="添加链接" :visible.sync="linkDialogVisible" width="30%" :close-on-click-modal="false"><el-form ref="linkForm" :model="linkForm" label-width="80px"><el-form-item label="链接地址"><el-input v-model="linkForm.url" placeholder="请输入链接地址,例如:http://" autocomplete="off"@blur="validateLink(linkForm.url.trim())"></el-input></el-form-item><el-form-item label="备注" style="margin-top: 10px;margin-bottom: 10px;"><el-input v-model="linkForm.text" placeholder="请输入链接备注" :disabled="!linkForm.url"></el-input></el-form-item><el-form-item><el-button type="primary" @click="handleLinkSubmit">确认</el-button><el-button @click="closeLinkDialog">取消</el-button></el-form-item></el-form></el-dialog><!-- 图片设置对话框 --><el-dialog title="插入图片" :visible.sync="imageDialogVisible" width="25%" :close-on-click-modal="false" @close="closeImageDialog"class="pic-dialog"><el-tabs v-model="activeTab" ref="imageTabs"><el-tab-pane label="本地图片" name="localImage"><el-form :model="localImageForm" label-width="80px"><el-form-item><el-upload ref="imageUpload" class="upload-demo" :http-request="uploadToCOS":on-success="handleImageSuccess" :on-error="handleError" accept="image/*":on-change="handleImageChange" action="#" :show-file-list="false":on-progress="handleImageUploadProgress"><div class="image-item"><el-image v-if="localImageForm.imageUrl" :src="localImageForm.imageUrl"fit="contain" slot="trigger"></el-image><img v-else src="@/assets/default-image.jpg" alt="Upload Failed Image"></div></el-upload><el-progress :percentage="uploadImagePercentage" style="margin-bottom: 10px;width:200px"></el-progress></el-form-item><el-form-item><el-button type="primary" @click="insertLocalImages">确认</el-button><el-button @click="closeImageDialog">取消</el-button></el-form-item></el-form></el-tab-pane><el-tab-pane label="链接图片" name="urlImage"><el-form :model="urlImageForm" label-width="80px"><el-form-item label="图片链接"><el-input v-model="urlImageForm.url" placeholder="请输入图片链接" autocomplete="off"></el-input><div class="image-item" style="margin-bottom: 20px;"><el-image v-if="urlImageForm.url" :src="urlImageForm.url" fit="contain"></el-image><img v-else src="@/assets/default-image.jpg" alt="Upload Failed Image"></div></el-form-item><el-form-item><el-button type="primary" @click="insertURLImage">确认</el-button><el-button @click="closeImageDialog">取消</el-button></el-form-item></el-form></el-tab-pane></el-tabs></el-dialog><!-- 视频设置对话框 --><el-dialog title="插入视频" :visible.sync="videoDialogVisible" width="25%" :close-on-click-modal="false" @close="closeVideoDialog"><el-tabs v-model="activeVideoTab" ref="videoTabs"><el-tab-pane label="本地视频" name="localVideo"><el-form :model="localVideoForm" label-width="80px"><el-form-item label="视频文件" style="margin-top: 10px;margin-bottom: 20px;"><!-- 视频上传 --><el-upload ref="videoUpload" class="upload-demo" :http-request="uploadToCOS":on-success="handleVideoSuccess" :on-error="handleError" :auto-upload="false":on-progress="handleUploadProgress" :show-file-list="false" accept="video/*":on-remove="handleVideoRemove" :on-change="handleVideoChange" action="#"><el-button slot="trigger" size="small" type="success"style="width:300px">打开并上传</el-button><div class="video-item"><video v-if="localVideoForm.videoUrl" :src="localVideoForm.videoUrl"controls="controls" width="300px" height="150px"></video><div slot="tip" class="el-upload__tip"></div></div><el-progress :percentage="uploadVideoPercentage" style="width:355px"></el-progress></el-upload></el-form-item><el-form-item label="设置宽度" style="margin-bottom: 20px;"><el-input v-model.number="localVideoForm.width" placeholder="请输入宽度"></el-input></el-form-item><el-form-item><el-button type="primary" @click="insertLocalVideo">确认</el-button><el-button @click="closeVideoDialog">取消</el-button></el-form-item></el-form></el-tab-pane><el-tab-pane label="视频链接" name="urlVideo"><el-form :model="urlVideoForm" label-width="80px"><el-form-item label="视频链接" style="margin-top: 10px;margin-bottom: 20px;"><el-input v-model="urlVideoForm.url" placeholder="请输入视频链接,例如:http://" autocomplete="off"@blur="validateLink(urlVideoForm.url.trim())"></el-input></el-form-item><el-form-item><el-button type="primary" @click="insertURLVideo">确认</el-button><el-button @click="closeVideoDialog">取消</el-button></el-form-item></el-form></el-tab-pane></el-tabs></el-dialog></div>
</template><script>
import { quillEditor } from "vue-quill-editor";
import { Quill } from "vue-quill-editor";
import resizeImage from "quill-image-resize-module";
import { ImageDrop } from "quill-image-drop-module";import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";Quill.register("modules/imageResize", resizeImage);
Quill.register("modules/imageDrop", ImageDrop);
Quill.register("modules/resizeImage", resizeImage);import "@/styles/quillEditor.css"
import { titleConfig, toolbarOptions } from "@/js/quillEditor";import COS from 'cos-js-sdk-v5';
import { v4 as uuidv4 } from 'uuid';// 扩展Quill以支持视频嵌入
const BlockEmbed = Quill.import('blots/block/embed');
class VideoBlot extends BlockEmbed {static create(value) {let node = super.create();node.setAttribute('src', value.url);node.setAttribute('controls', true);node.setAttribute('width', value.width || '50%');node.setAttribute('height', value.height || 'auto');return node;}static value(node) {return {url: node.getAttribute('src'),width: node.getAttribute('width'),height: node.getAttribute('height')};}
}
VideoBlot.blotName = 'video';
VideoBlot.tagName = 'video';
Quill.register(VideoBlot);
export default {components: {quillEditor,},data() {return {content: "",editorOption: {modules: {toolbar: {container: toolbarOptions,handlers: {image: this.openImageDialog,link: this.openLinkDialog,video: this.openVideoDialog},},imageDrop: true,imageResize: {displayStyles: {backgroundColor: "black",border: "none",color: "white",},modules: ["Resize", "DisplaySize", "Toolbar"],},},placeholder: "请输入正文....",theme: "snow",},linkDialogVisible: false,linkForm: {url: 'https://',text: '',},currentRange: null, // 保存当前选区信息imageDialogVisible: false,videoDialogVisible: false,activeTab: 'localImage', // 默认选中本地图片activeVideoTab: 'localVideo', // 默认选中本地视频localImageForm: {file: null,imageUrl: '', // 用于显示选中的本地图片},urlImageForm: {url: '',},localVideoForm: {file: null,videoUrl: '',width: '',},urlVideoForm: {url: 'https://',width: '',},file: null,editorDialogVisible: false,htmlData: '',cos: null, // 用于存储COS实例uploadVideoPercentage: 0,uploadImagePercentage:0,urlVideoUrl:''};},created() {// 初始化COS实例this.cos = new COS({SecretId: '', // 替换为你的SecretIdSecretKey: '', // 替换为你的SecretKey});},methods: {urlImageFormInput() {console.log("是否获取焦点")},uploadToCOS({ file, onProgress, onSuccess, onError }) {// 生成唯一的文件名const uniqueFileName = this.generateUniqueFileName(file.name);this.cos.uploadFile({Bucket: '', // 替换为你的BucketRegion: '', // 替换为你的RegionKey: uniqueFileName, // 使用生成的唯一文件名Body: file,SliceSize: 1024 * 1024, // 分块大小,单位为字节,这里设置为1MBonProgress: function (progressData) {onProgress(progressData);}}, (err, data) => {if (err) {onError(err);} else {onSuccess(data);}});},// 打开本地图片、视频、文件previewFile(file) {const reader = new FileReader();reader.readAsDataURL(file);},// 生成唯一文件名generateUniqueFileName(originalName) {// 获取文件扩展名const extension = originalName.substring(originalName.lastIndexOf('.'));// 生成唯一文件名const uniqueName = uuidv4();// 返回新的文件名return `${uniqueName}${extension}`;},// 提交上传本地图片、视频、文件submitUpload(refName) {this.$refs[refName].submit();},/* eslint-disable */handleError(err, file) {console.error('上传失败:', err);},handleInput() {// 输入内容后,隐藏错误信息this.$emit('input', this.content.trim());},/* eslint-disable */onEditorChange({ quill, html, text }) {this.content = html;},setTitleConfig() {for (const item of titleConfig) {const tip = document.querySelector(".quill-editor " + item.Choice);if (!tip) continue;tip.setAttribute("title", item.title);}},// 打开插入链接对话框 *********************************************************************************openLinkDialog() {const quill = this.$refs.myQuillEditor.quill;if (!quill) {console.error('Quill instance not found');return;}// 获取当前选区this.currentRange = quill.getSelection();if (!this.currentRange) {console.error('Selection not found');return;}// 重新打开对话框时清空输入框this.linkForm.url = 'https://';this.linkForm.text = '';this.linkDialogVisible = true;},// 关闭插入链接对话框closeLinkDialog() {this.linkDialogVisible = false;},handleLinkSubmit() {const quill = this.$refs.myQuillEditor.quill;if (!quill) {console.error('Quill instance not found');return;}const { url, text } = this.linkForm;if (!this.currentRange) {console.error('Current range not found');return;}console.log("如果有备注文本,则插入带备注的链接");// 如果有备注文本,则插入带备注的链接if (text) {this.$nextTick(() => {const linkHtml = `<a href="${url}" title="${text}">${text}</a>`;quill.clipboard.dangerouslyPasteHTML(this.currentRange.index, linkHtml, 'user');this.linkDialogVisible = false;});} else {// 否则直接插入普通链接this.$nextTick(() => {const linkHtml = `<a href="${url}">${url}</a>`;quill.clipboard.dangerouslyPasteHTML(this.currentRange.index, linkHtml, 'user');this.linkDialogVisible = false;});}// 更新内容this.content = quill.root.innerHTML;},validateLink(url) {// 校验链接格式// const url = this.linkForm.url.trim();if (url && !this.validateURL(url)) {this.$message.error('链接地址格式不正确,请输入有效的链接地址。');}},validateURL(url) {// 修正后的正则表达式用于验证链接格式,支持 http 和 https 协议const urlPattern = /^(https?:\/\/)?[\w.-]+\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)?$/;return urlPattern.test(url);},// 打开插入图片对话框 *********************************************************************************openImageDialog() {// 获取当前光标位置const quill = this.$refs.myQuillEditor.quill;if (quill) {this.currentRange = quill.getSelection();}if (!this.currentRange) {return;}this.imageDialogVisible = true;// 在下一次DOM更新后模拟点击本地图片选项卡this.$nextTick(() => {const tabs = this.$refs.imageTabs;if (tabs) {tabs.setCurrentName('localImage');}});},// 关闭插入图片对话框closeImageDialog() {this.imageDialogVisible = false;// 重置表单this.localImageForm.file = null;this.localImageForm.imageUrl = '';this.urlImageForm.url = '';this.uploadImagePercentage = 0;},// 2.1 插入本地图片insertLocalImages() {const { imageUrl } = this.localImageForm;if (!imageUrl) {return;}// 直接使用上传后的URL调用insertImage方法this.insertImage(imageUrl);},/* eslint-disable */handleImageSuccess(response, file) {console.log('图片上传成功:', response);const fileUrl = `https://${response.Location}`;this.localImageForm.imageUrl = fileUrl;// this.$refs.imageUpload.clearFiles(); //去掉文件列表 },handleImageChange(file) {if (file.raw) {this.file = file.raw; // 获取选择的文件对象this.previewFile(file.raw); // 预览图片this.submitUpload('imageUpload');}},handleImageRemove() {this.localImageForm.imageUrl = ''; // 重置视频文件},handleImageUploadProgress(event, file, fileList) {this.uploadImagePercentage = Math.round(event.loaded / event.total * 100);},// 2.2 插入链接图片insertURLImage() {const { url} = this.urlImageForm;this.insertImage(url);},// 2.3 图片嵌入到富文本insertImage(url) {const quill = this.$refs.myQuillEditor.quill;if (!quill) {console.error('Quill实例未找到');return;}const range = this.currentRange;// quill.clipboard.dangerouslyPasteHTML(range.index, `<img src="${url}" style="width: ${width}px; height: ${height}px;">`, 'user');quill.clipboard.dangerouslyPasteHTML(range.index, `<img src="${url}">`, 'user');this.content = quill.root.innerHTML;// 调整插入的图片大小并设置idthis.$nextTick(() => {const imgTags = quill.root.querySelectorAll('img');imgTags.forEach((img, index) => {if (!img.id) {const imageId = `img-${Date.now()}-${index}`;img.setAttribute('id', imageId);}});});// 插入后重置对话框和表单this.closeImageDialog();},adjustEditorHeight() {const quill = this.$refs.myQuillEditor.quill;if (!quill) return;const editorElement = quill.root;if (!editorElement) return;editorElement.style.minHeight = "500px";},// 视频设置 *********************************************************************************// 打开插入视频对话框openVideoDialog() {// 获取当前光标位置const quill = this.$refs.myQuillEditor.quill;if (quill) {this.currentRange = quill.getSelection();}if (!this.currentRange) {return;}this.videoDialogVisible = true;// 在下一次DOM更新后模拟点击本地图片选项卡this.$nextTick(() => {const tabs = this.$refs.videoTabs;if (tabs) {tabs.setCurrentName('localVideo');}});},// 关闭插入视频对话框closeVideoDialog() {this.videoDialogVisible = false;// 重置表单this.localVideoForm.file = null;this.localVideoForm.videoUrl = '';this.localVideoForm.width = '';this.urlVideoForm.url = '';this.urlVideoForm.width = '';// this.$refs.videoUpload.clearFiles(); // 清除上传文件列表this.uploadVideoPercentage = 0;},// 3.1 插入本地视频insertLocalVideo() {const { videoUrl, width } = this.localVideoForm;if (!videoUrl) {return;}// 直接使用上传后的URL调用insertVideo方法this.insertVideo(videoUrl, width);},handleVideoSuccess(response, file) {console.log('视频上传成功:', response);const fileUrl = `https://${response.Location}`;this.localVideoForm.videoUrl = fileUrl;},handleError(err, file) {console.error('上传失败:', err);},handleVideoChange(file) {if (file.raw) {this.file = file.raw; // 获取选择的文件对象this.previewFile(file.raw); // 预览视频}this.submitUpload('videoUpload')},handleVideoRemove() {this.localVideoForm.videoUrl = null; // 重置视频文件},handleUploadProgress(event, file, fileList) {this.uploadVideoPercentage = Math.round(event.loaded / event.total * 100);},// 3.2 插入链接视频insertURLVideo() {const { url, width } = this.urlVideoForm;this.insertVideo(url, width);},// 3.3 插入视频到富文本中insertVideo(url, width) {const quill = this.$refs.myQuillEditor.quill;if (!quill) {console.error('Quill实例未找到');return;}const range = this.currentRange;const index = range ? range.index : quill.getLength();quill.insertEmbed(index, 'video', {url: url,width: width,});this.content = quill.root.innerHTML;this.closeVideoDialog();},},mounted() {this.setTitleConfig();},
};
</script><style scoped>
/* 可以添加自定义样式 */
.image-item {margin-top: 20px;width: 150px;height: 150px;overflow: hidden;border: 2px dashed #ccc;background-color: white;display: flex;justify-content: center;align-items: center;position: relative;cursor: pointer;/* margin-bottom: 20px; */
}.video-item {margin-top: 20px;width: 300px;height: 150px;overflow: hidden;border: 2px dashed #ccc;background-color: white;display: flex;justify-content: center;align-items: center;position: relative;cursor: pointer;
}.progress-bar {width: 300px;height: 10px;background-color: #f0f0f0;margin-top: 10px;
}.progress-bar-inner {height: 100%;background-color: #409eff;
}
</style><style scoped>
.pic-dialog {.el-dialog__header {border-bottom: none;}.el-dialog__body {padding-top: 0;margin-right: 10px;}.dialog-content {padding: 0 40px;}.el-dialog__footer {padding: 10px 10px 10px;border-top: none;}
}.ql-editor.ql-blank /deep/ {min-height: 500px !important;/* 设置空内容时的最小高度 */
}.quill-editor {line-height: normal;
}
</style>

quillEditor.css

/* 字体风格 */
/* 处理下拉字体选择器中选项的文本溢出并显示省略号 */
.ql-snow .ql-picker.ql-font .ql-picker-label::before {width: 88px; /* 设置下拉选项宽度,可以根据需要调整 */white-space: nowrap; /* 不换行显示 */overflow: hidden; /* 隐藏溢出部分 */text-overflow: ellipsis; /* 使用省略号显示溢出文本 */
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimSun"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimSun"]::before {content: "宋体";font-family: "SimSun";
}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="SimHei"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="SimHei"]::before {content: "黑体";font-family: "SimHei";
}.ql-snow.ql-picker.ql-font.ql-picker-label[data-value="Microsoft-YaHei"]::before,
.ql-snow.ql-picker.ql-font.ql-picker-item[data-value="Microsoft-YaHei"]::before {content: "微软雅黑";font-family: "Microsoft YaHei";
}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="KaiTi"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="KaiTi"]::before {content: "楷体";font-family: "KaiTi";
}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="FangSong"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="FangSong"]::before {content: "仿宋";font-family: "FangSong";
}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="Arial"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="Arial"]::before {content: "Arial";font-family: "Arial";
}.ql-snow.ql-picker.ql-font.ql-picker-label[data-value="Times-New-Roman"]::before,
.ql-snow.ql-picker.ql-font.ql-picker-item[data-value="Times-New-Roman"]::before {content: "Times New Roman";font-family: "Times New Roman";
}.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="sans-serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="sans-serif"]::before {content: "sans-serif";font-family: "sans-serif";
}.ql-font-SimSun { font-family: "SimSun"; }
.ql-font-SimHei { font-family: "SimHei"; }
.ql-font-Microsoft-YaHei { font-family: "Microsoft YaHei"; }
.ql-font-KaiTi { font-family: "KaiTi"; }
.ql-font-FangSong { font-family: "FangSong"; }
.ql-font-Arial { font-family: "Arial"; }
.ql-font-Times-New-Roman { font-family: "Times New Roman"; }
.ql-font-sans-serif { font-family: "sans-serif"; }/* 字体大小 */
.ql-snow .ql-picker.ql-size .ql-picker-label::before { content: "字体大小"; }
.ql-snow .ql-picker.ql-size .ql-picker-item::before { content: "常规"; }
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="14px"]::before{content: "14px";font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before{content: "16px";font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before{content: "18px";font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before{content: "20px";font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="22px"]::before{content: "22px";font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="26px"]::before{content: "26px";font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="30px"]::before {content: "30px";font-size: 14px;
}.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before {content: "14px";font-size: 14px;
}.ql-size-14px { font-size: 14px; }/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {content: "16px";font-size: 16px;
}.ql-size-16px { font-size: 16px; }/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before {content: "18px";font-size: 18px;
}.ql-size-18px { font-size: 18px; }/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {content: "20px";font-size: 20px;
}.ql-size-20px { font-size: 20px; }/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="22px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="22px"]::before {content: "22px";font-size: 22px;
}.ql-size-22px { font-size: 22px; }/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="26px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="26px"]::before {content: "26px";font-size: 26px;
}.ql-size-26px { font-size: 26px; }/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="28px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="28px"]::before {content: "28px";font-size: 28px;
}.ql-size-28px { font-size: 28px; }/* .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="30px"]::before, */
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="30px"]::before {content: "30px";font-size: 30px;
}.ql-size-30px { font-size: 30px; }/* 段落大小 */
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {content: "标题1";
}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {content: "标题2";
}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {content: "标题3";
}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {content: "标题4";
}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {content: "标题5";
}.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {content: "标题6";
}.ql-snow .ql-picker.ql-header .ql-picker-item::before {content: "常规";
}/* .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, */
.ql-snow .ql-picker.ql-header .ql-picker-label::before {content: "标题大小";
}/* 默认设置 */
.ql-snow .ql-editor { font-size: 14px; }
/* 查看样式 */
.view-editor .ql-toolbar { display: none; }
.view-editor .ql-container.ql-snow { border: 0; }
.view-editor .ql-container.ql-snow .ql-editor { padding: 0; }
/* 编辑样式 */
.edit-editor .ql-toolbar { display: block; }
.edit-editor .ql-container.ql-snow {border: 1px solid #ccc;min-height: inherit;
}

 quillEditor.js

import { Quill } from "vue-quill-editor";
// 自定义字体大小
const sizes = [false,"14px","16px","18px","20px","22px","26px","28px","30px",];
const Size = Quill.import("formats/size");
Size.whitelist = sizes;
// 自定义字体
const fonts = ["SimSun","SimHei","Microsoft-YaHei","KaiTi","FangSong","Arial","Times-New-Roman","sans-serif",];
var Font = Quill.import("formats/font");
Font.whitelist = fonts;
Quill.register(Font, true);// 工具栏相关配置
export const toolbarOptions = [["bold", "italic", "underline"], // 加粗 斜体 下划线 删除线 [{ size: sizes }], // 字体大小[{ header: [1, 2, 3, 4, 5, false] }], // 标题[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色// [{ font: fonts }], // 字体种类["link", "image", "video","clean"], // 链接、图片、视频
];// 设置工具栏中文提示
export const titleConfig = [{ Choice: ".ql-insertMetric", title: "跳转配置" },{ Choice: ".ql-bold", title: "加粗" },{ Choice: ".ql-italic", title: "斜体" },{ Choice: ".ql-header", title: "段落格式" },{ Choice: ".ql-strike", title: "删除线" },// { Choice: ".ql-font", title: "字体" },{ 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-clean", title: "清除字体格式" },{ Choice: ".ql-size .ql-picker-item:nth-child(2)", title: "标准" },
];

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/48140.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【论文阅读笔记】In Search of an Understandable Consensus Algorithm (Extended Version)

1 介绍 分布式一致性共识算法指的是在分布式系统中&#xff0c;使得所有节点对同一份数据的认知能够达成共识的算法。且算法允许所有节点像一个整体一样工作&#xff0c;即使其中一些节点出现故障也能够继续工作。之前的大部分一致性算法实现都是基于Paxos&#xff0c;但Paxos…

前端Vue项目中腾讯地图SDK集成:经纬度与地址信息解析的实践

在前端开发中&#xff0c;我们经常需要将经纬度信息转化为具体的地址信息&#xff0c;这对于定位、地图展示等功能至关重要。Vue作为现代前端框架的代表&#xff0c;其组件化开发的特性使得我们能够更高效地实现这一功能。本文将介绍如何在Vue项目中集成腾讯地图SDK&#xff0c…

一个 基于nuxt3 + vite + ts 搭建的 网盘服务 (附带部署教程)

目录 介绍技术选型功能介绍代码地址部署安装 node 环境打包代码安装 pm2 去 后台运行代码安装一个nginx 介绍 最近 有个卖课的朋友 谈到 网盘没有目录分享的功能&#xff0c;我之前嫖了他太多课了&#xff0c;出于感激给他写个小服务。 在线地址&#xff1a; http://godboxs.c…

SpringMVC源码深度解析(上)

今天&#xff0c;聊聊SpringMVC框架的原理。SpringMVC属于Web框架&#xff0c;它不能单独存在&#xff0c;需要依赖Servlet容器&#xff0c;常用的Servlet容器有Tomcat、Jetty等&#xff0c;这里以Tomcat为例进行讲解。老规矩&#xff0c;先看看本项目的层级结构&#xff1a; 需…

【入门基础】java泛型和通配符详解

【入门基础】java泛型和通配符详解 文章目录 前言泛型类泛型方法泛型接口通配符&#xff08;Wildcards&#xff09;使用场景非主流用法 总结 前言 Java泛型&#xff08;Generics&#xff09;是JDK 5中引入的一个新特性&#xff0c;它提供了编译时类型安全检测机制&#xff0c;…

socket 收发TCP/UDP

一、c 个人测试记录&#xff0c;有问题还请指出&#xff0c;谢谢 参考&#xff1a;C开发基础之网络编程WinSock库使用详解TCP/UDP Socket开发_c udp使用什么库-CSDN博客 代码中Logger测试见文章&#xff1a; c中spdlog的使用/python中logger的使用-CSDN博客 1、main.cpp 收…

【体外诊断】ARM/X86+FPGA嵌入式计算机在医疗CT机中的应用

体外诊断 信迈科技提供基于Intel平台、AMD平台、NXP平台的核心板、2.5寸主板、Mini-ITX主板、4寸主板、PICO-ITX主板&#xff0c;以及嵌入式准系统等计算机硬件。产品支持GAHDMI等独立双显&#xff0c;提供丰富串口、USB、GPIO、PCIe扩展接口等I/O接口&#xff0c;扩展性强&…

前端组件化开发:以Vue自定义底部操作栏组件为例

摘要 随着前端技术的不断演进&#xff0c;组件化开发逐渐成为提升前端开发效率和代码可维护性的关键手段。本文将通过介绍一款Vue自定义的底部操作栏组件&#xff0c;探讨前端组件化开发的重要性、实践过程及其带来的优势。 一、引言 随着Web应用的日益复杂&#xff0c;传统的…

通义千问AI模型对接飞书机器人-模型配置(2-1)

一 背景 根据业务或者使用场景搭建自定义的智能ai模型机器人&#xff0c;可以较少我们人工回答的沟通成本&#xff0c;而且可以更加便捷的了解业务需求给出大家设定的业务范围的回答&#xff0c;目前基于阿里云的通义千问模型研究。 二 模型研究 参考阿里云帮助文档&#xf…

CSRF+XSS组合攻击实战

目录 0x01安装靶场 0x02分析功能点的请求接口&#xff0c;构造恶意请求 0x03寻找xss漏洞 0x01安装靶场 下载源码&#xff0c;解压到网站根目录 1.修改数据库配置文件 打开源码&#xff0c;进入到include目录下&#xff0c;打开数据库配置文件database.inc.php 将数据库的…

组内第一次会议

会议内容 1、科研平台使用 增删改查对文件 cp -r /root/mmdetection/dataset/ /root/user/wbzExperiment/mmdetection/ rm -r /root/user/yolov5-master tar -czvf test03.tar.gz test03/ unzip abc.zip 上传文件、解压文件&#xff1a;要在自己的目录中&#xff0c;进入…

Python函数基础:构建代码逻辑的基石(补全篇)

在前面我已经编写过一篇&#xff0c;python函数基础的博文&#xff0c;相信有基础的同学应该看得出来&#xff0c;那一篇的基础内容也是不全的&#xff0c;于是就有了这个补全篇。补全篇&#xff0c;补充了变量的作用与&#xff08;global与nonlocal&#xff09;、递归函数、闭…

acwing796-子矩阵的和-前缀和

s矩阵是全局变量&#xff0c;维度n*m,从1~n和 1~m存储元素【0】【0】~【0】【m】和【0】【0】~【n】【0】分别存储的都是0.s矩阵刚开始是存储输入的元素&#xff0c;后面用于存储前缀和。 s矩阵的意思是s【i】【j】表示从【0】【0】到【i】【j】为对角线的矩阵里面所有元素的和…

多类别支持向量机(Multi-class SVM)

多类别支持向量机&#xff08;Multi-class SVM&#xff09;是一种扩展二分类支持向量机以处理多类别分类问题的方法。常见的方法有“一对一”&#xff08;one-vs-one&#xff09;和“一对多”&#xff08;one-vs-rest&#xff09;。 一、数学模型理论推导 1.1 一对多&#xf…

新的铸造厂通过 PROFIBUS 技术实现完全自动化

钢铁生产商某钢以其在厚钢板类别中极高的产品质量而闻名。其原材料&#xff08;板坯连铸机&#xff09;在钢铁厂本地生产&#xff0c;该厂最近新建了一座垂直连铸厂。该项目的一个主要目标是从一开始就完全自动化这座新工厂和整个铸造过程&#xff0c;以高成本效率实现最佳产品…

用AI对抗AI:Fortinet解锁家电制造网络安全新密码

Fortinet盛大启幕《构筑垂直行业 网络安全防线》系列研讨会。首场研讨会聚焦于家电制造领域&#xff0c;以《利用AI打造家电制造网络安全的新质力》为主题。 Fortinet中国南区资深安全顾问黄志攀深入洞察家电制造行业的网络安全挑战&#xff0c;全面解析了Fortinet如何通过全栈…

数据库系统概论:数据库系统的锁机制

引言 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;数据作为一种共享资源&#xff0c;其并发访问的一致性和有效性是数据库必须解决的问题。锁机制通过对数据库中的数据对象&#xff08;如表、行等&#xff09;进行加锁&#xff0c;以确保在同…

基于python的去除图像内部填充

1 代码功能 该代码实现了一个图像处理的功能&#xff0c;具体来说是去除图像内部填充&#xff08;或更准确地说&#xff0c;是提取并显示图像中轮廓的外围区域&#xff0c;而忽略内部填充&#xff09;。以下是该功能的详细步骤&#xff1a; 读取图像&#xff1a;使用cv2.imread…

AWS服务器购买:如何选择合适的AWS云服务器

在当今数字化时代,云计算已成为企业IT基础设施的重要组成部分。作为全球领先的云服务提供商之一,亚马逊网络服务(AWS)提供了丰富多样的云服务器选项。然而,面对众多选择,如何为您的业务需求挑选最合适的AWS云服务器呢?我们结合九河云的分析来给你解答。 1. 明确业务需求 首先…

JVM调优:根据JVM自带工具定位问题(jps、jstat、Visual VM的使用)

JVM调优步骤 发现问题、定位问题、解决问题 发现问题 常见问题如下 GC频繁CPU负载过高内存溢出&#xff08;OOM&#xff09;内存泄露死锁程序响应时间较长 用JDK自带命令调优工具定位问题 jps&#xff08;java process status&#xff09;:查看正在运行的Java进程 基本语…