表格参考文献的预览

This commit is contained in:
2026-04-13 16:42:26 +08:00
parent 563def58a5
commit eff107aa15
8 changed files with 268 additions and 27 deletions

View File

@@ -0,0 +1,169 @@
/**
* 表格弹窗预览:将单元格内空 mycite 转为可见 [n],与 word.vue renderCiteLabels 一致(纯函数,无 Vue 依赖)
*/
export function 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(', ');
}
export function sortAutociteIdsByCiteNumber(ids, citeMap) {
const map = citeMap || {};
const uniq = [...new Set(ids.map(String))];
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));
});
}
/**
* @param {Array} refs - chanFerForm项含 p_refer_id
* @param {Array<string|number>} bodyCiteIdOrder - 正文首次出现顺序;空则按列表行序 1..n
*/
export function buildCiteMapFromRefs(refs, bodyCiteIdOrder) {
const refList = Array.isArray(refs) ? refs : [];
const refIdSet = new Set(
refList.map((r) => (r && r.p_refer_id != null ? String(r.p_refer_id) : '')).filter(Boolean)
);
let orderedIds = [];
if (Array.isArray(bodyCiteIdOrder) && bodyCiteIdOrder.length > 0) {
orderedIds = bodyCiteIdOrder.map(String);
}
if (orderedIds.length === 0) {
const map = {};
refList.forEach((row, idx) => {
const key = row && row.p_refer_id != null ? String(row.p_refer_id) : '';
if (key) map[key] = idx + 1;
});
return map;
}
const filtered = [];
orderedIds.forEach((id) => {
if (refIdSet.has(id) && !filtered.includes(id)) filtered.push(id);
});
const map = {};
filtered.forEach((id, idx) => {
map[id] = idx + 1;
});
let next = filtered.length + 1;
refList.forEach((r) => {
const key = r && r.p_refer_id != null ? String(r.p_refer_id) : '';
if (!key || map[key] != null) return;
map[key] = next++;
});
return map;
}
function escAttr(s) {
return String(s || '')
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;');
}
/**
* @param {string} html
* @param {Array} refs - chanFerForm
* @param {Object} citeMap - p_refer_id -> 显示序号
*/
export function renderCiteLabelsInHtml(html, refs, citeMap) {
if (!html || typeof html !== 'string') return html || '';
if (!Array.isArray(refs) || refs.length === 0) return html;
/** 与稿面一致:历史/表格 JSON 中可能为 <autocite> */
let h = html.replace(/<\/autocite>/gi, '</mycite>').replace(/<autocite\b/gi, '<mycite');
const refList = refs;
const refMap = refList.reduce((acc, item) => {
const key = item && item.p_refer_id != null ? String(item.p_refer_id) : '';
if (key) acc[key] = item;
return acc;
}, {});
let normalized = h.replace(/<mycite\b[^>]*\/?>[\s\S]*?(?:<\/mycite>)?/gi, (fullTag) => {
const attrMatch = fullTag.match(/\bdata-id\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i);
const dataId = (attrMatch && (attrMatch[1] || attrMatch[2] || attrMatch[3])) || '';
if (!dataId) return '';
return `<mycite data-id="${escAttr(dataId)}"></mycite>`;
});
const citeGroupRe = /(?:<mycite\s+data-id="([^"]*)"\s*><\/mycite>\s*)+/gi;
normalized = normalized.replace(citeGroupRe, (groupMatch) => {
const ids = [];
const innerRe = /<mycite\s+data-id="([^"]*)"\s*><\/mycite>/gi;
let m;
while ((m = innerRe.exec(groupMatch)) !== null) {
m[1].split(',').forEach((part) => {
const id = part.trim();
if (id) ids.push(id);
});
}
const sortedAll = sortAutociteIdsByCiteNumber(ids, citeMap);
const validIds = sortedAll.filter((id) => refMap[id]);
const sortedIds = validIds.length ? sortAutociteIdsByCiteNumber(validIds, citeMap) : [];
const map = citeMap || {};
const parts = sortedIds.map((id) => {
const ref = refMap[id];
const no = ref ? map[id] : null;
const num = no != null && no !== '' ? String(no) : null;
return { id, ref, num };
});
const numsForLabel = parts
.map((p) => p.num)
.filter((n) => n != null && n !== '')
.map(Number);
const label = numsForLabel.length > 0 ? formatCiteNumbers(numsForLabel) : '';
if (!label) {
return '';
}
const dataIdAttr = escAttr(sortedIds.join(','));
return `<mycite data-id="${dataIdAttr}">[${label}]</mycite>`;
});
return normalized.replace(/<mycite[^>]*data-id=""[^>]*>[\s\S]*?<\/mycite>/gi, '');
}
export function applyCiteLabelsToTableRows(rows, refs, citeMap) {
if (!Array.isArray(rows) || !rows.length) return rows;
if (!Array.isArray(refs) || refs.length === 0) return rows;
return rows.map((row) => {
if (!Array.isArray(row)) return row;
const nextRow = row.map((cell) => {
if (!cell || typeof cell !== 'object') return cell;
const t = cell.text;
const lower = typeof t === 'string' ? t.toLowerCase() : '';
if (!lower || (!lower.includes('mycite') && !lower.includes('autocite'))) return cell;
const next = renderCiteLabelsInHtml(t, refs, citeMap);
if (next === t) return cell;
return { ...cell, text: next };
});
// TableUtils.addRowIdToData 会给数组行对象挂 rowId渲染 oddColor 依赖它,不能在 map 后丢失
if (row.rowId != null) {
nextRow.rowId = row.rowId;
}
return nextRow;
});
}

