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使用样例
官网扩展菜单样例:
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
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
类型是一组用于存储变长文本数据的数据类型,根据存储容量的不同,分为 TINYTEXT
、TEXT
、MEDIUMTEXT
和 LONGTEXT
。以下是每种类型的特点和最大存储容量:
- TINYTEXT
- 最大存储容量:255 字节(2^8 - 1)
- 适用场景:存储非常短的文本内容,如标签、简短的备注等。
- 特点:占用空间小,适合存储少量文本。
- TEXT
- 最大存储容量:65,535 字节(2^16 - 1)
- 适用场景:存储中等长度的文本内容,如新闻摘要、用户评论等。
- 特点:适合大多数文本存储需求,但不适合存储非常大的文本。
- MEDIUMTEXT
- 最大存储容量:16,777,215 字节(2^24 - 1,约 16MB)
- 适用场景:存储较大的文本内容,如文章、产品说明书、HTML 页面等。
- 特点:适合存储中等长度的文本,但需要注意性能优化。
- 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
。