提交
This commit is contained in:
@@ -1164,6 +1164,7 @@ colTitle: 'Template title',
|
||||
notFoundById: 'No reference found for citation ID {id}',
|
||||
selectRef: 'Select Reference',
|
||||
reference: 'Reference',
|
||||
uncited: 'Uncited',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
remove: 'Remove',
|
||||
|
||||
@@ -1149,6 +1149,7 @@ const zh = {
|
||||
notFoundById: '未查询到编号{id}相关参考文献',
|
||||
selectRef: '选择参考文献',
|
||||
reference: '参考文献',
|
||||
uncited: '未引用',
|
||||
cancel: '取消',
|
||||
confirm: '确认',
|
||||
remove: '移除',
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
ref="editPublicRefTableOnly"
|
||||
:chanFerForm="chanFerForm"
|
||||
:chanFerFormRepeatList="chanFerFormRepeatList"
|
||||
:citeOrderFromParent="articleCiteIdOrder"
|
||||
:ref-order-follows-body="true"
|
||||
:p_article_id="p_article_id"
|
||||
@ChanFerMashUp="ChanFerMashUp"
|
||||
@@ -558,6 +559,16 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('wordCite.reference')">
|
||||
<template slot-scope="scope">
|
||||
<el-tag
|
||||
v-if="isUncitedReference(scope.row)"
|
||||
class="uncited-tag"
|
||||
size="mini"
|
||||
type="info"
|
||||
effect="plain"
|
||||
style="margin: 0 8px 6px 0"
|
||||
>
|
||||
{{ $t('wordCite.uncited') }}
|
||||
</el-tag>
|
||||
<!-- journal 形式 -->
|
||||
<div style="text-align: left" v-if="scope.row.refer_type == 'journal'" class="reference-item">
|
||||
<p>
|
||||
@@ -1163,6 +1174,13 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
isUncitedReference(row) {
|
||||
const id = row && row.p_refer_id != null ? String(row.p_refer_id) : '';
|
||||
if (!id) return false;
|
||||
const order = Array.isArray(this.articleCiteIdOrder) ? this.articleCiteIdOrder.map(String) : [];
|
||||
if (!order.length) return false;
|
||||
return !order.includes(id);
|
||||
},
|
||||
/** 参考文献:刷新(供编辑/新增/删除后更新选择器列表) */
|
||||
changeRefer() {
|
||||
return this.fetchReferList();
|
||||
@@ -3992,6 +4010,12 @@ export default {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::v-deep .uncited-tag.el-tag {
|
||||
background-color: #f56c6c !important;
|
||||
border-color: #f56c6c !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.type_CHar {
|
||||
position: relative;
|
||||
border-left: 4px solid rgba(0 102 153 / 20%);
|
||||
|
||||
@@ -1198,11 +1198,6 @@ export default {
|
||||
text: 'Reference',
|
||||
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', { currentRefIds: [] });
|
||||
@@ -1212,11 +1207,6 @@ export default {
|
||||
ed.on('click', function (e) {
|
||||
const autociteEl = e.target.closest('mycite');
|
||||
if (autociteEl) {
|
||||
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;
|
||||
|
||||
@@ -191,10 +191,23 @@ export default {
|
||||
}
|
||||
},
|
||||
sortedFilteredFields() {
|
||||
const kw = (this.fieldSearchText || '').trim().toLowerCase();
|
||||
const kwRaw = String(this.fieldSearchText || '');
|
||||
const normalize = (s) =>
|
||||
String(s || '')
|
||||
.trim()
|
||||
.replace(/\s+/g, ' ')
|
||||
.toLowerCase();
|
||||
const tokens = kwRaw
|
||||
? kwRaw
|
||||
.split(/[\r\n,,;;]+/g)
|
||||
.map((s) => normalize(s))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const list = (this.availableFields || []).filter((item) => {
|
||||
if (!kw) return true;
|
||||
return String(item.label || '').toLowerCase().includes(kw);
|
||||
if (!tokens.length) return true;
|
||||
const label = normalize(item.label || '');
|
||||
// 严格匹配:必须与字段名完全一致(忽略大小写与空白差异)
|
||||
return tokens.some((t) => t === label);
|
||||
});
|
||||
return list.slice().sort((a, b) => String(a.label || '').localeCompare(String(b.label || '')));
|
||||
},
|
||||
|
||||
@@ -1056,6 +1056,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="citePreviewVisible && citePreviewItems.length > 0"
|
||||
class="cite-preview-pop"
|
||||
:style="citePreviewStyle"
|
||||
@click.stop
|
||||
>
|
||||
<div class="cite-preview-head">
|
||||
<span>{{ $t('wordCite.reference') }}</span>
|
||||
<i class="el-icon-close" @click="hideCitePreview"></i>
|
||||
</div>
|
||||
<div class="cite-preview-body">
|
||||
<div v-for="(it, idx) in citePreviewItems" :key="`${it.id}-${idx}`" class="cite-preview-item">
|
||||
<div class="cite-preview-title">
|
||||
[{{ it.no }}] {{ it.title }}
|
||||
</div>
|
||||
<div class="cite-preview-meta">
|
||||
{{ it.meta }}
|
||||
</div>
|
||||
<a v-if="it.doi" class="cite-preview-doi" :href="it.doi" target="_blank">{{ it.doi }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<common-media-link-dialog
|
||||
ref="mediaLinkRef"
|
||||
@confirm="onLinkConfirm"
|
||||
@@ -1211,6 +1233,14 @@ export default {
|
||||
lastTag: null,
|
||||
/** 稿面点击 <mycite> 时记录,用于浮动条「修改引用 / 移除引用」 */
|
||||
manuscriptAutociteContext: null,
|
||||
citePreviewVisible: false,
|
||||
citePreviewItems: [],
|
||||
citePreviewStyle: {
|
||||
position: 'fixed',
|
||||
left: '0px',
|
||||
top: '0px',
|
||||
zIndex: 10001
|
||||
},
|
||||
/** 正文 DOM 中 mycite 首次出现顺序(唯一 p_refer_id),驱动 citeMap 与参考文献列表排序 */
|
||||
bodyCiteIdOrder: [],
|
||||
_syncRefOrderTimer: null,
|
||||
@@ -1725,6 +1755,7 @@ renderCiteLabels(html) {
|
||||
// 如果点在气泡内,不关
|
||||
if (e.target.closest('.bubble-container')) return;
|
||||
if (e.target.closest('.selection-bubble')) return;
|
||||
if (e.target.closest('.cite-preview-pop')) return;
|
||||
|
||||
const selection = window.getSelection();
|
||||
const hasSelection = selection && selection.toString().trim() !== '';
|
||||
@@ -1738,6 +1769,7 @@ renderCiteLabels(html) {
|
||||
this.currentData = {};
|
||||
this.currentId = '';
|
||||
this.manuscriptAutociteContext = null;
|
||||
this.hideCitePreview();
|
||||
|
||||
this.clearActiveGlow(); // 清除高亮
|
||||
}
|
||||
@@ -2185,6 +2217,52 @@ renderCiteLabels(html) {
|
||||
this.bubbleVisible = false;
|
||||
|
||||
this.isMenuVisible = false;
|
||||
this.hideCitePreview();
|
||||
},
|
||||
hideCitePreview() {
|
||||
this.citePreviewVisible = false;
|
||||
this.citePreviewItems = [];
|
||||
},
|
||||
showCitePreviewByAutocite(el, dataId) {
|
||||
const ids = String(dataId || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
if (!ids.length) {
|
||||
this.hideCitePreview();
|
||||
return;
|
||||
}
|
||||
const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : [];
|
||||
const citeMap = this.citeMap || {};
|
||||
const refMap = {};
|
||||
refs.forEach((r) => {
|
||||
if (r && r.p_refer_id != null) refMap[String(r.p_refer_id)] = r;
|
||||
});
|
||||
const items = ids
|
||||
.map((id) => {
|
||||
const ref = refMap[String(id)];
|
||||
if (!ref) return null;
|
||||
const no = citeMap[String(id)] || '?';
|
||||
// 标题优先走 Crossref 解析出的 title;兜底 refer_frag
|
||||
const title = (ref.title || ref.refer_frag || '').toString().trim() || '-';
|
||||
const meta = [ref.author, ref.joura, ref.dateno].filter(Boolean).join(' ').trim();
|
||||
const doi = (ref.doilink || ref.doi || ref.isbn || '').toString().trim();
|
||||
return { id: String(id), no, title, meta, doi };
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (!items.length) {
|
||||
this.hideCitePreview();
|
||||
return;
|
||||
}
|
||||
const rect = el.getBoundingClientRect();
|
||||
this.citePreviewStyle = {
|
||||
position: 'fixed',
|
||||
left: `${Math.max(12, Math.min(rect.left, window.innerWidth - 460))}px`,
|
||||
top: `${Math.min(rect.bottom + 8, window.innerHeight - 220)}px`,
|
||||
zIndex: 10001
|
||||
};
|
||||
this.citePreviewItems = items;
|
||||
this.citePreviewVisible = true;
|
||||
},
|
||||
escapeHtmlAttr(val) {
|
||||
return String(val == null ? '' : val).replace(/&/g, '&').replace(/"/g, '"');
|
||||
@@ -3439,6 +3517,7 @@ renderCiteLabels(html) {
|
||||
this.currentTag = '';
|
||||
this.currentTagData = null; // 必须重置,防止带入旧数据
|
||||
this.manuscriptAutociteContext = null;
|
||||
this.hideCitePreview();
|
||||
const clickedAutocite = event.target.closest('mycite');
|
||||
if (clickedAutocite && !this.isPreview) {
|
||||
const dataId = clickedAutocite.getAttribute('data-id');
|
||||
@@ -3446,6 +3525,7 @@ renderCiteLabels(html) {
|
||||
am_id: id,
|
||||
dataId: dataId != null && dataId !== '' ? String(dataId) : ''
|
||||
};
|
||||
this.showCitePreviewByAutocite(clickedAutocite, dataId);
|
||||
}
|
||||
const clickedTag = event.target.closest('myfigure, mytable, myh3');
|
||||
if (clickedTag) {
|
||||
@@ -5122,6 +5202,58 @@ font-weight: bold !important;
|
||||
::v-deep myh3 *{
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.cite-preview-pop {
|
||||
width: 440px;
|
||||
max-height: 220px;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);
|
||||
overflow: hidden;
|
||||
}
|
||||
.cite-preview-head {
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.cite-preview-head i {
|
||||
cursor: pointer;
|
||||
}
|
||||
.cite-preview-body {
|
||||
max-height: 188px;
|
||||
overflow: auto;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
.cite-preview-item {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.cite-preview-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.cite-preview-title {
|
||||
font-weight: 600;
|
||||
word-break: break-word;
|
||||
}
|
||||
.cite-preview-meta {
|
||||
color: #606266;
|
||||
word-break: break-word;
|
||||
}
|
||||
.cite-preview-doi {
|
||||
color: #006699;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@keyframes blueGlow {
|
||||
0%,
|
||||
100% {
|
||||
|
||||
@@ -239,18 +239,47 @@ export default {
|
||||
openAddDialog() { this.addDialogVisible = true; },
|
||||
resetAddForm() { this.addForm = { field: '', runNow: false }; this.addLoading = false; },
|
||||
async submitAddKeyword() {
|
||||
if (!this.addForm.field.trim()) return this.$message.warning(this.$t('crawlTask.enterKeyword'));
|
||||
const raw = String(this.addForm.field || '').trim();
|
||||
if (!raw) return this.$message.warning(this.$t('crawlTask.enterKeyword'));
|
||||
// 支持一次录入多个:按换行/逗号/分号拆分,去重
|
||||
const fields = raw
|
||||
.split(/[\r\n,,;;]+/g)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
const uniqFields = Array.from(new Set(fields));
|
||||
if (uniqFields.length === 0) return this.$message.warning(this.$t('crawlTask.enterKeyword'));
|
||||
this.addLoading = true;
|
||||
try {
|
||||
const res = await this.$api.post('api/expert_manage/addFetchField', { field: this.addForm.field });
|
||||
if (res.code === 0) {
|
||||
this.$message.success(this.$t('crawlTask.addKeywordSuccess'));
|
||||
if (this.addForm.runNow) {
|
||||
await this.$api.post('api/expert_finder/fetchOneField', { field: this.addForm.field });
|
||||
const ok = [];
|
||||
const fail = [];
|
||||
for (const field of uniqFields) {
|
||||
try {
|
||||
const res = await this.$api.post('api/expert_manage/addFetchField', { field });
|
||||
if (res && res.code === 0) {
|
||||
ok.push(field);
|
||||
if (this.addForm.runNow) {
|
||||
// 不阻塞整体添加:run once 失败也记录,不影响已添加成功
|
||||
try {
|
||||
await this.$api.post('api/expert_finder/fetchOneField', { field });
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fail.push({ field, msg: (res && res.msg) || this.$t('crawlTask.operationFail') });
|
||||
}
|
||||
} catch (e) {
|
||||
fail.push({ field, msg: this.$t('crawlTask.operationFail') });
|
||||
}
|
||||
this.addDialogVisible = false;
|
||||
this.fetchList();
|
||||
}
|
||||
if (ok.length > 0) {
|
||||
this.$message.success(`${this.$t('crawlTask.addKeywordSuccess')} (${ok.length}/${uniqFields.length})`);
|
||||
}
|
||||
if (fail.length > 0) {
|
||||
this.$message.warning(`Failed: ${fail.map((x) => x.field).join(', ')}`);
|
||||
}
|
||||
this.addDialogVisible = false;
|
||||
this.fetchList();
|
||||
} finally { this.addLoading = false; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,24 @@
|
||||
v-for="(item, index) in chanFerForm"
|
||||
:key="item.p_refer_id != null ? item.p_refer_id : `ref-row-${index}`"
|
||||
class="ref-item-row"
|
||||
:class="{ 'has-change': item.is_change == 1, 'is-repeat': item.is_repeat == 1 }"
|
||||
:class="{ 'has-change': item.is_change == 1, 'is-repeat': item.is_repeat == 1, 'uncited-row': isUncitedReference(item) }"
|
||||
>
|
||||
|
||||
<div class="ref-content-area">
|
||||
<div v-if="item.refer_type == 'journal'" class="reference-item">
|
||||
<b class="ref-number-prefix">{{ index + 1 }}.</b>
|
||||
<template v-if="!isUncitedReference(item)">
|
||||
<b class="ref-number-prefix">{{ index + 1 }}.</b>
|
||||
</template>
|
||||
<el-tag
|
||||
v-else
|
||||
class="uncited-tag"
|
||||
size="mini"
|
||||
type="info"
|
||||
effect="plain"
|
||||
style="margin-right: 8px"
|
||||
>
|
||||
{{ $t('wordCite.uncited') }}
|
||||
</el-tag>
|
||||
|
||||
<span class="author-text">{{ item.author }} </span>
|
||||
<span v-html="formatTitle(item.title)"></span>. <em class="journal-name">{{ item.joura }}</em
|
||||
@@ -31,14 +43,38 @@
|
||||
</div>
|
||||
|
||||
<div v-if="item.refer_type == 'book'" class="reference-item">
|
||||
<b class="ref-number-prefix">{{ index + 1 }}.</b>
|
||||
<template v-if="!isUncitedReference(item)">
|
||||
<b class="ref-number-prefix">{{ index + 1 }}.</b>
|
||||
</template>
|
||||
<el-tag
|
||||
v-else
|
||||
class="uncited-tag"
|
||||
size="mini"
|
||||
type="info"
|
||||
effect="plain"
|
||||
style="margin-right: 8px"
|
||||
>
|
||||
{{ $t('wordCite.uncited') }}
|
||||
</el-tag>
|
||||
{{ item.author }} <span v-html="formatTitle(item.title)"></span>. {{ item.dateno }}.
|
||||
<br />
|
||||
<a class="doiLink" :href="item.isbn" target="_blank">{{ item.isbn }}</a>
|
||||
</div>
|
||||
|
||||
<div v-if="item.refer_type == 'other'" class="reference-item">
|
||||
<b class="ref-number-prefix">{{ index + 1 }}.</b>
|
||||
<template v-if="!isUncitedReference(item)">
|
||||
<b class="ref-number-prefix">{{ index + 1 }}.</b>
|
||||
</template>
|
||||
<el-tag
|
||||
v-else
|
||||
class="uncited-tag"
|
||||
size="mini"
|
||||
type="info"
|
||||
effect="plain"
|
||||
style="margin-right: 8px"
|
||||
>
|
||||
{{ $t('wordCite.uncited') }}
|
||||
</el-tag>
|
||||
<span v-html="formatTitle(item.refer_frag)"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -325,6 +361,11 @@ export default {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
/** 父组件传入的正文首次出现顺序(被引用的 p_refer_id 列表);不在其中的视为“未引用” */
|
||||
citeOrderFromParent: {
|
||||
type: Array,
|
||||
default: () => null
|
||||
},
|
||||
gridData: {
|
||||
type: String,
|
||||
default: ''
|
||||
@@ -340,6 +381,13 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isUncitedReference(row) {
|
||||
const id = row && row.p_refer_id != null ? String(row.p_refer_id) : '';
|
||||
if (!id) return false;
|
||||
const order = Array.isArray(this.citeOrderFromParent) ? this.citeOrderFromParent.map(String) : [];
|
||||
if (!order.length) return false;
|
||||
return !order.includes(id);
|
||||
},
|
||||
/** 与当前 chanFerForm 数组顺序一致:行内 index 置为 1-based 序号(排序/正文重排后同步) */
|
||||
syncReferenceRowIndices() {
|
||||
const list = this.chanFerForm;
|
||||
@@ -1961,6 +2009,25 @@ export default {
|
||||
background-color: #f9fbff;
|
||||
}
|
||||
|
||||
.uncited-row .ref-content-area,
|
||||
.uncited-row .reference-item,
|
||||
.uncited-row .author-text,
|
||||
.uncited-row em,
|
||||
.uncited-row span {
|
||||
color: #909399 !important;
|
||||
}
|
||||
|
||||
.uncited-row .doiLink,
|
||||
.uncited-row .doi-link-text {
|
||||
color: #909399 !important;
|
||||
}
|
||||
|
||||
::v-deep .uncited-tag.el-tag {
|
||||
background-color: #f56c6c !important;
|
||||
border-color: #f56c6c !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* 序号与状态 */
|
||||
.ref-index-area {
|
||||
width: 50px;
|
||||
|
||||
@@ -279,11 +279,14 @@
|
||||
journal_id: this.query.journal_id || (this.journalList[0] ? this.journalList[0].journal_id : null),
|
||||
account: '',
|
||||
password: '',
|
||||
// password: '123456qwe..%%%',
|
||||
smtp_from_name: '',
|
||||
smtp_host: 'mail.tmrjournals.co.nz',
|
||||
// smtp_host: 'smtp.mxhichina.com',
|
||||
smtp_port: '465',
|
||||
smtp_encryption: 'ssl',
|
||||
imap_host: 'mail.tmrjournals.co.nz',
|
||||
// imap_host: 'imap.qiye.aliyun.com',
|
||||
imap_port: '993',
|
||||
};
|
||||
this.dialogVisible = true;
|
||||
|
||||
Reference in New Issue
Block a user