View File

@@ -1035,8 +1035,7 @@ str = str.replace(regex, function (match, content, offset, fullString) {
const cells = row.querySelectorAll('th, td'); // 获取每个行中的单元格(包括 <th> 和 <td>
return await Promise.all(
Array.from(cells).map(async (cell) => {
console.log("🚀 ~ parseTableToArray777 ~ cell:", cell);
const text = await this.extractMathJaxLatex(cell);
return {
text,

View File

@@ -57,6 +57,8 @@
table: 'api/Preaccept/getMainTables'
}"
:articleId="articleId"
:bodyCiteIdOrder="articleCiteIdOrder"
:chanFerForm="chanFerForm"
:content="ManuscirptContent"
ref="commonWordHtmlTypeSetting"
@onDragStart="onDragStart"

View File

@@ -681,11 +681,12 @@ export default {
const noCite = ref ? citeMap[String(id)] : null;
const noTable =
useTableBracketNums && tableNums[String(id)] != null ? tableNums[String(id)] : null;
// 单元格内 [n] 按 Original ordertable 序号)匹配;角标展示与正文一致,优先用 citeMap 对应序号
const num =
noTable != null && noTable !== '' && !Number.isNaN(Number(noTable))
? String(noTable)
: noCite != null && noCite !== ''
? String(noCite)
noCite != null && noCite !== '' && !Number.isNaN(Number(noCite))
? String(noCite)
: noTable != null && noTable !== '' && !Number.isNaN(Number(noTable))
? String(noTable)
: null;
return { id, ref, num };
});

View File

@@ -58,9 +58,25 @@
<script>
import { TableUtils } from '@/common/js/TableUtils';
import { mediaUrl } from '@/common/js/commonJS.js';
import {
buildCiteMapFromRefs,
applyCiteLabelsToTableRows
} from '@/common/js/citeTablePreview.js';
export default {
name: 'TablePreviewer',
props: {
/** 与稿面 chanFerForm 一致,用于把单元格内 <mycite> 显示为 [n] */
referenceList: {
type: Array,
default: () => []
},
/** 正文引用首次出现顺序p_refer_id与 GenerateCharts articleCiteIdOrder 一致 */
bodyCiteIdOrder: {
type: Array,
default: () => []
}
},
data() {
return {
visible: false,
@@ -70,23 +86,32 @@ export default {
mediaUrl,
};
},
computed: {
previewCiteMap() {
return buildCiteMapFromRefs(this.referenceList, this.bodyCiteIdOrder);
}
},
methods: {
async open(type, item,isNoBg) {
async open(type, item, isNoBg) {
this.visible = true;
this.type = type;
const hideOddRowBg = isNoBg === true || isNoBg === 'true' || isNoBg === 1 || isNoBg === '1';
if (type === 'table') {
this.loading = true;
setTimeout(() => {
try {
const processed = this.processTableData(item.table);
const processed = this.processTableData(item.table, {
refs: this.referenceList,
citeMap: this.previewCiteMap
});
this.processedItem = Object.freeze({
...item,
table: {
...item.table,
tableHeader: processed.tableHeader,
tableContent: processed.tableContent,
oddRowIds: isNoBg ? [] : processed.oddRowIds
oddRowIds: hideOddRowBg ? [] : processed.oddRowIds
}
});
} catch (err) {
@@ -97,6 +122,7 @@ export default {
}, 50);
} else {
this.processedItem = item;
}
// 数学公式渲染
@@ -105,12 +131,20 @@ export default {
}, 500);
},
processTableData(rawContent) {
processTableData(rawContent, citeCtx) {
try {
const tableList = typeof rawContent === 'string' ? JSON.parse(rawContent) : rawContent;
const { header, content } = TableUtils.splitTable(tableList);
const { rowData, rowIds } = TableUtils.addRowIdToData(content);
return { tableHeader: header, tableContent: rowData, oddRowIds: rowIds };
const refs = citeCtx && citeCtx.refs;
const citeMap = citeCtx && citeCtx.citeMap;
let tableHeader = header;
let tableContent = rowData;
if (Array.isArray(refs) && refs.length > 0 && citeMap && typeof citeMap === 'object') {
tableHeader = applyCiteLabelsToTableRows(header, refs, citeMap);
tableContent = applyCiteLabelsToTableRows(rowData, refs, citeMap);
}
return { tableHeader, tableContent, oddRowIds: rowIds };
} catch (e) {
return { tableHeader: [], tableContent: [], oddRowIds: [] };
}
@@ -238,6 +272,16 @@ export default {
background: rgb(250, 231, 232) !important;
}
/* 与稿面 mycite 引用样式一致,弹窗内可见 [n] */
.table_Box ::v-deep mycite {
display: inline;
vertical-align: baseline;
color: rgb(0, 130, 170) !important;
cursor: inherit;
text-decoration: none;
background-color: rgba(0, 130, 170, 0.08);
}
.table-fade-enter-active, .table-fade-leave-active { transition: opacity 0.3s ease; }
.table-fade-enter, .table-fade-leave-to { opacity: 0; }
</style>

View File

@@ -1102,21 +1102,21 @@
:key="`body-${it.id}-${idx}`"
:class="['cite-preview-item', { 'is-active': idx === citePreviewActiveIndex }]"
>
<div class="cite-preview-line cite-preview-num-auth">
<span class="cite-preview-line cite-preview-num-auth">
<span class="cite-preview-num">{{ it.no }}.</span>
<span v-if="it.author" class="cite-preview-auth">{{ it.author }}</span>
</div>
<div v-if="it.title && it.title !== '-'" class="cite-preview-line cite-preview-article-title">
</span>
<span v-if="it.title && it.title !== '-'" class="cite-preview-line cite-preview-article-title">
{{ it.title }}
</div>
<div
</span>
<span
v-if="it.joura || it.dateno"
class="cite-preview-line cite-preview-journal-row"
>
<em v-if="it.joura" class="cite-preview-journal">{{ it.joura }}</em>
<span v-if="it.joura && it.dateno">&nbsp;</span>
<span v-if="it.dateno" class="cite-preview-dateno">{{ it.dateno }}</span>
</div>
</span>
<a v-if="it.doi" class="cite-preview-doi" :href="it.doi" target="_blank">{{ it.doi }}</a>
</div>
</div>

View File

@@ -252,10 +252,11 @@
</ul>
</div>
<DynamicTable
ref="myTableModal"
/>
<DynamicTable
ref="myTableModal"
:reference-list="chanFerForm || []"
:body-cite-id-order="bodyCiteIdOrder || []"
/>
</div>
</template>
@@ -264,7 +265,18 @@
import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑
import DynamicTable from './DynamicTable.vue';
export default {
props: ['articleId', 'imgWidth', 'imgHeight', 'scale', 'isEdit', 'isShowEdit', 'urlList', 'content'],
props: [
'articleId',
'imgWidth',
'imgHeight',
'scale',
'isEdit',
'isShowEdit',
'urlList',
'content',
'chanFerForm',
'bodyCiteIdOrder'
],
data() {
return {
identity: localStorage.getItem('U_role'),

View File

@@ -464,10 +464,11 @@
</li>
</ul>
</div>
<DynamicTable
ref="myTableModal"
/>
<DynamicTable
ref="myTableModal"
:reference-list="chanFerForm || []"
:body-cite-id-order="bodyCiteIdOrder || []"
/>
</div>
</template>
@@ -476,7 +477,20 @@ import DynamicTable from './DynamicTable.vue';
import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑
export default {
props: ['articleId', 'imgWidth', 'imgHeight', 'scale', 'isEdit', 'isShowEdit', 'urlList', 'content'],
props: [
'articleId',
'imgWidth',
'imgHeight',
'scale',
'isEdit',
'isShowEdit',
'urlList',
'content',
/** 参考文献列表,供表格预览把 mycite 显示为 [n] */
'chanFerForm',
/** 正文引用顺序,与稿面 articleCiteIdOrder 一致 */
'bodyCiteIdOrder'
],
data() {
return {
isShowComment: false,