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

news2024/12/24 20:43:08
<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: '', // 替换为你的SecretId
            SecretKey: '', // 替换为你的SecretKey
        });
    },
    methods: {
        urlImageFormInput() {
            console.log("是否获取焦点")
        },
        uploadToCOS({ file, onProgress, onSuccess, onError }) {
            // 生成唯一的文件名
            const uniqueFileName = this.generateUniqueFileName(file.name);
            this.cos.uploadFile({
                Bucket: '', // 替换为你的Bucket
                Region: '', // 替换为你的Region
                Key: uniqueFileName, // 使用生成的唯一文件名
                Body: file,
                SliceSize: 1024 * 1024, // 分块大小,单位为字节,这里设置为1MB
                onProgress: 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;
            // 调整插入的图片大小并设置id
            this.$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.coloradmin.cn/o/1936741.html

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

相关文章

随手记:推荐vscode好用的几个小插件

原始用了挺久的插件&#xff0c;先上截图&#xff0c;以后有空再编辑&#xff1a; fittenCode 是一个AI小助手&#xff0c;相对来说很智能&#xff0c;你在vscode当中编写代码&#xff0c;甚至都可以知道你下一步知道干嘛&#xff0c;训练的还可以。而且还可以帮你起名字&…

spring MVC 简单的案例(2)用户登录

一、用户登录 1&#xff09;前端代码 index.html <!doctype html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport"content"widthdevice-width, user-scalableno, initial-scale1.0, maxim…

Filebeat k8s 部署(Deployment)采集 PVC 日志发送至 Kafka——日志处理(二)

文章目录 前言Filebeat Configmap 配置Filebeat Deployment验证总结 前言 在上篇文章中总结了 Django 日志控制台输出、文件写入按天拆分文件&#xff0c;自定义 Filter 增加 trace_id 以及过滤——日志处理&#xff08;一)&#xff0c;将日志以 JSON 格式写入日志文件。我们的…

【BUG】已解决:SyntaxError invalid syntax

SyntaxError invalid syntax 目录 SyntaxError invalid syntax 【常见模块错误】 错误原因&#xff1a; 解决办法&#xff1a; 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于…

CentOS 7 安装Jenkins2.346.1(war方式安装)

既然想要安装Jenkins&#xff0c;肯定是先要从官网解读所需环境配置信息&#xff0c;如需了解更多自行查阅 https://www.jenkins.io/doc/book/installing/linux/ JDK17&#xff0c;Maven3.9 安装 先从官网分别下载JDK17与Maven3.9 下载好之后上传至服务器、并解压&#xff1a…

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

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

vscode里的ts配置以及js配置不生效,可能是扩展程序被关了

在setting.json里检查了下typescript的配置发现配置都被置灰了。。。 最后在我不断重复尝试重启vscode的时候&#xff0c;右下角总是弹出一个扩展程序未使用&#xff0c;可能会影响编辑器使用&#xff0c;我点了一下开启&#xff0c;nnd&#xff0c;原来是builtin typescript-…

Leetcode1305.两颗二叉搜索树中的所有元素

1.题目要求: 给你 root1 和 root2 这两棵二叉搜索树。请你返回一个列表&#xff0c;其中包含 两棵树 中的所有整数并按 升序 排序。.2.思路: 我这个方法采用的是设立一个数组&#xff0c;然后用前序遍历把值存入数组中&#xff0c;然后用qsort给它排序 3.代码: /*** Definiti…

Spring Boot2(Spring Boot 的Web开发 springMVC 请求处理 参数绑定 常用注解 数据传递)

目录 一、Spring Boot 的Web开发 1. 静态资源映射规则 2. enjoy模板引擎 二、springMVC 1. springMVC-请求处理 测试&#xff1a; 以post方式请求 限制请求携带的参数 GetMapping 查询 PostMapping 新增 DeleteMapping删除 PutMapping 修改 2. springMVC-参…

5.java操作RabbitMQ-简单队列

1.引入依赖 <!--rabbitmq依赖客户端--> <dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId> </dependency> 操作文件的依赖 <!--操作文件流的一个依赖--> <dependency><groupId>c…

【AI教程-吴恩达讲解Prompts】第1篇 - 课程简介

文章目录 简介Prompt学习相关资源 两类大模型原则与技巧 简介 欢迎来到面向开发者的提示工程部分&#xff0c;本部分内容基于吴恩达老师的《Prompt Engineering for Developer》课程进行编写。《Prompt Engineering for Developer》课程是由吴恩达老师与 OpenAI 技术团队成员 I…

vue学习笔记(十)——Vuex(状态管理,组件间共享数据)

1. vuex基础-介绍 1.1 为什么会有Vuex ? 在现代 Web 开发复杂多变的需求驱动之下&#xff0c;组件化开发已然成为了事实上的标准。然而大多数场景下的组件都并不是独立存在的&#xff0c;而是相互协作共同构成了一个复杂的业务功能。 组件间的通信成为了必不可少的开发需求。…

Android IjkPlayer内核编译记(一)so库编译使用

转载请注明出处&#xff1a;https://blog.csdn.net/kong_gu_you_lan/article/details/140528831 本文出自 容华谢后的博客 0.写在前面 最近在搞RTMP协议直播拉流的功能&#xff0c;使用了B站开源的IjkPlayer作为播放器内核&#xff0c;在网络不好的情况下延迟会比较高&#xf…

Guns v7.3.0:基于 Vue3、Antdv 和 TypeScript 打造的开箱即用型前端框架

摘要 本文深入探讨了Guns v7.3.0前端项目&#xff0c;该项目是基于Vue3、Antdv和TypeScript的前端框架&#xff0c;以Vben Admin的脚手架为基础进行了改造。文章分析了Guns 7.3.0的技术特点&#xff0c;包括其使用Vue3、vite2和TypeScript等最新前端技术栈&#xff0c;以及提供…

环信IM x 亚马逊云科技,助力出海企业实现可靠通讯服务

随着全球化进程的加速&#xff0c;越来越多的企业选择出海&#xff0c;拓展国际市场。然而&#xff0c;面对不同国家和地区的用户&#xff0c;企业在即时通讯方面遇到了诸多挑战。为了帮助企业克服这些困难&#xff0c;环信IM与亚马逊云科技强强联手&#xff0c;共同推出了一套…

蚂蚁集团推出EchoMimic:能通过音频和面部标志生成逼真的肖像动画视频

蚂蚁集团最近推出了一项名为EchoMimic的新技术。能通过音频和面部标志生成逼真的肖像动画视频&#xff0c;让你的声音和面部动作被完美复制到视频中&#xff0c;效果自然如照镜子。 EchoMimic不仅可以单独使用音频或面部标志点生成肖像视频&#xff0c;也可以将两者结合&#…

CSPVD 智慧工地安全帽安全背心检测开发包

CSPVD SDK适用于为各种智慧工地应用增加安全防护穿戴合规的检测能力&#xff0c;能够有效检测未戴安全帽和未穿 安全背心的人员&#xff0c;提供Web API和原生API。官方下载&#xff1a;CSPVD工地安全防护检测 1、目录组织 CSPVD开发包的目录组织说明如下&#xff1a; xlpr_…

价格战再起:OpenAI 发布更便宜、更智能的 GPT-4o Mini 模型|TodayAI

OpenAI 今日推出了一款名为 GPT-4o Mini 的新模型&#xff0c;这款模型较轻便且成本更低&#xff0c;旨在为开发者提供一个经济实惠的选择。与完整版模型相比&#xff0c;GPT-4o mini 在成本效益方面表现卓越&#xff0c;价格仅为每百万输入 tokens 15 美分和每百万输出 tokens…

喜报!极限科技再获国家发明专利:《一种超大规模分布式集群架构的数据处理方法》,引领大数据处理技术创新

近日&#xff0c;极限数据&#xff08;北京&#xff09;科技有限公司&#xff08;简称&#xff1a;极限科技&#xff09;传来喜讯&#xff0c;公司再次斩获国家发明专利授权。这项名为"一种超大规模分布式集群架构的数据处理方法"的专利&#xff08;专利号&#xff1…

html+canvas 实现签名功能-手机触摸

手机上的效果图 需要注意&#xff0c;手机触摸和鼠标不是一个事件&#xff0c;不能通用&#xff0c;上一篇是关于使用鼠标的样例 相关代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewpo…