|
|
@@ -73,10 +73,9 @@
|
|
|
spellcheck="false"
|
|
|
@input="onInputChange"
|
|
|
/>
|
|
|
- <!-- 实时预览 -->
|
|
|
+ <!-- 实时预览:由 renderPreview 方法直接操作 DOM,避免 MathJax 缓存问题 -->
|
|
|
<div class="fm-preview" ref="fmPreview">
|
|
|
- <span v-if="latexInput">$${{ latexInput }}$$</span>
|
|
|
- <span v-else class="fm-preview-empty">公式预览</span>
|
|
|
+ <span class="fm-preview-empty">公式预览</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -141,9 +140,9 @@ export default {
|
|
|
value: { type: String, default: '' },
|
|
|
disabled: { type: Boolean, default: false },
|
|
|
height: { type: [Number, String], default: 400 },
|
|
|
- // 图片/附件上传接口地址
|
|
|
+ // 富文本纯文本内容最大长度,0 表示不限制
|
|
|
+ maxLength: { type: Number, default: 0 },
|
|
|
uploadUrl: { type: String, default: '' },
|
|
|
- // 上传请求头,如 { Authorization: 'Bearer xxx' }
|
|
|
uploadHeaders: { type: Object, default: () => ({}) }
|
|
|
},
|
|
|
data() {
|
|
|
@@ -288,6 +287,12 @@ export default {
|
|
|
toolbar_mode: 'wrap',
|
|
|
quickbars_selection_toolbar: 'bold italic underline | quicklink h2 h3 blockquote',
|
|
|
quickbars_insert_toolbar: false,
|
|
|
+ // 插入/编辑图片弹窗:隐藏上传 tab,只保留 URL 输入
|
|
|
+ image_uploadtab: false,
|
|
|
+ // 插入/编辑媒体弹窗:只保留"一般"tab,隐藏内嵌和高级
|
|
|
+ media_live_embeds: false,
|
|
|
+ media_alt_source: false,
|
|
|
+ media_poster: false,
|
|
|
font_formats:
|
|
|
'微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;' +
|
|
|
'宋体=SimSun,serif;黑体=SimHei,sans-serif;楷体=KaiTi,serif;' +
|
|
|
@@ -336,6 +341,22 @@ export default {
|
|
|
}
|
|
|
}),
|
|
|
setup: (editor) => {
|
|
|
+ // 字数限制:在 input 事件后检查纯文本长度
|
|
|
+ editor.on('input keydown', (e) => {
|
|
|
+ if (!this.maxLength) return
|
|
|
+ // keydown 时只拦截非删除类按键
|
|
|
+ if (e.type === 'keydown') {
|
|
|
+ const allowKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']
|
|
|
+ if (allowKeys.includes(e.key) || e.ctrlKey || e.metaKey) return
|
|
|
+ }
|
|
|
+ const text = editor.getContent({ format: 'text' })
|
|
|
+ if (text.length >= this.maxLength) {
|
|
|
+ if (e.type === 'keydown') {
|
|
|
+ e.preventDefault()
|
|
|
+ if (this.$message) this.$message.warning(`内容长度不能超过 ${this.maxLength} 个字符`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
editor.ui.registry.addButton('latex', {
|
|
|
text: '公式',
|
|
|
tooltip: '插入公式',
|
|
|
@@ -416,7 +437,25 @@ export default {
|
|
|
computed: {
|
|
|
editorContent: {
|
|
|
get() { return this.value },
|
|
|
- set(val) { this.$emit('input', val) }
|
|
|
+ set(val) {
|
|
|
+ if (this.maxLength > 0) {
|
|
|
+ const text = val.replace(/<[^>]*>/g, '')
|
|
|
+ if (text.length > this.maxLength) {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this._editor) {
|
|
|
+ this._editor.setContent(this.value)
|
|
|
+ this._editor.selection.select(this._editor.getBody(), true)
|
|
|
+ this._editor.selection.collapse(false)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (this.$message) {
|
|
|
+ this.$message.warning(`内容长度不能超过 ${this.maxLength} 个字符`)
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.$emit('input', val)
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
@@ -458,7 +497,15 @@ export default {
|
|
|
renderPreview() {
|
|
|
this.$nextTick(() => {
|
|
|
const el = this.$refs.fmPreview
|
|
|
- if (!el || !window.MathJax) return
|
|
|
+ if (!el) return
|
|
|
+ const latex = this.latexInput.trim()
|
|
|
+ if (!latex) {
|
|
|
+ el.innerHTML = '<span class="fm-preview-empty">公式预览</span>'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 每次重置 innerHTML,强制 MathJax 重新解析,避免缓存导致预览不更新
|
|
|
+ el.innerHTML = `$$${latex}$$`
|
|
|
+ if (!window.MathJax) return
|
|
|
window.MathJax.typesetClear([el])
|
|
|
window.MathJax.typesetPromise([el]).catch(() => {})
|
|
|
})
|
|
|
@@ -550,7 +597,7 @@ export default {
|
|
|
background: #fff;
|
|
|
border-radius: 6px;
|
|
|
box-shadow: 0 6px 30px rgba(0,0,0,0.2);
|
|
|
- width: 720px;
|
|
|
+ width: 1000px;
|
|
|
max-width: 96vw;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
@@ -583,9 +630,9 @@ export default {
|
|
|
.fm-toolbar {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 4px;
|
|
|
- padding: 8px 12px;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ gap: 3px;
|
|
|
+ padding: 6px 10px;
|
|
|
border-bottom: 1px solid #e4e4e4;
|
|
|
background: #fafafa;
|
|
|
}
|
|
|
@@ -637,10 +684,10 @@ export default {
|
|
|
|
|
|
/* 符号格子 */
|
|
|
.fm-symbol-grid {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(9, 28px);
|
|
|
- grid-template-rows: repeat(2, 26px);
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: nowrap;
|
|
|
gap: 2px;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
.fm-sym-btn {
|
|
|
width: 28px;
|
|
|
@@ -661,20 +708,24 @@ export default {
|
|
|
/* 分类按钮组 */
|
|
|
.fm-category-row {
|
|
|
display: flex;
|
|
|
- gap: 4px;
|
|
|
- flex-wrap: wrap;
|
|
|
+ gap: 3px;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
.fm-cat-wrap { position: relative; }
|
|
|
.fm-cat-btn {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
- padding: 3px 8px;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0 8px;
|
|
|
border: 1px solid #ccc;
|
|
|
border-radius: 4px;
|
|
|
background: #fff;
|
|
|
cursor: pointer;
|
|
|
min-width: 44px;
|
|
|
+ height: 60px;
|
|
|
+ box-sizing: border-box;
|
|
|
user-select: none;
|
|
|
gap: 1px;
|
|
|
}
|