富文本编辑器(wangeditor)导入附件

1、问题描述

之前系统接入了一个富文本,采用的wangeditor。本来用着挺好,但是需求无止境。copy单个图片,可以黏贴进去。单个图片+文字,不行。

复制到mac备忘录,可以复制;

复制到企微邮箱,提示需要授权;

猜测:单个图片,大小可控,允许;多个图片,大小不可控。临时存储到本地,但此时涉及到授权问题,是否有权限获取本地目录。而此目录和使用何种office、系统都有关系。

加之,浏览器自身的安全,总不能随意加载任意的目录。

2、前端整理

原因:由于安全问题浏览器不允许粘贴 file://文件路径的图片

但前端也提供了一个可行的思路,可以自定义一个按钮,进行导入。

3、解决思路

尝试检索该问题,共同的提到(mammoth.js)

复制代码
Mammoth.js 是一个开源的 JavaScript 库,主要用于将 Microsoft Word 文档(.docx 文件)转换为 HTML、Markdown 或其他文本格式。它的核心目标是通过文档中的语义信息生成简洁、干净的 HTML,而不是完全复制文档的样式。

主要功能

文档转换:将 Word 文档转换为 HTML 或 Markdown 格式,支持标题、列表、表格、图片、链接等多种元素。

样式映射:支持自定义样式映射,可以将 Word 中的样式映射为 HTML 的样式。

在线预览:转换后的 HTML 可以嵌入网页中,实现 Word 文档的在线预览。

灵活配置:提供多种配置选项,例如在转换前对文档进行预处理。

使用场景

在网页中展示 Word 文档内容,例如用户协议、文档预览等。

将 Word 文档转换为 Markdown 或纯文本格式。

在前端项目中实现文档上传和内容展示。

优势

简洁的 HTML 输出:生成的 HTML 结构清晰,便于进一步处理。

跨平台支持:可以在浏览器和 Node.js 环境中使用。

开源且灵活:基于 BSD-2-Clause 许可协议开源,开发者可以根据需要进行定制。


Mammoth.js 是一个高效且实用的工具,特别适合需要将 Word 文档内容转换为网页格式的场景。

于是,尝试集成到我们的项目中去。接下来,主要的问题可能是:

  • wangeditor如何自定义按钮并实现内容回显
  • mammoth使用样例

官网扩展菜单样例:

wangEditor extend modal menu

4、代码实现

1、引入js

复制代码
<script charset="utf-8" src="/wangeditor/mammoth.browser.min.js"></script>

下载链接,带走不谢。

https://cdn.jsdelivr.net/npm/mammoth@1.4.8/mammoth.browser.min.js

2、参考官网实现按钮自定义

复制代码
const toolbarConfig = {
    excludeKeys:[
        'fullScreen',
        'emotion',
        'insertImage',
        'insertVideo'
    ],
    insertKeys: {
        index: 0, // 插入的位置,基于当前的 toolbarKeys
        keys: ['myMenu'],
    }
}

// Extend menu
class MyMenu {
    constructor() {
        this.title = '导入'
        // this.iconSvg = '<svg >...</svg>'
        this.tag = 'button'
        this.showModal = true
        this.modalWidth = 300
    }
    getValue(editor) {
        return ''
    }
    isActive(editor) {
        return false // or true
    }
    isDisabled(editor) {
        return false // or true
    }
    exec(editor, value) {
        // do nothing 什么都不用做
    }
    getModalPositionNode(editor) {
        return null // modal 依据选区定位
    }
    getModalContentElem(editor) {

        const $container = $('<div></div>')
        const inputId = `input-${Math.random().toString(16).slice(-8)}`
        const buttonId = `button-${Math.random().toString(16).slice(-8)}`

        window.btnFn = () => {
            // e.preventDefault()
            // const text = $(`#${inputId}`).val();
            // if (!text) return;

            convertWord(inputId);

            editor.restoreSelection(); // 恢复选区
            // editor.insertText(text);
            // editor.insertText(' ');
        }
        const $inputContainer = $(`<label class="babel-container">
        <span>导入附件</span>
        <input type="file" id="${inputId}">
      </label>`)
        const $buttonContainer = $(`<div class="button-container">
        <button id="${buttonId}" onclick="btnFn()">保存</button>
      </div>`)

        $container.append($inputContainer).append($buttonContainer)
        setTimeout(() => {
            $(`#${inputId}`).focus()
        })

        return $container[0]
    }
}
const myMenuConf = {
    key: 'myMenu',
    factory() {
        return new MyMenu()
    }
}
window.wangEditor.Boot.registerMenu(myMenuConf);

