当前位置: 首页 > article >正文

WangEdtior富文本编辑器

WangEdtior官网,提供了vue2、vue3的安装方式>>

原生js使用

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
    <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>

    <style>
        #editor—wrapper {
            border: 1px solid #ccc;
            z-index: 100;
            /* 按需定义 */
            width: 800px;
            margin: 10px auto;
        }


        #toolbar-container {
            border-bottom: 1px solid #ccc;
        }

        #editor-container {
            height: 150px;
        }

        p {
            /* 将p的margin改为0, 不然会出现改了#editor-container的高度,编辑器已经足够放下内容了,但是仍然会有滚动条出现的问题 */
            margin: 0 !important;
            line-height: 1.3em;
        }

        .w-e-scroll>div:first-child {
            padding-top: 15px; /* 将输入起始位置与“请输入内容对齐” */
        }
    </style>
</head>

<body>

    <div id="editor—wrapper">
        <!-- 工具栏 -->
        <div id="toolbar-container"></div>
        <!-- 编辑器 -->
        <div id="editor-container"></div>
    </div>

</body>
<script>

    const { createEditor, createToolbar } = window.wangEditor

    /* 编辑器配置 */
    const editorConfig = {
        placeholder: '请输入内容...',
        onChange(editor) {
            const html = editor.getHtml()
            console.log('editor content', html)
        }
    }

    /* 编辑器 */
    const editor = createEditor({
        selector: '#editor-container',
        html: '<p><br></p>',
        config: editorConfig,
        mode: 'default', // or 'simple'
    })

    /* 工具栏配置 */
    const toolbarConfig = {}

    /* 工具栏 */
    const toolbar = createToolbar({
        editor,
        selector: '#toolbar-container',
        config: toolbarConfig,
        mode: 'default', // or 'simple'
    })
</script>

</html>

vue2

安装wangEditor

npm install @wangeditor/editor --save
npm install @wangeditor/editor-for-vue --save

ArticleWang.vue

在这里插入图片描述

<style>
p {
    /* 将p的margin改为0, 不然会出现改了#editor-container的高度,编辑器已经足够放下内容了,但是仍然会有滚动条出现的问题 */
    margin: 0 !important;
    line-height: 1.3em;
}

.w-e-scroll>div:first-child {
    padding-top: 15px;
    /* 将输入起始位置与“请输入内容对齐” */
}
</style>
<template>
    <div style="border: 1px solid red;padding: 10px;">

        <!-- 按钮组 -->
        <div style="text-align: center;">
            <el-button type="success" plain @click="getHtml">getHtml</el-button>
            <el-button type="success" plain @click="setHtml">setHtml</el-button>
            <el-button type="success" plain @click="resetHtml">resetHtml</el-button>
            <el-button type="success" plain @click="getText">getText</el-button>
            <el-button type="success" plain @click="focus">focus</el-button>
            <el-button type="success" plain @click="saveHtml">saveHtml</el-button>
        </div>
        <div style="text-align: center;margin-top: 10px;">
            <el-button type="success" plain @click="playToolbar">playToolbar</el-button>
            <el-button type="success" plain @click="enableEdit">enableEdit</el-button>
        </div>

        <div style="text-align: center;margin-top: 10px;">
            <el-button type="success" plain @click="getUploadImageMenuConfig">getUploadImageMenuConfig</el-button>
        </div>

        <!-- 编辑器 -->
        <div style="border: 1px solid #ccc; width: 800px;margin: 10px auto 0;">

            <!-- 工具栏 -->
            <Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />

            <!-- 编辑器 -->
            <Editor style="height: 310px;" v-model="htmlContent" :defaultConfig="editorConfig" :mode="mode"
                @onCreated="onCreated" @onChange="onChange"/>
        </div>

        <div style="width: 800px;margin: 10px auto;" v-html="htmlContent"></div>
    </div>
</template>

<script>

import { Editor, Toolbar } from '@wangeditor/editor-for-vue'

/* 可以用来获取toolbar */
import { DomEditor } from '@wangeditor/editor'

/* 引入wangEditor的样式 */
import '@wangeditor/editor/dist/css/style.css'

import axiosInstance from '@/utils/request'

