From 620a35f9588ed11c065ef767374d5455c9f8fdc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=8B=E4=BA=8E=E5=88=9D=E8=A7=81?= <752204717@qq.com> Date: Fri, 3 Apr 2026 09:05:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/js/commonJS.js | 7 +- src/components/common/langs/en.js | 7 +- src/components/common/langs/zh.js | 7 +- src/components/page/GenerateCharts.vue | 142 ++++++++-- .../page/components/Tinymce/index.vue | 218 ++++++++++++--- .../page/components/table/content.vue | 17 +- src/components/page/components/table/word.vue | 252 ++++++++++++++++-- src/components/page/crawlTaskMonitor.vue | 45 +++- .../page/editPublicRefTableOnly.vue | 20 +- 9 files changed, 609 insertions(+), 106 deletions(-) diff --git a/src/common/js/commonJS.js b/src/common/js/commonJS.js index 60bb1ab..53159c2 100644 --- a/src/common/js/commonJS.js +++ b/src/common/js/commonJS.js @@ -226,8 +226,7 @@ export default { str = this.transformHtmlString(processedContent, 'table',{ keepBr: true }) - console.log("🚀 ~ extractContentWithoutOuterSpan888888 ~ str:", str); - + // 创建一个临时的 DOM 元素来解析 HTML const div = document.createElement('div'); @@ -917,9 +916,9 @@ str = str.replace(regex, function (match, content, offset, fullString) { }); // 2. 删除所有不需要的标签 (除 `strong`, `em`, `sub`, `sup`, `b`, `i` 外的所有标签) if (type == 'table') { - inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img|myfigure|mytable|myh3))[^>]+>/g, ''); // 删除不需要的标签 + inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img|myfigure|mytable|myh3|autocite))[^>]+>/g, ''); // 删除不需要的标签 } else { - inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|myfigure|mytable|myh3))[^>]+>/g, ''); // 删除不需要的标签 + inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|myfigure|mytable|myh3|autocite))[^>]+>/g, ''); // 删除不需要的标签 } diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js index 30e97d0..fd95bc2 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -1152,12 +1152,17 @@ colTitle: 'Template title', placeholder: 'Please enter email content' }, wordCite: { + noRefs: 'Reference list is not loaded yet. Please wait or refresh the page.', notFoundById: 'No reference found for citation ID {id}', selectRef: 'Select Reference', reference: 'Reference', cancel: 'Cancel', confirm: 'Confirm', - remove: 'Remove' + remove: 'Remove', + selected: 'Selected', + modifyRef: 'Edit citation', + removeRefTag: 'Remove citation', + citeUpdateFail: 'Could not update the citation in the text. Try again or use Edit.' } diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index a6d16ba..5cf7e8f 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -1137,12 +1137,17 @@ const zh = { placeholder: '请输入邮件内容' }, wordCite: { + noRefs: '参考文献列表尚未加载,请稍候再试或刷新页面', notFoundById: '未查询到编号{id}相关参考文献', selectRef: '选择参考文献', reference: '参考文献', cancel: '取消', confirm: '确认', - remove: '移除' + remove: '移除', + selected: '已选择', + modifyRef: '修改引用', + removeRefTag: '移除引用', + citeUpdateFail: '未能更新正文中的引用标签,请重试或进入编辑修改' } diff --git a/src/components/page/GenerateCharts.vue b/src/components/page/GenerateCharts.vue index 1e0601c..583a329 100644 --- a/src/components/page/GenerateCharts.vue +++ b/src/components/page/GenerateCharts.vue @@ -122,6 +122,7 @@ @onEditTitle="onEditTitle" @onAddRow="onAddRow" @changeComment="changeComment" + @openRefSelector="handleOpenRefSelectorFromManuscript" style="width: calc(100%); height: calc(100%)" :style="`100%`" > @@ -447,34 +448,46 @@ +
+ {{ $t('wordCite.selected') }}: [{{ refPreviewLabel }}] +
- - + + - + {{ $t('wordCite.cancel') }} - {{ $t('wordCite.remove') }} - {{ $t('wordCite.confirm') }} + {{ $t('wordCite.remove') }} + {{ $t('wordCite.confirm') }}
@@ -616,8 +629,8 @@ export default { chanFerForm: [], chanFerFormRepeatList: [], refSelectorVisible: false, - refSelectorCurrentRefId: null, - refSelectedRow: null, + refSelectorIsEdit: false, + refSelectedRows: [], refSelectorSource: 'commonContent' }; }, @@ -628,6 +641,29 @@ export default { editPublicRefTableOnly }, computed: { + refPreviewLabel() { + if (!this.refSelectedRows.length) return ''; + const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : []; + const nums = this.refSelectedRows.map((row) => { + const idx = refs.findIndex((r) => r.p_refer_id === row.p_refer_id); + return idx >= 0 ? idx + 1 : null; + }).filter((n) => n != null); + if (!nums.length) return '?'; + const sorted = [...new Set(nums)].sort((a, b) => a - b); + const result = []; + let i = 0; + while (i < sorted.length) { + let j = i; + while (j < sorted.length - 1 && sorted[j + 1] === sorted[j] + 1) j++; + if (j - i >= 2) { + result.push(`${sorted[i]}–${sorted[j]}`); + } else { + for (let k = i; k <= j; k++) result.push(sorted[k]); + } + i = j + 1; + } + return result.join(', '); + }, catalogueContent() { const base = Array.isArray(this.Main_List) ? this.Main_List : []; if (!Array.isArray(this.chanFerForm) || this.chanFerForm.length === 0) return base; @@ -721,41 +757,89 @@ export default { if (this.$refs.editPublicRefTableOnly) { this.$refs.editPublicRefTableOnly.init(); } + if (this.editVisible && this.$refs.commonContent && this.$refs.commonContent.refreshAutociteDisplay) { + this.$refs.commonContent.refreshAutociteDisplay(); + } + if (this.addContentVisible && this.$refs.addContent && this.$refs.addContent.refreshAutociteDisplay) { + this.$refs.addContent.refreshAutociteDisplay(); + } }); }) .catch((err) => { console.log(err); }); }, - handleOpenRefSelector(data) { - this.refSelectorCurrentRefId = data && data.currentRefId ? data.currentRefId : null; - this.refSelectedRow = null; - if (this.editVisible) { + handleOpenRefSelectorFromManuscript(data) { + this.handleOpenRefSelector(data, 'manuscriptAutocite'); + }, + handleOpenRefSelector(data, sourceOverride) { + const currentIds = data && Array.isArray(data.currentRefIds) ? data.currentRefIds : []; + this.refSelectorIsEdit = currentIds.length > 0; + this.refSelectedRows = []; + if (sourceOverride === 'manuscriptAutocite' || (data && data.source === 'manuscript')) { + this.refSelectorSource = 'manuscriptAutocite'; + } else if (this.editVisible) { this.refSelectorSource = 'commonContent'; } else if (this.addContentVisible) { this.refSelectorSource = 'addContent'; } this.refSelectorVisible = true; + this.$nextTick(() => { + const table = this.$refs.refSelectorTable; + if (table) { + table.clearSelection(); + if (currentIds.length > 0) { + const idSet = new Set(currentIds.map(String)); + this.chanFerForm.forEach((row) => { + if (idSet.has(String(row.p_refer_id))) { + table.toggleRowSelection(row, true); + } + }); + } + } + }); }, - handleRefCurrentChange(row) { - this.refSelectedRow = row; + handleRefSelectionChange(rows) { + this.refSelectedRows = rows; }, handleConfirmRefCite() { - if (!this.refSelectedRow) return; + if (this.refSelectedRows.length === 0) return; + const ids = this.refSelectedRows.map((r) => r.p_refer_id); + if (this.refSelectorSource === 'manuscriptAutocite') { + const w = this.$refs.commonWord; + if (w && typeof w.applyManuscriptAutocite === 'function') { + w.applyManuscriptAutocite(ids); + } + this.refSelectorVisible = false; + this.refSelectedRows = []; + return; + } const ref = this.$refs[this.refSelectorSource]; if (ref) { - ref.insertAutocite(this.refSelectedRow.p_refer_id); + ref.insertAutocite(ids); } this.refSelectorVisible = false; - this.refSelectedRow = null; + this.refSelectedRows = []; }, handleRemoveRefCite() { + const idsToStrip = (this.refSelectedRows || []).map((r) => r.p_refer_id); + if (this.refSelectorSource === 'manuscriptAutocite') { + const w = this.$refs.commonWord; + if (w && typeof w.stripManuscriptAutociteIds === 'function') { + w.stripManuscriptAutociteIds(idsToStrip); + } + this.refSelectorVisible = false; + this.refSelectedRows = []; + return; + } const ref = this.$refs[this.refSelectorSource]; - if (ref) { + if (ref && typeof ref.stripAutociteIds === 'function') { + ref.stripAutociteIds(idsToStrip); + } else if (ref) { ref.removeAutocite(); } this.refSelectorVisible = false; - this.refSelectedRow = null; + this.refSelectedRows = []; }, ChanFerMashUp(e) { this.$api @@ -1825,6 +1909,16 @@ export default { this.editVisible = true; this.currentId = dataId; + if (this.p_article_id) { + this.fetchReferList(); + } + this.$nextTick(() => { + this.$nextTick(() => { + if (this.$refs.commonContent && this.$refs.commonContent.refreshAutociteDisplay) { + this.$refs.commonContent.refreshAutociteDisplay(); + } + }); + }); } }, onAddContent(dataId) { diff --git a/src/components/page/components/Tinymce/index.vue b/src/components/page/components/Tinymce/index.vue index cfa1c18..185ded0 100644 --- a/src/components/page/components/Tinymce/index.vue +++ b/src/components/page/components/Tinymce/index.vue @@ -147,7 +147,7 @@ export default { handler(val) { if (!this.hasChange && this.hasInit) { this.$nextTick(() => { - window.tinymce.get(this.tinymceId).setContent(val); + this.handleSetContent(val); }); } }, @@ -155,9 +155,11 @@ export default { }, chanFerForm: { handler() { - if (this.editorInstance) { - this.renderAutociteInEditor(this.editorInstance); - } + this.$nextTick(() => { + if (this.editorInstance) { + this.renderAutociteInEditor(this.editorInstance); + } + }); }, deep: true } @@ -183,10 +185,52 @@ export default { this.destroyTinymce(); }, methods: { + /** 与正文一致:两项连续写作 1, 2;三项及以上连续写作 1-3 */ + formatCiteNumbers(nums) { + if (!nums || !nums.length) return ''; + const sorted = [...new Set(nums)].sort((a, b) => a - b); + const result = []; + let i = 0; + while (i < sorted.length) { + let j = i; + while (j < sorted.length - 1 && sorted[j + 1] === sorted[j] + 1) j++; + if (j - i >= 2) { + result.push(`${sorted[i]}–${sorted[j]}`); + } else { + for (let k = i; k <= j; k++) result.push(sorted[k]); + } + i = j + 1; + } + return result.join(', '); + }, + parseAutociteDataIds(attr) { + if (!attr || typeof attr !== 'string') return []; + return attr + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + }, + /** 与 word.vue renderCiteLabels:tooltip 行按引用序号排序 */ + sortAutociteIdsByCiteNumber(ids) { + const uniq = [...new Set(ids.map(String))]; + const map = this.citeMap || {}; + return uniq.sort((a, b) => { + const na = map[a]; + const nb = map[b]; + const ha = na != null && na !== ''; + const hb = nb != null && nb !== ''; + if (ha && hb) return Number(na) - Number(nb); + if (ha) return -1; + if (hb) return 1; + return String(a).localeCompare(String(b)); + }); + }, renderAutociteInEditor(ed) { const body = ed.getBody(); if (!body) return; - const autocites = body.querySelectorAll('autocite'); + const allAutocites = Array.from(body.querySelectorAll('autocite')); + if (!allAutocites.length) return; + const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : []; const refMap = {}; refs.forEach((item) => { @@ -194,41 +238,65 @@ export default { if (key) refMap[key] = item; }); - autocites.forEach((el) => { + allAutocites.forEach((el) => { ed.dom.setAttrib(el, 'contenteditable', 'false'); - const dataId = el.getAttribute('data-id'); - const num = this.citeMap[String(dataId)]; - el.textContent = num != null ? `[${num}]` : '[?]'; + el.style.display = ''; + const ids = this.parseAutociteDataIds(el.getAttribute('data-id')); + const sortedIds = this.sortAutociteIdsByCiteNumber(ids); + const nums = sortedIds.map((id) => this.citeMap[String(id)]).filter((n) => n != null); + const label = nums.length > 0 ? this.formatCiteNumbers(nums) : '?'; - const ref = refMap[String(dataId)]; - if (ref) { - const content = ref.refer_frag || [ref.author, ref.title, ref.joura, ref.dateno].filter(Boolean).join(' ').trim() || ''; - const doi = ref.doilink || ref.isbn || ref.doi || ''; - const tip = `[${num || '?'}] ${content}${doi ? '\nDOI: ' + doi : ''}`; - ed.dom.setAttrib(el, 'title', tip); - } else { - ed.dom.setAttrib(el, 'title', this.$t('wordCite.notFoundById', { id: num != null ? num : dataId })); + const lines = sortedIds.map((id) => { + const no = this.citeMap[String(id)] != null ? this.citeMap[String(id)] : '?'; + const ref = refMap[String(id)]; + if (!ref) return this.$t('wordCite.notFoundById', { id: no === '?' ? id : no }); + const content = ref.refer_frag || [ref.author, ref.title, ref.joura, ref.dateno].filter(Boolean).join(' ').trim() || '[?]'; + const doi = ref.doilink || ref.isbn || ref.doi || '[?]'; + return `[${no}] ${content}\nDOI: ${doi}`; + }); + + el.textContent = `[${label}]`; + ed.dom.setAttrib(el, 'title', lines.join('\n')); + }); + this.padAutociteCaretPlaceholder(ed); + }, + /** 段尾不可编辑节点后浏览器/TinyMCE 容易把后续输入新开

,在引用后补零宽空格让光标留在同一段 */ + padAutociteCaretPlaceholder(ed) { + const doc = ed.getDoc(); + const body = doc.body; + if (!body) return; + body.querySelectorAll('autocite').forEach((el) => { + const next = el.nextSibling; + if (next === null) { + el.parentNode.appendChild(doc.createTextNode('\u200b')); + return; + } + if (next.nodeType === 3) { + if (next.textContent === '\u200b') return; + if (next.textContent === '') { + next.textContent = '\u200b'; + } } }); }, - insertAutocite(refId) { + insertAutocite(refIds) { const ed = this.editorInstance; if (!ed) return; + const ids = (Array.isArray(refIds) ? refIds : [refIds]).map(String); + const dataId = ids.join(','); + const escaped = dataId.replace(/"/g, '"'); + if (this._editingAutocite) { - this._editingAutocite.setAttribute('data-id', refId); - const num = this.citeMap[String(refId)]; - this._editingAutocite.textContent = num != null ? `[${num}]` : '[?]'; + this._editingAutocite.setAttribute('data-id', dataId); this._editingAutocite = null; ed.fire('change'); } else { if (this._refBookmark) { ed.selection.moveToBookmark(this._refBookmark); } - const num = this.citeMap[String(refId)]; - const label = num != null ? `[${num}]` : '[?]'; - const html = `${label}`; - ed.insertContent(html); + ed.insertContent(`​`); } + this.renderAutociteInEditor(ed); }, removeAutocite() { const ed = this.editorInstance; @@ -237,10 +305,45 @@ export default { this._editingAutocite = null; ed.fire('change'); }, + /** + * 从当前编辑的 autocite 中去掉指定 p_refer_id;去掉后无 id 则删除整段标签。 + * ids 为空:删除整段(与 removeAutocite 一致)。 + */ + stripAutociteIds(idsToRemove) { + const ed = this.editorInstance; + if (!ed || !this._editingAutocite) return; + const remove = new Set((idsToRemove || []).map((id) => String(id))); + const el = this._editingAutocite; + this._editingAutocite = null; + if (remove.size === 0) { + ed.dom.remove(el); + ed.fire('change'); + return; + } + const parts = this.parseAutociteDataIds(el.getAttribute('data-id') || ''); + const remaining = parts.filter((id) => !remove.has(String(id))); + if (remaining.length === 0) { + ed.dom.remove(el); + } else { + const sorted = this.sortAutociteIdsByCiteNumber(remaining); + el.setAttribute('data-id', sorted.join(',')); + this.renderAutociteInEditor(ed); + } + ed.fire('change'); + }, + /** TinyMCE 会剔除「空」的行内标签;空 autocite 必须在入编辑器前占位,否则合并引用 [1–3] 等整段消失 */ + normalizeAutociteHtmlForEditor(html) { + if (!html || typeof html !== 'string') return html; + let out = html.replace(/]*)>\s*<\/autocite>/gi, '​'); + // 与 getSafeContent 标签边界逻辑一致:autocite 左右水平空白改为  ,不换行符(避免吃掉段间 \n) + out = out.replace(/[^\S\r\n]+(?=)[^\S\r\n]+/gi, ' '); + return out; + }, handleSetContent(val) { if (!this.editorInstance) return; - let finalContent = val || ''; + let finalContent = this.normalizeAutociteHtmlForEditor(val || ''); // 你的业务逻辑:自动包裹

标签 if (!finalContent.includes('wordTableHtml') && !finalContent.startsWith('

')) { finalContent = '

' + finalContent + '

'; @@ -394,7 +497,10 @@ export default { }, getDetail(val) { if (this.hasInit == true) { - this.$nextTick(() => window.tinymce.get(this.tinymceId).setContent(val)); + this.$nextTick(() => { + const ed = window.tinymce.get(this.tinymceId); + if (ed) ed.setContent(this.normalizeAutociteHtmlForEditor(val || '')); + }); } }, //将字符串添加到富文本编辑器中 @@ -505,7 +611,7 @@ export default { return new Blob([u8arr], { type: mime }); }, formatHtml(val) { - const rawValue = val || ''; // 处理 null + const rawValue = this.normalizeAutociteHtmlForEditor(val || ''); // 须先于 cleanEmptyTags,否则空 autocite 被删 const cleanEmptyTags = /<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g; const replaceSpaces = /\s+(?=<)|(?<=>)\s+/g; const removeBr = //gi; // 移除所有 br 标签 @@ -530,7 +636,7 @@ export default { } }, getSafeContent(val) { - const rawValue = val || ''; + const rawValue = this.normalizeAutociteHtmlForEditor(val || ''); const cleanEmptyTags = /<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g; const replaceSpaces = /\s+(?=<)|(?<=>)\s+/g; @@ -572,7 +678,8 @@ export default { ..._this.tinymceOtherInit, trim_span_elements: false, // 禁止修剪内联标签周围的空格 extended_valid_elements: 'blue[*],autocite[*]', - custom_elements: 'blue,autocite', + /* ~ 前缀:按行内(类似 span)处理,否则默认当块级会拆段导致引用后强制换行 */ + custom_elements: 'blue,~autocite', valid_children: '+blue[#text|i|em|b|strong|span],+body[blue|autocite],+p[blue|autocite]', inline: false, // 使用 iframe 模式 @@ -615,16 +722,19 @@ export default { font-weight: bold !important; } + /* inline 与 blue 引用一致,避免 inline-block 在行尾产生多余换行感 */ autocite { - display: inline-block; + display: inline; + vertical-align: baseline; color: rgb(0, 130, 170); - font-weight: bold; + // font-weight: bold; cursor: pointer; padding: 0 2px; border-radius: 3px; background-color: rgba(0, 130, 170, 0.08); user-select: all; font-size: 12px; + white-space: nowrap; } autocite:hover { background-color: rgba(0, 130, 170, 0.2); @@ -649,7 +759,23 @@ export default { }, body_class: 'panel-body ', object_resizing: false, - toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar, + toolbar: (function () { + const tb = _this.toolbar; + const hasCustom = + (Array.isArray(tb) && tb.length > 0) || + (typeof tb === 'string' && tb.length > 0); + /* 与 content.vue 默认一致,且含 insertRef;避免未传 toolbar 时引用到未定义的变量 */ + const defaultToolbar = [ + 'bold italic |customBlue removeBlue|LateX insertRef| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace' + ]; + if (!hasCustom) return defaultToolbar; + const rows = Array.isArray(tb) ? tb : [tb]; + return rows.map((row) => + typeof row === 'string' && row.indexOf('insertRef') === -1 + ? row.replace(/\|LateX\|/g, '|LateX insertRef|').replace(/\|\s*LateX\s*\|/g, '| LateX insertRef |') + : row + ); + })(), menubar: false, // 启用菜单栏并保持必要的项目 statusbar: false, // 关闭底部状态栏 custom_colors: false, @@ -721,19 +847,29 @@ export default { text: 'Ref', tooltip: 'Insert Reference', onAction: function () { + const refs = Array.isArray(_this.chanFerForm) ? _this.chanFerForm : []; + if (refs.length === 0) { + _this.$message.warning(_this.$t('wordCite.noRefs')); + return; + } _this._refBookmark = ed.selection.getBookmark(2); _this._editingAutocite = null; - _this.$emit('openRefSelector', { currentRefId: null }); + _this.$emit('openRefSelector', { currentRefIds: [] }); } }); ed.on('click', function (e) { const autociteEl = e.target.closest('autocite'); if (autociteEl) { - const dataId = autociteEl.getAttribute('data-id'); + const refs = Array.isArray(_this.chanFerForm) ? _this.chanFerForm : []; + if (refs.length === 0) { + _this.$message.warning(_this.$t('wordCite.noRefs')); + return; + } + const dataIds = _this.parseAutociteDataIds(autociteEl.getAttribute('data-id')); _this._refBookmark = ed.selection.getBookmark(2); _this._editingAutocite = autociteEl; - _this.$emit('openRefSelector', { currentRefId: dataId }); + _this.$emit('openRefSelector', { currentRefIds: dataIds }); return; } @@ -909,7 +1045,12 @@ export default { ed.on('GetContent', function (e) { e.content = e.content.replace(//g, '').replace(/<\/b>/g, ''); e.content = e.content.replace(//g, '').replace(/<\/em>/g, ''); - e.content = e.content.replace(/]*)>[^<]*<\/autocite>/gi, ''); + e.content = e.content.replace(/<\/autocite>\s*​/gi, ''); + e.content = e.content.replace(/<\/autocite>\s*\u200b/g, ''); + e.content = e.content.replace(/]*)>[^<]*<\/autocite>/gi, function (match, attrs) { + var clean = attrs.replace(/\s*style="[^"]*"/gi, '').replace(/\s*title="[^"]*"/gi, '').replace(/\s*contenteditable="[^"]*"/gi, ''); + return ''; + }); }); }, paste_preprocess: function (plugin, args) { @@ -1075,7 +1216,8 @@ export default { }, //设置内容 setContent(value) { - window.tinymce.get(this.tinymceId).setContent(value); + const ed = window.tinymce.get(this.tinymceId); + if (ed) ed.setContent(this.normalizeAutociteHtmlForEditor(value || '')); }, //获取内容 async getContent(type) { diff --git a/src/components/page/components/table/content.vue b/src/components/page/components/table/content.vue index 71c1def..bbd3e67 100644 --- a/src/components/page/components/table/content.vue +++ b/src/components/page/components/table/content.vue @@ -44,8 +44,8 @@ export default { }, computed: { toolbarConfig() { - const hasRefs = Array.isArray(this.chanFerForm) && this.chanFerForm.length > 0; - const refBtn = hasRefs ? ' insertRef' : ''; + // Ref 必须始终出现在配置里:TinyMCE 只在 init 时读一次 toolbar,若首屏 chanFerForm 为空则按钮会永久缺失 + const refBtn = ' insertRef'; if (!this.isAutomaticUpdate) { return [`bold italic |customBlue removeBlue|LateX${refBtn}| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace`]; } @@ -107,6 +107,19 @@ export default { }, removeAutocite() { this.$refs.tinymceChild1.removeAutocite(); + }, + /** 从当前 autocite 的 data-id 中移除指定 id;无剩余则删标签;未选 id 时删整段 */ + stripAutociteIds(ids) { + if (this.$refs.tinymceChild1 && typeof this.$refs.tinymceChild1.stripAutociteIds === 'function') { + this.$refs.tinymceChild1.stripAutociteIds(ids); + } + }, + /** 参考文献异步到达后刷新编辑器内 autocite 数字(供父组件在打开弹窗 / fetch 完成后调用) */ + refreshAutociteDisplay() { + const t = this.$refs.tinymceChild1; + if (t && typeof t.renderAutociteInEditor === 'function' && t.editorInstance) { + t.renderAutociteInEditor(t.editorInstance); + } } } }; diff --git a/src/components/page/components/table/word.vue b/src/components/page/components/table/word.vue index 27d66fa..8acea78 100644 --- a/src/components/page/components/table/word.vue +++ b/src/components/page/components/table/word.vue @@ -963,7 +963,12 @@ -
+
H1 @@ -977,9 +982,21 @@ -
+
-
@@ -483,6 +477,29 @@ export default { .prog-box { flex: 1; } .prog-num { font-size: 12px; font-weight: bold; display: block; text-align: right; margin-bottom: 4px; min-width: 56px; } .pagination-container { margin-top: 20px; display: flex; justify-content: flex-end; padding: 10px; } + +.btn-group { + display: inline-flex; + align-items: center; + gap: 12px; + flex-shrink: 0; +} +/* 单次抓取按钮单独线框(与右侧启停区分) */ +.crawl-once-frame { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border: 1px solid #006699; + border-radius: 6px; + background: #fff; + line-height: 1; + box-sizing: border-box; +} +.crawl-once-frame .op-run-once { + margin-left: 0; + margin-right: 0; +} + .btn-group .el-button.op-pause, .btn-group .el-button.op-pause i { color: #fc4d4d !important; } .btn-group .el-button.op-resume, diff --git a/src/components/page/editPublicRefTableOnly.vue b/src/components/page/editPublicRefTableOnly.vue index 1290d4d..c41494f 100644 --- a/src/components/page/editPublicRefTableOnly.vue +++ b/src/components/page/editPublicRefTableOnly.vue @@ -349,7 +349,7 @@ export default { } // 如果连续数字 >= 3 个,使用连字符 '-' if (j - i >= 2) { - result.push(`${sorted[i]}-${sorted[j]}`); + result.push(`${sorted[i]}–${sorted[j]}`); } else { // 否则(1个或2个)逐个列出 for (let k = i; k <= j; k++) { @@ -368,11 +368,19 @@ export default { let html = this.highlightText2(text, annotations, type, am_id); // 2. 借鉴 EndNote:对处理完批注的 HTML 进行引用联动渲染 - // 正则匹配连续的 autocite 标签(允许中间有空格) - const citeGroupRe = /(<\/autocite>\s*)+/gi; + // 支持 data-id="a,b,c" 单标签 或 多个相邻单 id 标签 + const citeGroupRe = /(?:<\/autocite>\s*)+/gi; return html.replace(citeGroupRe, (groupMatch) => { - const ids = [...groupMatch.matchAll(/data-id="(\d+)"/gi)].map((m) => m[1]); + const ids = []; + const innerRe = /<\/autocite>/gi; + let m; + while ((m = innerRe.exec(groupMatch)) !== null) { + m[1].split(',').forEach((part) => { + const id = part.trim(); + if (id) ids.push(id); + }); + } // 从全局 citeMap 中获取序号(citeMap 是你根据全文顺序生成的 {ID: Index}) const nums = ids.map((id) => this.citeMap[id]).filter((n) => n); @@ -1876,9 +1884,9 @@ export default { border-radius: 30px; } .text-highlight { - background-color: rgb(252, 98, 93); + /* background-color: rgb(252, 98, 93); background-color: rgb(252 98 93 / 68%); - color: #333; + color: #333; */ } .template-info { margin-top: 20px;