3、使用mammoth

复制代码
/**
 * 转换为html 并设置到文本框
 * @param inputId
 */
function convertWord(inputId) {
    const input = document.getElementById(inputId);
    const file = input.files[0];
    if(!notNull(file)){
        Message.info('附件为空');
        return;
    }

    const reader = new FileReader();
    reader.onload = (evt) => {
        mammoth.convertToHtml({ arrayBuffer: evt.target.result })
            .then((result) => {
                const html = result.value;
                editor.dangerouslyInsertHtml(html); // 将 HTML 插入到编辑器中
            });
    };
    reader.readAsArrayBuffer(file);
}

注,网站示例是参入文本,使用:editor.insertText(text),而此处是使用:editor.dangerouslyInsertHtml(html);

具体使用,可参考:编辑器 API | wangEditor

4、验证保存后,是否回显是否正常

5、遇到的问题

5.1、click事件无法绑定

复制代码
getModalContentElem(editor) {
    const $container = $('<div></div>')

    const inputId = `input-${Math.random().toString(16).slice(-8)}`
    const buttonId = `button-${Math.random().toString(16).slice(-8)}`

    const $inputContainer = $(`<label class="babel-container">
        <span>Text</span>
        <input type="text" id="${inputId}" value="hello world">
      </label>`)
    const $buttonContainer = $(`<div class="button-container">
        <button id="${buttonId}">insert text</button>
      </div>`)

    $container.append($inputContainer).append($buttonContainer)

    $container.on('click', `#${buttonId}`, e => {
      e.preventDefault()

      const text = $(`#${inputId}`).val()
      if (!text) return

      editor.restoreSelection() // 恢复选区
      editor.insertText(text)
      editor.insertText(' ')
    })

    setTimeout(() => {
      $(`#${inputId}`).focus()
    })

    return $container[0]

    // PS:也可以把 $container 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
  }
}

5.2、Form is larger than max length 200000 idea jetty

复制代码
2025-01-20 10:56:24.905 ERROR [qtp517268856-136]com.chaboshi.passport.client.interceptors.ClientCertificationInterceptor(123) | 权限拦截器发生异常
org.eclipse.jetty.http.BadMessageException: 400: Unable to parse form content
    at org.eclipse.jetty.server.Request.getParameters(Request.java:434)
    at org.eclipse.jetty.server.Request.getParameter(Request.java:1059)
    at com.chaboshi.passport.common.utils.URLParamHelper.getString(URLParamHelper.java:22)
    at com.chaboshi.passport.client.interceptors.ClientCertificationInterceptor.before(ClientCertificationInterceptor.java:41)
    at com.chaboshi.wf.mvc.InterceptorHandler.excuteActionBeforeInterceptors(InterceptorHandler.java:61)
    at com.chaboshi.wf.mvc.action.MethodAction.invoke(MethodAction.java:191)
    at com.chaboshi.wf.mvc.Dispatcher.service(Dispatcher.java:34)
    at com.chaboshi.wf.mvc.WFBootstrap.doFilter(WFBootstrap.java:64)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1618)
    at com.chaboshi.web.qa.filter.DMaskingFilter.doFilter(DMaskingFilter.java:93)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:549)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:602)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1610)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1369)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:489)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1580)
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1284)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:191)
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
    at org.eclipse.jetty.server.Server.handle(Server.java:501)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:556)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:272)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
    at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:375)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: Form is larger than max length 200000
    at org.eclipse.jetty.server.Request.extractFormParameters(Request.java:562)
    at org.eclipse.jetty.server.Request.extractContentParameters(Request.java:519)
    at org.eclipse.jetty.server.Request.getParameters(Request.java:430)
    ... 43 more
2025-01-20 10:56:24.911 INFO  [qtp517268856-136]STATS(28) | 1Min average time: 1078, qps: 0.15, concurrent: 0.17, total time: 19408ms, request count: 18.
2025-01-20 10:57:09.019 INFO  [statis]com.chaboshi.wf.mvc.toolbox.metrics.MetricsStatis(46) | HeapUsage : 27.05%, HeapUsed : 984.00M, HeapMax : 3641.00M

Caused by: java.lang.IllegalStateException: Form is larger than max length 200000 idea jetty 启动解决-CSDN博客