export default {
    name: 'ArticleWang',
    components: {
        Editor, Toolbar
    },
    data() {
        return {
            editor: null,                 /* 编辑器实例,在编辑器创建完成时,赋值 */
            htmlContent: '<p>hello</p>',  /* 编辑器内容 */
            toolbarConfig: {              /* 工具栏配置 */
                excludeKeys: [
                    'group-video'  // 排除视频这个菜单
                ]
            },
            editorConfig: {               /* 编辑器配置 */
                placeholder: '请输入内容...',
                // maxLength: 50, // 限制长度50
                
                MENU_CONF: {
                    emotion: { // 表情菜单
                        emotions: '😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉'.split(' ') // 数组
                    },
                    uploadImage: { // 图片上传
                        server: 'http://localhost:8083/article/uploadImg', /* 上传接口 */
                        fieldName: 'mfile', /* 文件参数名 */
                        "allowedFileTypes": [ /* 允许的文件类型 */
                            "image/*"
                        ],
                        maxFileSize: 10 * 1024 * 1024, /* 单个文件的最大体积限制,默认为 2M */
                        maxNumberOfFiles: 10, /* 最多可上传几个文件,默认为 100 */
                        // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
                        meta: {
                            token: 'xxx',
                            otherKey: 'yyy'
                        },

                        // 将 meta 拼接到 url 参数中,默认 false
                        metaWithUrl: false,

                        // 自定义增加 http  header
                        headers: {
                            Accept: 'application/json;charset=utf-8',
                            otherKey: 'xxx'
                        },

                        // 跨域是否传递 cookie ,默认为 false
                        withCredentials: true,

                        // 超时时间,默认为 10 秒
                        timeout: 5 * 1000, // 5 秒

                        // 自定义插入图片
                        /* 上传成功的返回格式:{
                                                "errno": 0, // 注意:值是数字,不能是字符串
                                                "data": {
                                                    "url": "xxx", // 图片 src ,必须
                                                    "alt": "yyy", // 图片描述文字,非必须
                                                    "href": "zzz" // 图片的链接,非必须
                                                }
                                             }
                           上传失败的返回格式:{
                                                "errno": 1, // 只要不等于 0 就行
                                                "message": "失败信息"
                                             }

                            如果你的服务端 response body 无法按照上文规定的格式,则无法插入图片,提示失败。
                            但你可以使用 customInsert 来自定义插入图片) */
                        /* customInsert(res, insertFn) {
                            // 从 res 中找到 url alt href ,然后插入图片
                            // insertFn(url, alt, href)
                            insertFn(`http://localhost:8083/img/${res.data}`)
                        }, */

                        /* 自己实现文件上传 */
                        customUpload(file, insertFn) {
                            // file 即选中的文件
                            // 自己实现上传,并得到图片 url alt href
                            // 最后插入图片

                            // 拿到 files 之后上传到文件服务器,然后向编辑框中插入对应的内容
                            let formData = new FormData()
                            formData.append('mfile', file)
                            axiosInstance({
                                url: "http://127.0.0.1:8083/article/uploadImg",
                                method: 'POST',
                                data: formData,
                                headers: { 'Content-Type': 'multipart/form-data' }
                            }).then(res => {
                                insertFn('http://127.0.0.1:8083/img/' + res, res)
                                console.log('插入成功');
                            })
                            
                        },
                    },
                    insertImage: {
                        /* 插入过的图片都会回调这个方法,最后保存富文本时,比较插入的记录和最终获取的image(editor.getElemsByType('image')),就能知道该删除哪些图片 */
                        onInsertedImage(imageNode) {
                            console.log('onInsertedImage->imageNode',imageNode);
                        }
                    }

                }
            },
            mode: 'default',
            editorEnabled: true,
        }
    },
    methods: {
        onCreated(editor) {/* 编辑器创建完毕时的回调函数。 */
            this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
        },
        onChange(editor) {/* 编辑器内容、选区变化时的回调函数 */
            console.log('有人在动编辑器');
        },  
        getHtml() {
            console.log(this.editor.getHtml());
        },
        setHtml() {
        	/* 这里设置的内容只能是从wangEditor中获取的内容哦 */
            this.editor.setHtml('<p>模拟 Ajax 异步设置内容 SETHTML</p>')
        },
        resetHtml() {
            this.editor.setHtml('')
        },
        getText() {
            console.log(this.editor.getText());
        },
        focus() {
            this.editor.focus() // 获取焦点,光标的位置默认会回到之前的位置闪烁
        },
        saveHtml() {
            axiosInstance({
                url: "http://127.0.0.1:8083/article/save",
                method: 'POST',
                data: { htmlContent: this.htmlContent },
            }).then(res => {
                console.log('成功');
            })
        },
        playToolbar() {
            const toolbar = DomEditor.getToolbar(this.editor)
            const curToolbarConfig = toolbar.getConfig()
            /* 当前菜单排序和分组, 目的是可以知道默认的工具栏配置 */
            console.log(curToolbarConfig.toolbarKeys)
            /* 可获取编辑器所有菜单 */
            console.log(this.editor.getAllMenuKeys());
            console.log(curToolbarConfig === this.toolbarConfig)  // false, 不是同一个
        },
        enableEdit() {
            /* 开启或禁用 编辑 */
            if (this.editorEnabled) {
                this.editor.disable()
            } else {
                this.editor.enable()
            }
            this.editorEnabled = !this.editorEnabled
        },
        getUploadImageMenuConfig() { /* 获取图片上传菜单的默认配置 */
            console.log(this.editor.getMenuConfig('uploadImage'));
            console.log(JSON.stringify(this.editor.getMenuConfig('uploadImage'))); // 获取 uploadImage菜单 的当前配置
            /*
             {         
                "server":"",
                "fieldName":"wangeditor-uploaded-image",
                "maxFileSize":2097152,
                "maxNumberOfFiles":100,
                "allowedFileTypes":[
                    "image/*"
                ],
                "meta":{

                },
                "metaWithUrl":false,
                "withCredentials":false,
                "timeout":10000,
                "base64LimitSize":0
            }
            */
        }
    },
    mounted() {
        // 模拟 ajax 请求,异步渲染编辑器
        setTimeout(() => {
            this.htmlContent = '<p>模拟 Ajax 异步设置内容 HTML</p>'
        }, 1500)
    },
    beforeDestroy() {
        const editor = this.editor
        if (editor == null) return
        editor.destroy() // 组件销毁时,及时销毁编辑器
    }
}
</script>

