diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js index 3727c96..c713f3d 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -1150,6 +1150,9 @@ colTitle: 'Template title', deleteLogSuccess: 'Deleted', deleteLogFailed: 'Delete failed', noFailureReason: 'No failure reason', + logDetailEditTitle: 'Edit delivery log', + logDetailPreviewTitle: 'Preview delivery log', + logLoadFailed: 'Failed to load logs', deletedSuccess: 'Deleted', mockPromotionSubject: 'Promotion for {journal}', mockPromotionContent: '

Dear {name},

Check out our latest journal updates...

' @@ -1163,8 +1166,8 @@ colTitle: 'Template title', missingBoundRef: 'The bound reference no longer exists. Please cite again.', notFoundById: 'No reference found for citation ID {id}', selectRef: 'Select Reference', - reference: 'Reference', originalOrder: 'Original order', + reference: 'Reference', uncited: 'Uncited', cancel: 'Cancel', confirm: 'Confirm', diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index 84d4c51..446f8f0 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -1135,6 +1135,9 @@ const zh = { deleteLogSuccess: '删除成功', deleteLogFailed: '删除失败', noFailureReason: '暂无失败原因', + logDetailEditTitle: '编辑发送记录', + logDetailPreviewTitle: '预览发送记录', + logLoadFailed: '加载日志失败', deletedSuccess: '已删除', mockPromotionSubject: '自动推广:{journal}', mockPromotionContent: '

亲爱的 {name},

请查看我们最新的期刊更新...

