表格参考文献的预览
This commit is contained in:
169
src/common/js/citeTablePreview.js
Normal file
169
src/common/js/citeTablePreview.js
Normal 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, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
table: 'api/Preaccept/getMainTables'
|
||||
}"
|
||||
:articleId="articleId"
|
||||
:bodyCiteIdOrder="articleCiteIdOrder"
|
||||
:chanFerForm="chanFerForm"
|
||||
:content="ManuscirptContent"
|
||||
ref="commonWordHtmlTypeSetting"
|
||||
@onDragStart="onDragStart"
|
||||
|
||||
@@ -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 order(table 序号)匹配;角标展示与正文一致,优先用 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 };
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
@@ -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"> </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>
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user