request.js

import axios from 'axios'
import router from '@/router'

const instance = axios.create({
    baseURL: 'http://localhost:8083',
    timeout: 60000,
    withCredentials: true /* 需要设置这个选项,axios发送请求时,才会携带cookie, 否则不会携带 */
})

// Add a request interceptor
instance.interceptors.request.use(function (config) {
    // Do something before request is sent


    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
instance.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    console.log('收到响应',response);

    if(response.data.code == 401) {
        router.push('/login')
    }

    return response.data.data;
  }, function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
  });

export default instance

WebConfig

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
                .addMapping("/**")
                .maxAge(3600)
                .allowCredentials(true)
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .exposedHeaders("token","Authorization")
        ;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/img/**")
                .addResourceLocations("file:/D:\\Projects\\vue-springboot\\src\\main\\resources\\static\\img\\");
    }
}

ArticleController

@RestController
@RequestMapping("article")
public class ArticleController {

    @PostMapping("uploadImg")
    public Result uploadImg(@RequestParam("mfile") MultipartFile mfile) throws IOException {
        String filename = mfile.getOriginalFilename();
        mfile.transferTo(new File("D:\\Projects\\vue-springboot\\src\\main\\resources\\static\\img\\"+filename));
        return Result.ok(filename);
    }
}

yml配置

server:
  port: 8083

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://localhost:3306/vue-springboot?serverTimezone=UTC&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

mybatis-plus:
  mapper-locations: classpath:/mapper/**.xml

http://www.kler.cn/a/7226.html

相关文章:

  • Halcon HImage 与 Qt QImage 的相互转换(修订版)
  • vue2+3 —— Day5/6
  • git push时报错! [rejected] master -> master (fetch first)error: ...
  • 跨平台WPF框架Avalonia教程 十五
  • 如何让手机ip变成动态
  • AI驱动的桌面笔记应用Reor
  • echarts圆形统计图与柱状图结合
  • 少儿编程 电子学会图形化编程等级考试Scratch二级真题解析(判断题)2022年12月
  • 将本地项目上传到远程仓库的步骤
  • 2.5.3 乘法
  • 【C++从0到1】14、C++中逻辑运算
  • chatGPT 又来帮忙了
  • C++ Primer第五版练习题冲冲冲
  • 卡顿优化小结
  • 计算机视觉的应用1-OCR分栏识别:两栏识别三栏识别都可以,本地部署完美拼接
  • 从零开始实现一个C++高性能服务器框架----Socket模块
  • 【分享】免梯子的GPT,玩 ChatGPT 的正确姿势
  • 《底层逻辑》读书笔记
  • python的元类
  • IDEA中查看源码点击Download Sources时出现Cannot download sources的问题复现及解决
  • C++ Primer第五版_第十章习题答案(31~40)
  • leetcode53:最大子数组和
  • Kotlin 基础语法
  • 【数据库运维】mysql备份恢复练习
  • 【nnunet】个人数据训练心得
  • STL容器篇之stack和queue