' @@ -1148,8 +1151,8 @@ const zh = { missingBoundRef: '当前绑定的参考文献不存在,请重新引用', notFoundById: '未查询到编号{id}相关参考文献', selectRef: '选择参考文献', - reference: '参考文献', originalOrder: '原排序', + reference: '参考文献', uncited: '未引用', cancel: '取消', confirm: '确认', diff --git a/src/components/page/GenerateCharts.vue b/src/components/page/GenerateCharts.vue index 06d609e..b10f107 100644 --- a/src/components/page/GenerateCharts.vue +++ b/src/components/page/GenerateCharts.vue @@ -597,13 +597,7 @@ @@ -893,6 +887,8 @@ export default { articleCiteIdOrder: [], /** Edit Content 弹窗内当前 HTML(与保存合并逻辑一致),用于实时算出与稿面相同的 [n],避免仅依赖防抖后的 articleCiteIdOrder */ editModalDraftHtml: '', + /** 编辑器 getContent(raw) 快照;draft 为空时 editModalBodyCiteOrder 用其与 extract 对齐稿面 */ + editModalSyncedHtml: '', /** 表格抽屉:Title+Table+Note 合并稿,驱动 tableModalBodyCiteOrder(与 editModalDraftHtml 对 Edit Content 一致) */ tableModalDraftHtml: '', refSelectorVisible: false, @@ -1049,16 +1045,22 @@ export default { } ]; }, - /** 弹窗内引用角标:按 Main_List + 当前草稿段合并后的全文首次出现顺序,与保存后一致 */ + /** 弹窗内引用角标:与稿面同源 —— 始终用 extractAutociteOrderFromMainList(Main_List, am_id, 当前用于合并的 HTML) */ editModalBodyCiteOrder() { if (!this.editVisible || !this.currentContent || this.currentContent.am_id == null) { return this.articleCiteIdOrder; } - const draft = this.editModalDraftHtml; - if (draft == null || draft === '') { + const d = this.editModalDraftHtml; + const draftTrim = d != null && String(d).trim() !== '' ? d : ''; + const s = this.editModalSyncedHtml; + const syncedTrim = s != null && String(s).trim() !== '' ? s : ''; + const saved = this.currentContent.content; + const savedTrim = saved != null && String(saved).trim() !== '' ? saved : ''; + const htmlForExtract = draftTrim || syncedTrim || savedTrim; + if (!htmlForExtract) { return this.articleCiteIdOrder; } - const order = extractAutociteOrderFromMainList(this.Main_List, this.currentContent.am_id, draft); + const order = extractAutociteOrderFromMainList(this.Main_List, this.currentContent.am_id, htmlForExtract); return order.length > 0 ? order : this.articleCiteIdOrder; }, /** @@ -1123,11 +1125,23 @@ export default { }, watch: { editVisible(val) { - if (!val) this.editModalDraftHtml = ''; + if (!val) { + this.editModalDraftHtml = ''; + this.editModalSyncedHtml = ''; + } + }, + /** 计算属性 editModalBodyCiteOrder 变化后重绘 TinyMCE 角标,与 body-cite-id-order 一致 */ + editModalBodyCiteOrder: { + handler() { + if (!this.editVisible) return; + this.$nextTick(() => { + this.refreshEditModalAutociteDisplay(); + }); + } }, threeVisible(val) { if (!val) this.tableModalDraftHtml = ''; - } + }, // 监听计算属性 // combinedValue(newVal, oldVal) { // console.log('value1 或 value2 发生变化'); @@ -1519,8 +1533,8 @@ 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.editVisible && this.$refs.commonContent) { + this.refreshEditModalAutociteDisplay(); } if (this.addContentVisible && this.$refs.addContent && this.$refs.addContent.refreshAutociteDisplay) { this.$refs.addContent.refreshAutociteDisplay(); @@ -1548,13 +1562,12 @@ export default { applyRefOrderAfterFetchReferList() { if (this.editVisible && this.currentContent && this.currentContent.am_id != null) { let html = this.editModalDraftHtml; - if (html === '' || html == null) { - const t = this.$refs.commonContent && this.$refs.commonContent.$refs && this.$refs.commonContent.$refs.tinymceChild1; - if (t && t.editorInstance) { - html = t.editorInstance.getContent({ format: 'raw' }) || ''; - } else { - html = (this.currentContent && this.currentContent.content) || ''; - } + if (html === '' || html == null || !String(html).trim()) { + html = this.syncEditModalHtmlFromEditor(); + if (html) this.editModalDraftHtml = html; + } + if (html === '' || html == null || !String(html).trim()) { + html = (this.currentContent && this.currentContent.content) || ''; } this.flushReorderFromEditModal(html); return; @@ -1913,8 +1926,28 @@ export default { this.articleCiteIdOrder = order; }, onEditModalEditorInput(html) { - this.editModalDraftHtml = html || ''; - this.flushReorderFromEditModal(html); + const h = html || ''; + this.editModalDraftHtml = h; + this.editModalSyncedHtml = h; + this.flushReorderFromEditModal(h); + }, + /** 从 Edit Content 内 TinyMCE 取 raw HTML 写入 editModalSyncedHtml,供 extract 与 applyRefOrder 与稿面对齐 */ + syncEditModalHtmlFromEditor() { + if (!this.editVisible || !this.currentContent || this.currentContent.am_id == null) return ''; + const cc = this.$refs.commonContent; + const inst = cc && cc.$refs && cc.$refs.tinymceChild1; + const ed = inst && inst.editorInstance; + if (!ed || typeof ed.getContent !== 'function') return ''; + const raw = ed.getContent({ format: 'raw' }) || ''; + this.editModalSyncedHtml = raw; + return raw; + }, + refreshEditModalAutociteDisplay() { + if (!this.editVisible) return; + const cc = this.$refs.commonContent; + if (cc && typeof cc.refreshAutociteDisplay === 'function') { + cc.refreshAutociteDisplay(); + } }, /** 打开表格抽屉时用已有 lineStyle 字段拼合并稿,先写入 tableModalDraftHtml,角标与 Edit Content 一样按全文算 */ seedTableModalDraftFromLineStyle() { @@ -1977,6 +2010,28 @@ export default { handleRefSelectionChange(rows) { this.refSelectedRows = rows; }, + /** + * 选择文献弹窗「原排序」列:与表格 [n] 一致 —— 有 old_index 时显示 old_index+1,否则显示 order_index(已为 n) + */ + refSelectorOrderIndexDisplay(row) { + if (!row) return '—'; + const pick = (v) => { + if (v == null || v === '') return null; + const x = Number(v); + return Number.isNaN(x) ? null : x; + }; + const oi = + row.old_index != null && row.old_index !== '' + ? pick(row.old_index) + : pick(row.oldIndex); + if (oi != null) return oi + 1; + const ord = + row.order_index != null && row.order_index !== '' + ? pick(row.order_index) + : pick(row.orderIndex); + if (ord != null) return ord; + return '—'; + }, parseQuickPickNumbers(input) { const s = String(input || '').trim(); if (!s) return []; @@ -2074,15 +2129,13 @@ export default { this.flushReorderFromTableModal(); } if (this.editVisible && this.$refs.commonContent) { - const t = this.$refs.commonContent.$refs && this.$refs.commonContent.$refs.tinymceChild1; - if (t && t.editorInstance) { - const html = t.editorInstance.getContent({ format: 'raw' }); - this.editModalDraftHtml = html || ''; + const html = this.syncEditModalHtmlFromEditor(); + if (html) { + this.editModalDraftHtml = html; + this.editModalSyncedHtml = html; this.flushReorderFromEditModal(html); } - if (typeof this.$refs.commonContent.refreshAutociteDisplay === 'function') { - this.$refs.commonContent.refreshAutociteDisplay(); - } + this.refreshEditModalAutociteDisplay(); } if ( this.addContentVisible && @@ -3244,13 +3297,15 @@ export default { } this.$nextTick(() => { this.$nextTick(() => { - if (this.$refs.commonContent && this.$refs.commonContent.refreshAutociteDisplay) { - this.$refs.commonContent.refreshAutociteDisplay(); + const raw = this.syncEditModalHtmlFromEditor(); + if (raw) { + this.editModalDraftHtml = raw; } - const draft = this.currentContent && this.currentContent.content; - if (draft != null) { - this.flushReorderFromEditModal(draft); + const html = raw || (this.currentContent && this.currentContent.content) || ''; + if (html) { + this.flushReorderFromEditModal(html); } + this.refreshEditModalAutociteDisplay(); }); }); } diff --git a/src/components/page/components/Tinymce/index.vue b/src/components/page/components/Tinymce/index.vue index 855de9b..141dfa5 100644 --- a/src/components/page/components/Tinymce/index.vue +++ b/src/components/page/components/Tinymce/index.vue @@ -93,9 +93,22 @@ export default { showRefButton: { type: Boolean, default: true + }, + /** + * true:仅「表格单元格编辑」场景,角标/自动匹配 [n] 按 old_index+1(或 order_index); + * false:与稿面一致,始终用 bodyCiteIdOrder 的 citeMap(Edit Content 等虽 type=table 放宽标签,也必须 false) + */ + useTableLocalCitationIndex: { + type: Boolean, + default: false } }, computed: { + /** 是否启用表格局部序号(与全文 citeMap 互斥) */ + tableLocalCiteActive() { + if (this.type !== 'table' || !this.useTableLocalCitationIndex) return false; + return Object.keys(this.tableBracketNumToRefIdMap || {}).length > 0; + }, citeMap() { const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : []; const refIdSet = new Set( @@ -136,33 +149,30 @@ export default { return refs.some((r) => r && r.p_refer_id != null && String(r.p_refer_id).trim() !== ''); }, /** - * 表格编辑器专用:接口文献行的 old_index(0 起)+1 = 单元格里 [n] 对应的全局显示序号,再映射到 p_refer_id。 - * 无 old_index 时返回空对象,自动匹配回退为正文 citeMap。 + * 表格编辑器专用:单元格 [n] 中的 n → p_refer_id。 + * - 有 old_index(0 起)时:n = old_index + 1 + * - 仅有 order_index 时:按接口约定已为与 [n] 一致的序号,n = order_index(不再 +1) */ tableBracketNumToRefIdMap() { const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : []; const map = {}; refs.forEach((r) => { if (!r || r.p_refer_id == null) return; - const oi = r.old_index != null && r.old_index !== '' ? r.old_index : r.oldIndex; - if (oi == null || oi === '') return; - const n = Number(oi); - if (Number.isNaN(n)) return; - map[n + 1] = String(r.p_refer_id); + const n = this._getTableBracketNoForRow(r); + if (n == null) return; + map[n] = String(r.p_refer_id); }); return map; }, - /** p_refer_id → 表格角标序号(old_index+1),用于表格内渲染 [n] 与排序 */ + /** p_refer_id → 表格角标数字 n(与 [n] 一致) */ tableRefIdToBracketNum() { const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : []; const m = {}; refs.forEach((r) => { if (!r || r.p_refer_id == null) return; - const oi = r.old_index != null && r.old_index !== '' ? r.old_index : r.oldIndex; - if (oi == null || oi === '') return; - const num = Number(oi); - if (Number.isNaN(num)) return; - m[String(r.p_refer_id)] = num + 1; + const n = this._getTableBracketNoForRow(r); + if (n == null) return; + m[String(r.p_refer_id)] = n; }); return m; } @@ -302,6 +312,27 @@ export default { .map((s) => s.trim()) .filter(Boolean); }, + /** + * 与正文 [n] 一致的角标数字 n。old_index 为 0 起 → n=old_index+1;无 old_index 时用 order_index(已为 n). + */ + _getTableBracketNoForRow(r) { + if (!r) return null; + const pick = (v) => { + if (v == null || v === '') return null; + const x = Number(v); + return Number.isNaN(x) ? null : x; + }; + const oi = + r.old_index != null && r.old_index !== '' + ? pick(r.old_index) + : pick(r.oldIndex); + if (oi != null) return oi + 1; + const ord = + r.order_index != null && r.order_index !== '' + ? pick(r.order_index) + : pick(r.orderIndex); + return ord; + }, /** 与 word.vue renderCiteLabels:合并角标 id 按 citeMap 序号排序 */ sortAutociteIdsByCiteNumber(ids) { const uniq = [...new Set(ids.map(String))]; @@ -347,10 +378,10 @@ export default { }); return map; }, - /** 自动匹配 [n]:表格内用 old_index+1 映射;mytable 之后段落仍用全局 citeMap */ + /** 自动匹配 [n]:表格内用 old_index+1 或 order_index(=n);mytable 之后段落仍用全局 citeMap */ _getBracketNumToIdMaps() { const globalMap = this.buildNumToRefIdMap(); - if (this.type !== 'table') { + if (this.type !== 'table' || !this.useTableLocalCitationIndex) { return { primary: globalMap, afterTable: globalMap }; } const tm = this.tableBracketNumToRefIdMap || {}; @@ -375,17 +406,13 @@ export default { }); }, _sortAutociteIdsForDisplay(ids) { - if (this.type === 'table' && Object.keys(this.tableBracketNumToRefIdMap || {}).length > 0) { + if (this.tableLocalCiteActive) { return this.sortAutociteIdsByTableBracketNumber(ids); } return this.sortAutociteIdsByCiteNumber(ids); }, _sortIdsAfterBracketMatch(ids, tableOffset) { - if ( - this.type === 'table' && - tableOffset === 0 && - Object.keys(this.tableBracketNumToRefIdMap || {}).length > 0 - ) { + if (this.tableLocalCiteActive && tableOffset === 0) { return this.sortAutociteIdsByTableBracketNumber(ids); } return this.sortAutociteIdsByCiteNumber(ids); @@ -534,32 +561,40 @@ export default { let replaced = 0; let skippedSpecial = 0; while ((m = re.exec(text)) !== null) { - const nums = this.parseBracketInnerToNumbers(m[1]); - // 规则:只要括号里包含 0 或 -1,这一段不做解析替换(但不影响其它正常 [1][2]) - if (nums.some((n) => n === 0 || n === -1)) { + const rawNums = this.parseBracketInnerToNumbers(m[1]); + // 仅 [0]、[-1]、[0, -1] 等「全是 0/-1」不转换;与正常号混写时去掉 0/-1 再匹配 + const onlyZeroOrNegOne = + rawNums.length > 0 && rawNums.every((n) => n === 0 || n === -1); + if (onlyZeroOrNegOne) { pieces.push({ type: 'text', s: text.slice(lastIndex, m.index) }); pieces.push({ type: 'text', s: m[0] }); lastIndex = m.index + m[0].length; skippedSpecial++; continue; } - // 任一序号在参考文献中无对应(含超出列表、tableOffset 后仍无效)则整段不转换,避免部分匹配 + const nums = rawNums.filter((n) => n !== 0 && n !== -1); const mapNo = (n) => (n > 0 && tableOffset > 0 ? n + tableOffset - 1 : n); - if ( - !nums.length || - nums.some((n) => { - const mappedNo = mapNo(n); - return !numToId[mappedNo]; - }) - ) { + + if (!nums.length) { pieces.push({ type: 'text', s: text.slice(lastIndex, m.index) }); pieces.push({ type: 'text', s: m[0] }); lastIndex = m.index + m[0].length; continue; } + + const validNums = nums.filter((n) => numToId[mapNo(n)]); + const invalidNums = nums.filter((n) => !numToId[mapNo(n)]); + + if (validNums.length === 0) { + pieces.push({ type: 'text', s: text.slice(lastIndex, m.index) }); + pieces.push({ type: 'text', s: m[0] }); + lastIndex = m.index + m[0].length; + continue; + } + const ids = []; const seen = new Set(); - nums.forEach((n) => { + validNums.forEach((n) => { const mappedNo = mapNo(n); const id = numToId[mappedNo]; if (id && !seen.has(id)) { @@ -571,6 +606,13 @@ export default { if (ids.length > 0) { const sorted = this._sortIdsAfterBracketMatch(ids, tableOffset); pieces.push({ type: 'cite', ids: sorted }); + if (invalidNums.length) { + const invSorted = [...new Set(invalidNums)].sort((a, b) => a - b); + const invLabel = this.formatCiteNumbers(invSorted); + if (invLabel) { + pieces.push({ type: 'text', s: ', [' + invLabel + ']' }); + } + } replaced++; } else { pieces.push({ type: 'text', s: m[0] }); @@ -598,7 +640,7 @@ export default { // 提示:仅提示一次即可;这里在节点级别提示可能重复,放到宏任务末尾合并展示 clearTimeout(this._autoLinkSkipToastTimer); this._autoLinkSkipToastTimer = setTimeout(() => { - this.$message.info(`Skipped ${skippedSpecial} bracket cite(s) containing 0/-1.`); + this.$message.info(`Skipped ${skippedSpecial} bracket cite(s) containing only 0 or -1.`); }, 0); } return replaced; @@ -622,7 +664,7 @@ export default { const citeMap = this.citeMap || {}; const tableNums = this.tableRefIdToBracketNum || {}; - const useTableBracketNums = this.type === 'table' && Object.keys(this.tableBracketNumToRefIdMap || {}).length > 0; + const useTableBracketNums = this.tableLocalCiteActive; allAutocites.forEach((el) => { ed.dom.setAttrib(el, 'contenteditable', 'false'); diff --git a/src/components/page/components/table/table.vue b/src/components/page/components/table/table.vue index ed5f754..5a39fa2 100644 --- a/src/components/page/components/table/table.vue +++ b/src/components/page/components/table/table.vue @@ -2,6 +2,7 @@