5.3、调整数据库长度

复制代码
org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'article_content' at row 1
### The error may involve com.chaboshi.web.qa.dao.detection.detectionTemplateV2.DetectionV2ComponentLocationPictureStoreDao.saveOrUpdateAll-Inline
### The error occurred while setting parameters
### SQL: UPDATE t_detection_v2_component_location_picture_store SET article_title = ?, add_time = ?, model_id = ?, article_content = ?, update_time = ?, pic = ? WHERE ( id = ?)
### Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'article_content' at row 1


alter table xx
    modify article_content longtext null comment '调整为 longtext |20250120|XX。';

5.4、待定.......(估计还有问题)

6、其他扩展

在 MySQL 中,TEXT 类型是一组用于存储变长文本数据的数据类型,根据存储容量的不同,分为 TINYTEXTTEXTMEDIUMTEXTLONGTEXT。以下是每种类型的特点和最大存储容量:

  1. TINYTEXT
  • 最大存储容量:255 字节(2^8 - 1)
  • 适用场景:存储非常短的文本内容,如标签、简短的备注等。
  • 特点:占用空间小,适合存储少量文本。
  1. TEXT
  • 最大存储容量:65,535 字节(2^16 - 1)
  • 适用场景:存储中等长度的文本内容,如新闻摘要、用户评论等。
  • 特点:适合大多数文本存储需求,但不适合存储非常大的文本。
  1. MEDIUMTEXT
  • 最大存储容量:16,777,215 字节(2^24 - 1,约 16MB)
  • 适用场景:存储较大的文本内容,如文章、产品说明书、HTML 页面等。
  • 特点:适合存储中等长度的文本,但需要注意性能优化。
  1. LONGTEXT
  • 最大存储容量:4,294,967,295 字节(2^32 - 1,约 4GB)
  • 适用场景:存储超大文本内容,如日志文件、富文本内容、HTML 页面等。
  • 特点:适合存储非常大的文本,但可能会影响性能。

字符集对存储容量的影响

存储容量不仅取决于数据类型,还与字符集有关。以下是不同字符集下的最大字符数量:

  • UTF-8
    • 每个字符最多占用 3 字节。
    • TEXT 最多存储约 21,845 个字符。
    • MEDIUMTEXT 最多存储约 5,592,405 个字符。
    • LONGTEXT 最多存储约 1,431,655,765 个字符。
  • UTF-8MB4(支持表情符号):
    • 每个字符最多占用 4 字节。
    • TEXT 最多存储约 16,383 个字符。
    • MEDIUMTEXT 最多存储约 4,194,303 个字符。
    • LONGTEXT 最多存储约 1,073,741,823 个字符。
  • latin1(单字节字符集):
    • 每个字符占用 1 字节。
    • TEXT 最多存储 65,535 个字符。
    • MEDIUMTEXT 最多存储 16,777,215 个字符。
    • LONGTEXT 最多存储 4,294,967,295 个字符。

选择合适的 TEXT 类型

在选择 TEXT 类型时,应根据实际需求选择合适的数据类型:

  • 如果文本内容较短(如标签、简短备注),使用 TINYTEXT
  • 如果文本内容中等长度(如用户评论、新闻摘要),使用 TEXT
  • 如果文本内容较大(如文章、HTML 页面),使用 MEDIUMTEXT
  • 如果文本内容非常大(如日志文件、富文本内容),使用 LONGTEXT
相关推荐
coding随想2 分钟前
JavaScript中的BOM:Window对象全解析
开发语言·javascript·ecmascript
難釋懷3 分钟前
TypeScript-webpack
javascript·webpack·typescript
Rockson7 分钟前
使用Ruby接入实时行情API教程
javascript·python
前端小巷子1 小时前
Web开发中的文件上传
前端·javascript·面试
上单带刀不带妹2 小时前
手写 Vue 中虚拟 DOM 到真实 DOM 的完整过程
开发语言·前端·javascript·vue.js·前端框架
前端风云志2 小时前
typescript结构化类型应用两例
javascript
杨进军3 小时前
React 创建根节点 createRoot
前端·react.js·前端框架
gnip3 小时前
总结一期正则表达式
javascript·正则表达式
爱分享的程序员4 小时前
前端面试专栏-算法篇:18. 查找算法(二分查找、哈希查找)
前端·javascript·node.js
翻滚吧键盘4 小时前
vue 条件渲染(v-if v-else-if v-else v-show)
前端·javascript·vue.js