tijiao
This commit is contained in:
@@ -102,6 +102,22 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST x-www-form-urlencoded,数组字段序列化为 list[]=a&list[]=b(兼容 PHP 批量接口)
|
||||||
|
*/
|
||||||
|
postFormBracket(url, params) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
service
|
||||||
|
.post(url, qs.stringify(params, { arrayFormat: 'brackets' }))
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res.data)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* post方法,对应post请求
|
* post方法,对应post请求
|
||||||
* @param {String} url [请求的url地址]
|
* @param {String} url [请求的url地址]
|
||||||
|
|||||||
@@ -1164,6 +1164,7 @@ colTitle: 'Template title',
|
|||||||
notFoundById: 'No reference found for citation ID {id}',
|
notFoundById: 'No reference found for citation ID {id}',
|
||||||
selectRef: 'Select Reference',
|
selectRef: 'Select Reference',
|
||||||
reference: 'Reference',
|
reference: 'Reference',
|
||||||
|
originalOrder: 'Original order',
|
||||||
uncited: 'Uncited',
|
uncited: 'Uncited',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
@@ -1178,7 +1179,10 @@ colTitle: 'Template title',
|
|||||||
removeRefNeedClickCite: 'Click a citation in the text first, then choose Reference remove.',
|
removeRefNeedClickCite: 'Click a citation in the text first, then choose Reference remove.',
|
||||||
quickPickPlaceholder: 'Enter e.g. [5, 6, 10-15] to auto-select',
|
quickPickPlaceholder: 'Enter e.g. [5, 6, 10-15] to auto-select',
|
||||||
quickPickApply: 'Link References',
|
quickPickApply: 'Link References',
|
||||||
quickPickClear: 'Clear selection'
|
quickPickClear: 'Clear selection',
|
||||||
|
currentCiteNo: 'Current',
|
||||||
|
locateInBody: 'Click to scroll to citation in text',
|
||||||
|
locateInRefHint: 'Highlight this entry in the list below'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1149,6 +1149,7 @@ const zh = {
|
|||||||
notFoundById: '未查询到编号{id}相关参考文献',
|
notFoundById: '未查询到编号{id}相关参考文献',
|
||||||
selectRef: '选择参考文献',
|
selectRef: '选择参考文献',
|
||||||
reference: '参考文献',
|
reference: '参考文献',
|
||||||
|
originalOrder: '原排序',
|
||||||
uncited: '未引用',
|
uncited: '未引用',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
confirm: '确认',
|
confirm: '确认',
|
||||||
@@ -1163,7 +1164,10 @@ const zh = {
|
|||||||
removeRefNeedClickCite: '请先在正文中点击要删除的引用角标,再点「移除参考文献」。',
|
removeRefNeedClickCite: '请先在正文中点击要删除的引用角标,再点「移除参考文献」。',
|
||||||
quickPickPlaceholder: '输入如 [5, 6, 10-15] 自动勾选对应参考文献',
|
quickPickPlaceholder: '输入如 [5, 6, 10-15] 自动勾选对应参考文献',
|
||||||
quickPickApply: '链接参考文献',
|
quickPickApply: '链接参考文献',
|
||||||
quickPickClear: '清空勾选'
|
quickPickClear: '清空勾选',
|
||||||
|
currentCiteNo: '当前序号',
|
||||||
|
locateInBody: '点击定位正文角标',
|
||||||
|
locateInRefHint: '在下方列表中高亮该条'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -136,16 +136,7 @@
|
|||||||
<!-- <div v-else style="padding: 20px; box-sizing: border-box"></div> -->
|
<!-- <div v-else style="padding: 20px; box-sizing: border-box"></div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content_box mt20 stepbox">
|
|
||||||
<!-- 文章引用 -->
|
|
||||||
<div class="con">
|
|
||||||
<h4 class="con-title">{{ this.$t('PreAccept.step2') }}</h4>
|
|
||||||
<p style="color: #505050; font-size: 14px; padding: 20px; box-sizing: border-box">
|
|
||||||
<el-button @click="goGenerateCharts(thisArtcleId)" icon="el-icon-edit" type="text">Edit</el-button>
|
|
||||||
</p>
|
|
||||||
<!-- <div v-else style="padding: 20px; box-sizing: border-box"></div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="content_box mt20 stepbox">
|
<div class="content_box mt20 stepbox">
|
||||||
<!-- 文章引用 -->
|
<!-- 文章引用 -->
|
||||||
<div class="con">
|
<div class="con">
|
||||||
@@ -170,7 +161,16 @@
|
|||||||
<!-- <div v-else style="padding: 20px; box-sizing: border-box"></div> -->
|
<!-- <div v-else style="padding: 20px; box-sizing: border-box"></div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="content_box mt20 stepbox">
|
||||||
|
<!-- 文章引用 -->
|
||||||
|
<div class="con">
|
||||||
|
<h4 class="con-title">{{ this.$t('PreAccept.step2') }}</h4>
|
||||||
|
<p style="color: #505050; font-size: 14px; padding: 20px; box-sizing: border-box">
|
||||||
|
<el-button @click="goGenerateCharts(thisArtcleId)" icon="el-icon-edit" type="text">Edit</el-button>
|
||||||
|
</p>
|
||||||
|
<!-- <div v-else style="padding: 20px; box-sizing: border-box"></div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- 答疑 -->
|
<!-- 答疑 -->
|
||||||
<div class="mt20 helpcontent">
|
<div class="mt20 helpcontent">
|
||||||
<div class="flexbox">
|
<div class="flexbox">
|
||||||
|
|||||||
@@ -595,6 +595,17 @@
|
|||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('wordCite.originalOrder')" width="120" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{
|
||||||
|
scope.row.old_index != null && scope.row.old_index !== ''
|
||||||
|
? scope.row.old_index+1
|
||||||
|
: scope.row.old_index != null && scope.row.old_index !== ''
|
||||||
|
? scope.row.old_index+1
|
||||||
|
: '—'
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column align="center" :width="'200'">
|
<el-table-column align="center" :width="'200'">
|
||||||
<div slot-scope="scope">
|
<div slot-scope="scope">
|
||||||
<div class="operation" style="">
|
<div class="operation" style="">
|
||||||
@@ -890,6 +901,8 @@ export default {
|
|||||||
refSelectorSource: 'commonContent',
|
refSelectorSource: 'commonContent',
|
||||||
/** 表格抽屉内合并稿防抖:按全文首次出现顺序更新 articleCiteIdOrder,避免角标停留在列表序号 [38] */
|
/** 表格抽屉内合并稿防抖:按全文首次出现顺序更新 articleCiteIdOrder,避免角标停留在列表序号 [38] */
|
||||||
_tableReorderTimer: null,
|
_tableReorderTimer: null,
|
||||||
|
/** 最近一次 getReferList 返回的 p_refer_id 顺序;与当前 chanFerForm 不一致时回写 batchUpdateRefer */
|
||||||
|
_lastReferListApiOrder: null,
|
||||||
/** 打开选择器时若带 currentRefIds(编辑已有引用),排序用「传值计算的顺序」;关闭后清空 */
|
/** 打开选择器时若带 currentRefIds(编辑已有引用),排序用「传值计算的顺序」;关闭后清空 */
|
||||||
refSelectorContextIds: [],
|
refSelectorContextIds: [],
|
||||||
/** 选择参考文献弹窗:按 # 序号快速勾选,如 [5, 6, 10-15] */
|
/** 选择参考文献弹窗:按 # 序号快速勾选,如 [5, 6, 10-15] */
|
||||||
@@ -1496,6 +1509,7 @@ export default {
|
|||||||
p_article_id: this.p_article_id
|
p_article_id: this.p_article_id
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
this._lastReferListApiOrder = this.referIdOrderSnapshot(res.data && res.data.refers).slice();
|
||||||
this.chanFerForm = res.data.refers;
|
this.chanFerForm = res.data.refers;
|
||||||
this.chanFerFormRepeatList = Object.values(res.data.repeat || {});
|
this.chanFerFormRepeatList = Object.values(res.data.repeat || {});
|
||||||
for (let i = 0; i < this.chanFerForm.length; i++) {
|
for (let i = 0; i < this.chanFerForm.length; i++) {
|
||||||
@@ -1522,6 +1536,8 @@ export default {
|
|||||||
}
|
}
|
||||||
/** getReferList 会整表覆盖 chanFerForm,需再按正文首次出现顺序对齐,否则下方列表与 [n] 脱节 */
|
/** getReferList 会整表覆盖 chanFerForm,需再按正文首次出现顺序对齐,否则下方列表与 [n] 脱节 */
|
||||||
this.applyRefOrderAfterFetchReferList();
|
this.applyRefOrderAfterFetchReferList();
|
||||||
|
/** 正文序与接口序不一致时回写(Main_List 未就绪时可能无变化,getDate 后会再比一次) */
|
||||||
|
this.scheduleTryBatchSyncReferOrderIfOutOfSync();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -1774,6 +1790,69 @@ export default {
|
|||||||
if (sig(next) === sig(refs)) return;
|
if (sig(next) === sig(refs)) return;
|
||||||
this.chanFerForm = next;
|
this.chanFerForm = next;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 将当前 chanFerForm 全文同步到后端(顺序 + 各字段内容与 getReferList 行结构一致)。
|
||||||
|
* 接口:api/References/batchUpdateRefer:p_article_id + list(JSON 字符串,文献对象数组,顺序即保存序)
|
||||||
|
*/
|
||||||
|
syncBatchReferenceOrderToServer() {
|
||||||
|
const pid = this.p_article_id;
|
||||||
|
if (pid == null || pid === '') return Promise.resolve();
|
||||||
|
const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : [];
|
||||||
|
if (refs.length === 0) return Promise.resolve();
|
||||||
|
let listJson;
|
||||||
|
try {
|
||||||
|
listJson = JSON.stringify(JSON.parse(JSON.stringify(refs)));
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return this.$api
|
||||||
|
.post('api/References/batchUpdateRefer', {
|
||||||
|
p_article_id: String(pid),
|
||||||
|
list: listJson
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
const ok = res && (res.code === 0 || res.code === 1 || res.status === 1);
|
||||||
|
if (ok) {
|
||||||
|
this._lastReferListApiOrder = this.referIdOrderSnapshot(this.chanFerForm).slice();
|
||||||
|
}
|
||||||
|
if (!ok && res && (res.msg || res.message)) {
|
||||||
|
console.warn('[batchUpdateRefer]', res.msg || res.message);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn('[batchUpdateRefer] request failed', err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 从 getReferList 原始 refers 提取 p_refer_id 顺序(字符串,便于比较) */
|
||||||
|
referIdOrderSnapshot(refs) {
|
||||||
|
return (Array.isArray(refs) ? refs : [])
|
||||||
|
.map((r) => (r && r.p_refer_id != null ? String(r.p_refer_id) : ''))
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
|
/** 当前 chanFerForm 顺序与 _lastReferListApiOrder 不一致则调用 batchUpdateRefer */
|
||||||
|
tryBatchSyncReferOrderIfOutOfSync() {
|
||||||
|
if (this._lastReferListApiOrder == null) return;
|
||||||
|
const track = this._lastReferListApiOrder.map(String);
|
||||||
|
const cur = this.referIdOrderSnapshot(this.chanFerForm);
|
||||||
|
if (cur.length === 0) return;
|
||||||
|
const same = track.length === cur.length && track.every((id, i) => id === cur[i]);
|
||||||
|
if (!same) {
|
||||||
|
this.syncBatchReferenceOrderToServer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 等 applyRefOrder / syncRefOrder / 表格抽屉内 nextTick 跑完后再比对顺序。
|
||||||
|
*/
|
||||||
|
scheduleTryBatchSyncReferOrderIfOutOfSync() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.tryBatchSyncReferOrderIfOutOfSync();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
extractBracketCiteNumbersFromText(raw) {
|
extractBracketCiteNumbersFromText(raw) {
|
||||||
const out = [];
|
const out = [];
|
||||||
if (!raw || typeof raw !== 'string') return out;
|
if (!raw || typeof raw !== 'string') return out;
|
||||||
@@ -2278,6 +2357,9 @@ export default {
|
|||||||
/** 以接口回写后的 Main_List 再对齐一次;稿面 word 会由 contentList 监听触发 syncRefOrder */
|
/** 以接口回写后的 Main_List 再对齐一次;稿面 word 会由 contentList 监听触发 syncRefOrder */
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.reorderReferencesFromMainListBody(null, null);
|
this.reorderReferencesFromMainListBody(null, null);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncBatchReferenceOrderToServer();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
loading.close();
|
loading.close();
|
||||||
@@ -3294,6 +3376,8 @@ export default {
|
|||||||
/** 正文加载后按 Main_List 扫出全文首次出现顺序,写入 articleCiteIdOrder,弹窗角标与稿面一致 */
|
/** 正文加载后按 Main_List 扫出全文首次出现顺序,写入 articleCiteIdOrder,弹窗角标与稿面一致 */
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.reorderReferencesFromMainListBody(null, null);
|
this.reorderReferencesFromMainListBody(null, null);
|
||||||
|
/** getReferList 早于 Main_List 时在此才完成正文序重排,与接口序不一致则回写 */
|
||||||
|
this.scheduleTryBatchSyncReferOrderIfOutOfSync();
|
||||||
});
|
});
|
||||||
loading.close();
|
loading.close();
|
||||||
});
|
});
|
||||||
@@ -3519,6 +3603,9 @@ export default {
|
|||||||
if (w && typeof w.syncRefOrder === 'function') {
|
if (w && typeof w.syncRefOrder === 'function') {
|
||||||
w.syncRefOrder();
|
w.syncRefOrder();
|
||||||
}
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncBatchReferenceOrderToServer();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg);
|
this.$message.error(res.msg);
|
||||||
@@ -3571,6 +3658,9 @@ export default {
|
|||||||
if (w && typeof w.syncRefOrder === 'function') {
|
if (w && typeof w.syncRefOrder === 'function') {
|
||||||
w.syncRefOrder();
|
w.syncRefOrder();
|
||||||
}
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncBatchReferenceOrderToServer();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg);
|
this.$message.error(res.msg);
|
||||||
|
|||||||
@@ -332,11 +332,11 @@ export default {
|
|||||||
// refName: 'setFiveRef',
|
// refName: 'setFiveRef',
|
||||||
// rongCont: 'Modify the article body.'
|
// rongCont: 'Modify the article body.'
|
||||||
// },
|
// },
|
||||||
{
|
// {
|
||||||
name: 'Text Proofread',
|
// name: 'Text Proofread',
|
||||||
refName: 'setThreeRef',
|
// refName: 'setThreeRef',
|
||||||
rongCont: 'HTML layout.'
|
// rongCont: 'HTML layout.'
|
||||||
}
|
// }
|
||||||
// {
|
// {
|
||||||
// name: 'Create Build',
|
// name: 'Create Build',
|
||||||
// refName: 'setSevenRef',
|
// refName: 'setSevenRef',
|
||||||
|
|||||||
@@ -218,6 +218,8 @@ export default {
|
|||||||
refSelectorIsEdit: false,
|
refSelectorIsEdit: false,
|
||||||
refSelectedRows: [],
|
refSelectedRows: [],
|
||||||
refSelectorSource: 'commonContent',
|
refSelectorSource: 'commonContent',
|
||||||
|
/** 最近一次 getReferList 返回的 p_refer_id 顺序;与当前 chanFerForm 不一致时回写 batchUpdateRefer */
|
||||||
|
_lastReferListApiOrder: null,
|
||||||
/** 打开选择器时若带 currentRefIds(编辑已有引用),排序用「传值计算的顺序」;关闭后清空 */
|
/** 打开选择器时若带 currentRefIds(编辑已有引用),排序用「传值计算的顺序」;关闭后清空 */
|
||||||
refSelectorContextIds: [],
|
refSelectorContextIds: [],
|
||||||
/** 选择参考文献弹窗:按 # 序号快速勾选,如 [5, 6, 10-15] */
|
/** 选择参考文献弹窗:按 # 序号快速勾选,如 [5, 6, 10-15] */
|
||||||
@@ -448,6 +450,7 @@ export default {
|
|||||||
p_article_id: this.p_article_id
|
p_article_id: this.p_article_id
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
this._lastReferListApiOrder = this.referIdOrderSnapshot(res.data && res.data.refers).slice();
|
||||||
this.chanFerForm = res.data.refers;
|
this.chanFerForm = res.data.refers;
|
||||||
this.chanFerFormRepeatList = Object.values(res.data.repeat || {});
|
this.chanFerFormRepeatList = Object.values(res.data.repeat || {});
|
||||||
for (let i = 0; i < this.chanFerForm.length; i++) {
|
for (let i = 0; i < this.chanFerForm.length; i++) {
|
||||||
@@ -474,6 +477,7 @@ export default {
|
|||||||
}
|
}
|
||||||
/** getReferList 会整表覆盖 chanFerForm,需再按正文首次出现顺序对齐,否则下方列表与 [n] 脱节 */
|
/** getReferList 会整表覆盖 chanFerForm,需再按正文首次出现顺序对齐,否则下方列表与 [n] 脱节 */
|
||||||
this.applyRefOrderAfterFetchReferList();
|
this.applyRefOrderAfterFetchReferList();
|
||||||
|
this.scheduleTryBatchSyncReferOrderIfOutOfSync();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -647,6 +651,64 @@ export default {
|
|||||||
if (sig(next) === sig(refs)) return;
|
if (sig(next) === sig(refs)) return;
|
||||||
this.chanFerForm = next;
|
this.chanFerForm = next;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 将当前 chanFerForm 全文同步到后端(顺序 + 各字段)。
|
||||||
|
* 接口:api/References/batchUpdateRefer:p_article_id + list(JSON 字符串,文献对象数组)
|
||||||
|
*/
|
||||||
|
syncBatchReferenceOrderToServer() {
|
||||||
|
const pid = this.p_article_id;
|
||||||
|
if (pid == null || pid === '') return Promise.resolve();
|
||||||
|
const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : [];
|
||||||
|
if (refs.length === 0) return Promise.resolve();
|
||||||
|
let listJson;
|
||||||
|
try {
|
||||||
|
listJson = JSON.stringify(JSON.parse(JSON.stringify(refs)));
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return this.$api
|
||||||
|
.post('api/References/batchUpdateRefer', {
|
||||||
|
p_article_id: String(pid),
|
||||||
|
list: listJson
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
const ok = res && (res.code === 0 || res.code === 1 || res.status === 1);
|
||||||
|
if (ok) {
|
||||||
|
this._lastReferListApiOrder = this.referIdOrderSnapshot(this.chanFerForm).slice();
|
||||||
|
}
|
||||||
|
if (!ok && res && (res.msg || res.message)) {
|
||||||
|
console.warn('[batchUpdateRefer]', res.msg || res.message);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn('[batchUpdateRefer] request failed', err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
referIdOrderSnapshot(refs) {
|
||||||
|
return (Array.isArray(refs) ? refs : [])
|
||||||
|
.map((r) => (r && r.p_refer_id != null ? String(r.p_refer_id) : ''))
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
|
tryBatchSyncReferOrderIfOutOfSync() {
|
||||||
|
if (this._lastReferListApiOrder == null) return;
|
||||||
|
const track = this._lastReferListApiOrder.map(String);
|
||||||
|
const cur = this.referIdOrderSnapshot(this.chanFerForm);
|
||||||
|
if (cur.length === 0) return;
|
||||||
|
const same = track.length === cur.length && track.every((id, i) => id === cur[i]);
|
||||||
|
if (!same) {
|
||||||
|
this.syncBatchReferenceOrderToServer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scheduleTryBatchSyncReferOrderIfOutOfSync() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.tryBatchSyncReferOrderIfOutOfSync();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
/** 编辑弹窗内正文一变立即按全文合并稿重排参考文献(不再防抖,选中/输入后列表马上跟正文一致) */
|
/** 编辑弹窗内正文一变立即按全文合并稿重排参考文献(不再防抖,选中/输入后列表马上跟正文一致) */
|
||||||
scheduleReorderFromEditModal(html) {
|
scheduleReorderFromEditModal(html) {
|
||||||
if (!this.editVisible || !this.currentContent || this.currentContent.am_id == null) return;
|
if (!this.editVisible || !this.currentContent || this.currentContent.am_id == null) return;
|
||||||
@@ -1066,6 +1128,9 @@ export default {
|
|||||||
/** 以接口回写后的 Main_List 再对齐一次;稿面 word 会由 contentList 监听触发 syncRefOrder */
|
/** 以接口回写后的 Main_List 再对齐一次;稿面 word 会由 contentList 监听触发 syncRefOrder */
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.reorderReferencesFromMainListBody(null, null);
|
this.reorderReferencesFromMainListBody(null, null);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncBatchReferenceOrderToServer();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
loading.close();
|
loading.close();
|
||||||
@@ -2078,6 +2143,10 @@ export default {
|
|||||||
if (this.$refs.catalogue) {
|
if (this.$refs.catalogue) {
|
||||||
await this.$refs.catalogue.getCatalogueList();
|
await this.$refs.catalogue.getCatalogueList();
|
||||||
}
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.reorderReferencesFromMainListBody(null, null);
|
||||||
|
this.scheduleTryBatchSyncReferOrderIfOutOfSync();
|
||||||
|
});
|
||||||
loading.close();
|
loading.close();
|
||||||
});
|
});
|
||||||
// }, 1000);
|
// }, 1000);
|
||||||
@@ -2304,6 +2373,9 @@ export default {
|
|||||||
if (w && typeof w.syncRefOrder === 'function') {
|
if (w && typeof w.syncRefOrder === 'function') {
|
||||||
w.syncRefOrder();
|
w.syncRefOrder();
|
||||||
}
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncBatchReferenceOrderToServer();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg);
|
this.$message.error(res.msg);
|
||||||
@@ -2356,6 +2428,9 @@ export default {
|
|||||||
if (w && typeof w.syncRefOrder === 'function') {
|
if (w && typeof w.syncRefOrder === 'function') {
|
||||||
w.syncRefOrder();
|
w.syncRefOrder();
|
||||||
}
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncBatchReferenceOrderToServer();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg);
|
this.$message.error(res.msg);
|
||||||
|
|||||||
@@ -134,6 +134,37 @@ export default {
|
|||||||
hasReferencesForAutoLink() {
|
hasReferencesForAutoLink() {
|
||||||
const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : [];
|
const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : [];
|
||||||
return refs.some((r) => r && r.p_refer_id != null && String(r.p_refer_id).trim() !== '');
|
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。
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
},
|
||||||
|
/** p_refer_id → 表格角标序号(old_index+1),用于表格内渲染 [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;
|
||||||
|
});
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -271,7 +302,7 @@ export default {
|
|||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
},
|
},
|
||||||
/** 与 word.vue renderCiteLabels:tooltip 行按引用序号排序 */
|
/** 与 word.vue renderCiteLabels:合并角标 id 按 citeMap 序号排序 */
|
||||||
sortAutociteIdsByCiteNumber(ids) {
|
sortAutociteIdsByCiteNumber(ids) {
|
||||||
const uniq = [...new Set(ids.map(String))];
|
const uniq = [...new Set(ids.map(String))];
|
||||||
const map = this.citeMap || {};
|
const map = this.citeMap || {};
|
||||||
@@ -316,10 +347,62 @@ export default {
|
|||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
},
|
},
|
||||||
/** 解析 [1]、[1,2]、[1–4] 等括号内数字列表(不含方括号) */
|
/** 自动匹配 [n]:表格内用 old_index+1 映射;mytable 之后段落仍用全局 citeMap */
|
||||||
|
_getBracketNumToIdMaps() {
|
||||||
|
const globalMap = this.buildNumToRefIdMap();
|
||||||
|
if (this.type !== 'table') {
|
||||||
|
return { primary: globalMap, afterTable: globalMap };
|
||||||
|
}
|
||||||
|
const tm = this.tableBracketNumToRefIdMap || {};
|
||||||
|
const has = Object.keys(tm).length > 0;
|
||||||
|
return {
|
||||||
|
primary: has ? tm : globalMap,
|
||||||
|
afterTable: globalMap
|
||||||
|
};
|
||||||
|
},
|
||||||
|
sortAutociteIdsByTableBracketNumber(ids) {
|
||||||
|
const uniq = [...new Set(ids.map(String))];
|
||||||
|
const numById = this.tableRefIdToBracketNum || {};
|
||||||
|
return uniq.sort((a, b) => {
|
||||||
|
const na = numById[a];
|
||||||
|
const nb = numById[b];
|
||||||
|
const ha = na != null && na !== '' && !Number.isNaN(Number(na));
|
||||||
|
const hb = nb != null && nb !== '' && !Number.isNaN(Number(nb));
|
||||||
|
if (ha && hb) return Number(na) - Number(nb);
|
||||||
|
if (ha) return -1;
|
||||||
|
if (hb) return 1;
|
||||||
|
return String(a).localeCompare(String(b));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_sortAutociteIdsForDisplay(ids) {
|
||||||
|
if (this.type === 'table' && Object.keys(this.tableBracketNumToRefIdMap || {}).length > 0) {
|
||||||
|
return this.sortAutociteIdsByTableBracketNumber(ids);
|
||||||
|
}
|
||||||
|
return this.sortAutociteIdsByCiteNumber(ids);
|
||||||
|
},
|
||||||
|
_sortIdsAfterBracketMatch(ids, tableOffset) {
|
||||||
|
if (
|
||||||
|
this.type === 'table' &&
|
||||||
|
tableOffset === 0 &&
|
||||||
|
Object.keys(this.tableBracketNumToRefIdMap || {}).length > 0
|
||||||
|
) {
|
||||||
|
return this.sortAutociteIdsByTableBracketNumber(ids);
|
||||||
|
}
|
||||||
|
return this.sortAutociteIdsByCiteNumber(ids);
|
||||||
|
},
|
||||||
|
/** 解析 [1]、[1,2]、[1–4]、[1, 2–3, 4] 等括号内数字列表(不含方括号) */
|
||||||
parseBracketInnerToNumbers(inner) {
|
parseBracketInnerToNumbers(inner) {
|
||||||
if (!inner || typeof inner !== 'string') return [];
|
if (!inner || typeof inner !== 'string') return [];
|
||||||
const t = inner.trim().replace(/,/g, ',');
|
const t = inner.trim().replace(/,/g, ',');
|
||||||
|
// 先按逗号拆段,再对每段解析单号或区间;避免 parseInt('41-42') 只得到 41
|
||||||
|
if (/[,,]/.test(t)) {
|
||||||
|
const parts = t.split(/[,,]/).map((s) => String(s).trim()).filter(Boolean);
|
||||||
|
const out = [];
|
||||||
|
parts.forEach((part) => {
|
||||||
|
out.push(...this.parseBracketInnerToNumbers(part));
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
const range = t.match(/^(\d+)\s*[-–—]\s*(\d+)$/);
|
const range = t.match(/^(\d+)\s*[-–—]\s*(\d+)$/);
|
||||||
if (range) {
|
if (range) {
|
||||||
const a = Number(range[1]);
|
const a = Number(range[1]);
|
||||||
@@ -331,12 +414,6 @@ export default {
|
|||||||
for (let i = lo; i <= hi; i++) out.push(i);
|
for (let i = lo; i <= hi; i++) out.push(i);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
if (/[,,]/.test(t)) {
|
|
||||||
return t
|
|
||||||
.split(/[,,]/)
|
|
||||||
.map((x) => parseInt(String(x).trim(), 10))
|
|
||||||
.filter((n) => !Number.isNaN(n));
|
|
||||||
}
|
|
||||||
const n = parseInt(t, 10);
|
const n = parseInt(t, 10);
|
||||||
return Number.isNaN(n) ? [] : [n];
|
return Number.isNaN(n) ? [] : [n];
|
||||||
},
|
},
|
||||||
@@ -352,8 +429,8 @@ export default {
|
|||||||
this.$message.warning(this.$t('wordCite.noRefs'));
|
this.$message.warning(this.$t('wordCite.noRefs'));
|
||||||
return { replaced: 0 };
|
return { replaced: 0 };
|
||||||
}
|
}
|
||||||
const numToId = this.buildNumToRefIdMap();
|
const maps = this._getBracketNumToIdMaps();
|
||||||
if (Object.keys(numToId).length === 0) {
|
if (Object.keys(maps.primary).length === 0) {
|
||||||
this.$message.warning(this.$t('wordCite.noRefs'));
|
this.$message.warning(this.$t('wordCite.noRefs'));
|
||||||
return { replaced: 0 };
|
return { replaced: 0 };
|
||||||
}
|
}
|
||||||
@@ -362,7 +439,7 @@ export default {
|
|||||||
if (!body) return { replaced: 0 };
|
if (!body) return { replaced: 0 };
|
||||||
let replaced = 0;
|
let replaced = 0;
|
||||||
ed.undoManager.transact(() => {
|
ed.undoManager.transact(() => {
|
||||||
replaced = this._replaceBracketCitesInDocOrder(body, doc, numToId, { tableOffset: 0 });
|
replaced = this._replaceBracketCitesInDocOrder(body, doc, maps, { tableOffset: 0 });
|
||||||
});
|
});
|
||||||
this.renderAutociteInEditor(ed);
|
this.renderAutociteInEditor(ed);
|
||||||
ed.fire('change');
|
ed.fire('change');
|
||||||
@@ -377,8 +454,8 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : [];
|
const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : [];
|
||||||
const numToId = this.buildNumToRefIdMap();
|
const maps = this._getBracketNumToIdMaps();
|
||||||
if (refs.length && Object.keys(numToId).length) {
|
if (refs.length && Object.keys(maps.primary).length) {
|
||||||
this.$message.info(this.$t('wordCite.matchBracketRefsNone'));
|
this.$message.info(this.$t('wordCite.matchBracketRefsNone'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -418,9 +495,9 @@ export default {
|
|||||||
* 按文档顺序遍历,使「表格链接」后任意后续段落里的 [n] 都能用上该表的最大全局序号偏移。
|
* 按文档顺序遍历,使「表格链接」后任意后续段落里的 [n] 都能用上该表的最大全局序号偏移。
|
||||||
* 旧实现只在同一父节点下、MYTABLE 与后续文本为兄弟时才生效,MYTABLE 在上一段、角标在下一段时会失效。
|
* 旧实现只在同一父节点下、MYTABLE 与后续文本为兄弟时才生效,MYTABLE 在上一段、角标在下一段时会失效。
|
||||||
*/
|
*/
|
||||||
_replaceBracketCitesInDocOrder(node, doc, numToId, state) {
|
_replaceBracketCitesInDocOrder(node, doc, maps, state) {
|
||||||
if (node.nodeType === 3) {
|
if (node.nodeType === 3) {
|
||||||
return this._processTextNodeForBracketCites(node, doc, numToId, state.tableOffset);
|
return this._processTextNodeForBracketCites(node, doc, maps, state.tableOffset);
|
||||||
}
|
}
|
||||||
if (node.nodeType !== 1) return 0;
|
if (node.nodeType !== 1) return 0;
|
||||||
const name = node.nodeName;
|
const name = node.nodeName;
|
||||||
@@ -431,7 +508,7 @@ export default {
|
|||||||
if (isMytable) {
|
if (isMytable) {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
Array.from(node.childNodes).forEach((c) => {
|
Array.from(node.childNodes).forEach((c) => {
|
||||||
total += this._replaceBracketCitesInDocOrder(c, doc, numToId, state);
|
total += this._replaceBracketCitesInDocOrder(c, doc, maps, state);
|
||||||
});
|
});
|
||||||
const tid = (node.getAttribute && node.getAttribute('data-id')) || '';
|
const tid = (node.getAttribute && node.getAttribute('data-id')) || '';
|
||||||
const map = this.tableLinkCiteMaxMap || {};
|
const map = this.tableLinkCiteMaxMap || {};
|
||||||
@@ -443,11 +520,12 @@ export default {
|
|||||||
}
|
}
|
||||||
let total = 0;
|
let total = 0;
|
||||||
Array.from(node.childNodes).forEach((c) => {
|
Array.from(node.childNodes).forEach((c) => {
|
||||||
total += this._replaceBracketCitesInDocOrder(c, doc, numToId, state);
|
total += this._replaceBracketCitesInDocOrder(c, doc, maps, state);
|
||||||
});
|
});
|
||||||
return total;
|
return total;
|
||||||
},
|
},
|
||||||
_processTextNodeForBracketCites(textNode, doc, numToId, tableOffset = 0) {
|
_processTextNodeForBracketCites(textNode, doc, maps, tableOffset = 0) {
|
||||||
|
const numToId = tableOffset > 0 ? maps.afterTable : maps.primary;
|
||||||
const text = textNode.textContent;
|
const text = textNode.textContent;
|
||||||
const re = /\[([\d\s,,\-–—]+)\]/g;
|
const re = /\[([\d\s,,\-–—]+)\]/g;
|
||||||
let m;
|
let m;
|
||||||
@@ -465,11 +543,24 @@ export default {
|
|||||||
skippedSpecial++;
|
skippedSpecial++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// 任一序号在参考文献中无对应(含超出列表、tableOffset 后仍无效)则整段不转换,避免部分匹配
|
||||||
|
const mapNo = (n) => (n > 0 && tableOffset > 0 ? n + tableOffset - 1 : n);
|
||||||
|
if (
|
||||||
|
!nums.length ||
|
||||||
|
nums.some((n) => {
|
||||||
|
const mappedNo = mapNo(n);
|
||||||
|
return !numToId[mappedNo];
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
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 ids = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
nums.forEach((n) => {
|
nums.forEach((n) => {
|
||||||
// 表格链接后的局部序号从 tableMax+1 开始接续:1->max+1, 2->max+2 ...
|
const mappedNo = mapNo(n);
|
||||||
const mappedNo = n > 0 && tableOffset > 0 ? n + tableOffset - 1 : n;
|
|
||||||
const id = numToId[mappedNo];
|
const id = numToId[mappedNo];
|
||||||
if (id && !seen.has(id)) {
|
if (id && !seen.has(id)) {
|
||||||
seen.add(id);
|
seen.add(id);
|
||||||
@@ -478,7 +569,7 @@ export default {
|
|||||||
});
|
});
|
||||||
pieces.push({ type: 'text', s: text.slice(lastIndex, m.index) });
|
pieces.push({ type: 'text', s: text.slice(lastIndex, m.index) });
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
const sorted = this.sortAutociteIdsByCiteNumber(ids);
|
const sorted = this._sortIdsAfterBracketMatch(ids, tableOffset);
|
||||||
pieces.push({ type: 'cite', ids: sorted });
|
pieces.push({ type: 'cite', ids: sorted });
|
||||||
replaced++;
|
replaced++;
|
||||||
} else {
|
} else {
|
||||||
@@ -517,8 +608,8 @@ export default {
|
|||||||
if (!body) return;
|
if (!body) return;
|
||||||
const allAutocites = Array.from(
|
const allAutocites = Array.from(
|
||||||
ed.dom && typeof ed.dom.select === 'function'
|
ed.dom && typeof ed.dom.select === 'function'
|
||||||
? ed.dom.select('mycite', body)
|
? ed.dom.select('mycite,autocite', body)
|
||||||
: body.querySelectorAll('mycite')
|
: body.querySelectorAll('mycite, autocite')
|
||||||
);
|
);
|
||||||
if (!allAutocites.length) return;
|
if (!allAutocites.length) return;
|
||||||
|
|
||||||
@@ -530,23 +621,30 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const citeMap = this.citeMap || {};
|
const citeMap = this.citeMap || {};
|
||||||
|
const tableNums = this.tableRefIdToBracketNum || {};
|
||||||
|
const useTableBracketNums = this.type === 'table' && Object.keys(this.tableBracketNumToRefIdMap || {}).length > 0;
|
||||||
|
|
||||||
allAutocites.forEach((el) => {
|
allAutocites.forEach((el) => {
|
||||||
ed.dom.setAttrib(el, 'contenteditable', 'false');
|
ed.dom.setAttrib(el, 'contenteditable', 'false');
|
||||||
el.style.display = '';
|
el.style.display = '';
|
||||||
const sortedAll = this.sortAutociteIdsByCiteNumber(
|
const sortedAll = this._sortAutociteIdsForDisplay(this.parseAutociteDataIds(el.getAttribute('data-id')));
|
||||||
this.parseAutociteDataIds(el.getAttribute('data-id'))
|
|
||||||
);
|
|
||||||
const validIds = sortedAll.filter((id) => refMap[String(id)]);
|
const validIds = sortedAll.filter((id) => refMap[String(id)]);
|
||||||
if (validIds.length < sortedAll.length) {
|
if (validIds.length < sortedAll.length) {
|
||||||
ed.dom.setAttrib(el, 'data-id', validIds.length ? validIds.join(',') : '');
|
ed.dom.setAttrib(el, 'data-id', validIds.length ? validIds.join(',') : '');
|
||||||
}
|
}
|
||||||
const sortedIds = validIds.length ? this.sortAutociteIdsByCiteNumber(validIds) : [];
|
const sortedIds = validIds.length ? this._sortAutociteIdsForDisplay(validIds) : [];
|
||||||
|
|
||||||
const parts = sortedIds.map((id) => {
|
const parts = sortedIds.map((id) => {
|
||||||
const ref = refMap[String(id)];
|
const ref = refMap[String(id)];
|
||||||
const no = ref ? citeMap[String(id)] : null;
|
const noCite = ref ? citeMap[String(id)] : null;
|
||||||
const num = no != null && no !== '' ? String(no) : null;
|
const noTable =
|
||||||
|
useTableBracketNums && tableNums[String(id)] != null ? tableNums[String(id)] : null;
|
||||||
|
const num =
|
||||||
|
noTable != null && noTable !== '' && !Number.isNaN(Number(noTable))
|
||||||
|
? String(noTable)
|
||||||
|
: noCite != null && noCite !== ''
|
||||||
|
? String(noCite)
|
||||||
|
: null;
|
||||||
return { id, ref, num };
|
return { id, ref, num };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -556,22 +654,6 @@ export default {
|
|||||||
.map(Number);
|
.map(Number);
|
||||||
const label = numsForLabel.length > 0 ? this.formatCiteNumbers(numsForLabel) : '';
|
const label = numsForLabel.length > 0 ? this.formatCiteNumbers(numsForLabel) : '';
|
||||||
|
|
||||||
const lines = parts
|
|
||||||
.filter((p) => p.ref)
|
|
||||||
.map((p) => {
|
|
||||||
const ref = p.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 numLabel = p.num;
|
|
||||||
if (numLabel) {
|
|
||||||
return `[${numLabel}] ${content}\nDOI: ${doi}`;
|
|
||||||
}
|
|
||||||
return `${content}\nDOI: ${doi}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!label) {
|
if (!label) {
|
||||||
ed.dom.remove(el);
|
ed.dom.remove(el);
|
||||||
return;
|
return;
|
||||||
@@ -579,8 +661,18 @@ export default {
|
|||||||
|
|
||||||
el.textContent = `[${label}]`;
|
el.textContent = `[${label}]`;
|
||||||
el.style.display = '';
|
el.style.display = '';
|
||||||
ed.dom.setAttrib(el, 'title', lines.join('\n'));
|
if (el.removeAttribute) el.removeAttribute('title');
|
||||||
ed.dom.setAttrib(el, 'data-cite-missing', null);
|
ed.dom.setAttrib(el, 'data-cite-missing', null);
|
||||||
|
if (String(el.tagName || '').toLowerCase() === 'autocite') {
|
||||||
|
const doc = ed.getDoc();
|
||||||
|
const nu = doc.createElement('mycite');
|
||||||
|
nu.setAttribute('data-id', el.getAttribute('data-id') || '');
|
||||||
|
nu.setAttribute('contenteditable', 'false');
|
||||||
|
const st = el.getAttribute('style');
|
||||||
|
if (st) nu.setAttribute('style', st);
|
||||||
|
nu.textContent = el.textContent;
|
||||||
|
el.parentNode.replaceChild(nu, el);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.padAutociteCaretPlaceholder(ed);
|
this.padAutociteCaretPlaceholder(ed);
|
||||||
},
|
},
|
||||||
@@ -589,7 +681,7 @@ export default {
|
|||||||
const doc = ed.getDoc();
|
const doc = ed.getDoc();
|
||||||
const body = doc.body;
|
const body = doc.body;
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
body.querySelectorAll('mycite').forEach((el) => {
|
body.querySelectorAll('mycite, autocite').forEach((el) => {
|
||||||
const next = el.nextSibling;
|
const next = el.nextSibling;
|
||||||
if (next === null) {
|
if (next === null) {
|
||||||
el.parentNode.appendChild(doc.createTextNode('\u200b'));
|
el.parentNode.appendChild(doc.createTextNode('\u200b'));
|
||||||
@@ -658,8 +750,10 @@ export default {
|
|||||||
/** TinyMCE 会剔除「空」的行内标签;空 mycite 必须在入编辑器前占位,否则合并引用 [1–3] 等整段消失 */
|
/** TinyMCE 会剔除「空」的行内标签;空 mycite 必须在入编辑器前占位,否则合并引用 [1–3] 等整段消失 */
|
||||||
normalizeAutociteHtmlForEditor(html) {
|
normalizeAutociteHtmlForEditor(html) {
|
||||||
if (!html || typeof html !== 'string') return html;
|
if (!html || typeof html !== 'string') return html;
|
||||||
|
// 历史库/表格里可能为 <autocite>,与 mycite 统一,否则 renderAutociteInEditor 扫不到、无效 id 会残留
|
||||||
|
let out = html.replace(/<\/autocite>/gi, '</mycite>').replace(/<autocite\b/gi, '<mycite');
|
||||||
/** 角标显示一律由 renderAutociteInEditor 根据 data-id 生成,禁止保留库内遗留的 [1-4]、[1–4] 等旧文案 */
|
/** 角标显示一律由 renderAutociteInEditor 根据 data-id 生成,禁止保留库内遗留的 [1-4]、[1–4] 等旧文案 */
|
||||||
let out = html.replace(/<mycite([^>]*)>[\s\S]*?<\/mycite>/gi, '<mycite$1>​</mycite>');
|
out = out.replace(/<mycite([^>]*)>[\s\S]*?<\/mycite>/gi, '<mycite$1>​</mycite>');
|
||||||
// 外侧:连续空格 / 合并为单个 ,避免「普通空格 + 」叠成大缝
|
// 外侧:连续空格 / 合并为单个 ,避免「普通空格 + 」叠成大缝
|
||||||
out = out.replace(/(?:\s| | )+(?=<mycite\b)/gi, ' ');
|
out = out.replace(/(?:\s| | )+(?=<mycite\b)/gi, ' ');
|
||||||
out = out.replace(/(?<=<\/mycite>)(?:\s| | )+/gi, ' ');
|
out = out.replace(/(?<=<\/mycite>)(?:\s| | )+/gi, ' ');
|
||||||
@@ -1018,7 +1112,7 @@ export default {
|
|||||||
valid_elements:
|
valid_elements:
|
||||||
this.type == 'table'
|
this.type == 'table'
|
||||||
? '*[*]'
|
? '*[*]'
|
||||||
: `img[src|alt|width|height],strong,em,sub,sup,blue,table,b,i,myfigure,mytable,wmath,mycite[data-id|contenteditable|title|data-cite-missing|style]${this.valid_elements}`, // 允许的标签和属性
|
: `img[src|alt|width|height],strong,em,sub,sup,blue,table,b,i,myfigure,mytable,wmath,mycite[data-id|contenteditable|data-cite-missing|style]${this.valid_elements}`, // 允许的标签和属性(mycite 不使用 title 悬停)
|
||||||
// valid_elements: '*[*]', // 允许所有 HTML 标签
|
// valid_elements: '*[*]', // 允许所有 HTML 标签
|
||||||
noneditable_editable_class: 'MathJax',
|
noneditable_editable_class: 'MathJax',
|
||||||
height: this.height,
|
height: this.height,
|
||||||
@@ -1205,7 +1299,7 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ed.on('click', function (e) {
|
ed.on('click', function (e) {
|
||||||
const autociteEl = e.target.closest('mycite');
|
const autociteEl = e.target.closest('mycite') || e.target.closest('autocite');
|
||||||
if (autociteEl) {
|
if (autociteEl) {
|
||||||
const dataIds = _this.parseAutociteDataIds(autociteEl.getAttribute('data-id'));
|
const dataIds = _this.parseAutociteDataIds(autociteEl.getAttribute('data-id'));
|
||||||
_this._refBookmark = ed.selection.getBookmark(2);
|
_this._refBookmark = ed.selection.getBookmark(2);
|
||||||
|
|||||||
@@ -997,23 +997,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="row-divider"
|
class="row-divider"
|
||||||
v-if="(currentData.type == 0 || currentData.type == 1) && (isEditComment || manuscriptAutociteContext)"
|
v-if="(currentData.type == 0 || currentData.type == 1) && isEditComment"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<template
|
|
||||||
v-if="
|
|
||||||
manuscriptAutociteContext &&
|
|
||||||
currentData &&
|
|
||||||
(currentData.type == 0 || currentData.type == 1 || currentData.type == 2)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="menu-item menu-autocite-ref" @click.stop="menuAction('editRefCite')">
|
|
||||||
<i class="el-icon-edit-outline"></i><span>{{ $t('wordCite.modifyRef') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="menu-item danger" @click.stop="menuAction('removeRefCite')">
|
|
||||||
<i class="el-icon-remove-outline"></i><span>{{ $t('wordCite.removeRefTag') }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div
|
<div
|
||||||
class="menu-item menu-link"
|
class="menu-item menu-link"
|
||||||
v-if="currentData.type == 0 && !['figure', 'table'].includes(currentTag) && !manuscriptAutociteContext"
|
v-if="currentData.type == 0 && !['figure', 'table'].includes(currentTag) && !manuscriptAutociteContext"
|
||||||
@@ -1064,15 +1050,72 @@
|
|||||||
>
|
>
|
||||||
<div class="cite-preview-head">
|
<div class="cite-preview-head">
|
||||||
<span>{{ $t('wordCite.reference') }}</span>
|
<span>{{ $t('wordCite.reference') }}</span>
|
||||||
<i class="el-icon-close" @click="hideCitePreview"></i>
|
<div>
|
||||||
|
<span
|
||||||
|
class="cite-preview-action-btn"
|
||||||
|
:title="$t('wordCite.modifyRef')"
|
||||||
|
@click.stop="menuAction('editRefCite')"
|
||||||
|
>
|
||||||
|
<i class="el-icon-edit-outline"></i>Edit citation
|
||||||
|
</span>
|
||||||
|
<span style="margin-left: 20px"
|
||||||
|
class="cite-preview-action-btn cite-preview-action-danger"
|
||||||
|
:title="$t('wordCite.removeRefTag')"
|
||||||
|
@click.stop="menuAction('removeRefCite')"
|
||||||
|
>
|
||||||
|
<i class="el-icon-remove-outline"></i>Remove citation
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<i class="el-icon-close" @click="hideCitePreview" style="margin-left: 20px"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="cite-preview-body">
|
<div v-if="citePreviewSortedItems.length" class="cite-preview-toolbar">
|
||||||
<div v-for="(it, idx) in citePreviewItems" :key="`${it.id}-${idx}`" class="cite-preview-item">
|
<div class="cite-preview-nums-wrap">
|
||||||
<div class="cite-preview-title">
|
<span
|
||||||
[{{ it.no }}] {{ it.title }}
|
v-for="(it, idx) in citePreviewSortedItems"
|
||||||
|
:key="`chip-${it.id}-${idx}`"
|
||||||
|
:class="['cite-num-chip', { active: idx === citePreviewActiveIndex }]"
|
||||||
|
:title="$t('wordCite.locateInRefHint')"
|
||||||
|
@click.stop="onCitePreviewChipClick(idx)"
|
||||||
|
>{{ it.no }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="citePreviewSortedItems.length > 1" class="cite-preview-pager">
|
||||||
|
<span class="cite-preview-pager-text">{{ citePreviewActiveIndex + 1 }} / {{ citePreviewSortedItems.length }}</span>
|
||||||
|
<i
|
||||||
|
class="el-icon-arrow-left cite-preview-pager-btn"
|
||||||
|
:class="{ disabled: citePreviewActiveIndex <= 0 }"
|
||||||
|
@click.stop="citePreviewStep(-1)"
|
||||||
|
></i>
|
||||||
|
<i
|
||||||
|
class="el-icon-arrow-right cite-preview-pager-btn"
|
||||||
|
:class="{ disabled: citePreviewActiveIndex >= citePreviewSortedItems.length - 1 }"
|
||||||
|
@click.stop="citePreviewStep(1)"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ref="citePreviewBody" class="cite-preview-body">
|
||||||
|
<div
|
||||||
|
v-for="(it, idx) in citePreviewSortedItems"
|
||||||
|
: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-num">{{ it.no }}.</span>
|
||||||
|
<span v-if="it.author" class="cite-preview-auth">{{ it.author }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="cite-preview-meta">
|
<div v-if="it.title && it.title !== '-'" class="cite-preview-line cite-preview-article-title">
|
||||||
{{ it.meta }}
|
{{ it.title }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
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>
|
</div>
|
||||||
<a v-if="it.doi" class="cite-preview-doi" :href="it.doi" target="_blank">{{ it.doi }}</a>
|
<a v-if="it.doi" class="cite-preview-doi" :href="it.doi" target="_blank">{{ it.doi }}</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -1235,10 +1278,15 @@ export default {
|
|||||||
manuscriptAutociteContext: null,
|
manuscriptAutociteContext: null,
|
||||||
citePreviewVisible: false,
|
citePreviewVisible: false,
|
||||||
citePreviewItems: [],
|
citePreviewItems: [],
|
||||||
|
/** 合并引用多文献时,顶部序号条当前高亮项(与分页箭头同步) */
|
||||||
|
citePreviewActiveIndex: 0,
|
||||||
citePreviewStyle: {
|
citePreviewStyle: {
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
left: '0px',
|
left: '0px',
|
||||||
top: '0px',
|
top: '0px',
|
||||||
|
bottom: 'auto',
|
||||||
|
width: '',
|
||||||
|
maxHeight: '',
|
||||||
zIndex: 10001
|
zIndex: 10001
|
||||||
},
|
},
|
||||||
/** 正文 DOM 中 mycite 首次出现顺序(唯一 p_refer_id),驱动 citeMap 与参考文献列表排序 */
|
/** 正文 DOM 中 mycite 首次出现顺序(唯一 p_refer_id),驱动 citeMap 与参考文献列表排序 */
|
||||||
@@ -1408,6 +1456,24 @@ export default {
|
|||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
},
|
},
|
||||||
|
/** 弹窗内文献按全局序号排序,与稿面角标顺序一致 */
|
||||||
|
citePreviewSortedItems() {
|
||||||
|
const items = this.citePreviewItems || [];
|
||||||
|
return items.slice().sort((a, b) => {
|
||||||
|
const na = Number(a.no);
|
||||||
|
const nb = Number(b.no);
|
||||||
|
if (!Number.isNaN(na) && !Number.isNaN(nb)) return na - nb;
|
||||||
|
if (!Number.isNaN(na)) return -1;
|
||||||
|
if (!Number.isNaN(nb)) return 1;
|
||||||
|
return String(a.no || '').localeCompare(String(b.no || ''));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
citePreviewActiveItem() {
|
||||||
|
const arr = this.citePreviewSortedItems;
|
||||||
|
if (!arr.length) return null;
|
||||||
|
const i = Math.max(0, Math.min(this.citePreviewActiveIndex, arr.length - 1));
|
||||||
|
return arr[i];
|
||||||
|
},
|
||||||
sortedProofreadingList() {
|
sortedProofreadingList() {
|
||||||
const order = [2, 1, 3];
|
const order = [2, 1, 3];
|
||||||
const rank = { 2: 0, 1: 1, 3: 2 };
|
const rank = { 2: 0, 1: 1, 3: 2 };
|
||||||
@@ -1599,22 +1665,6 @@ renderCiteLabels(html) {
|
|||||||
const label =
|
const label =
|
||||||
numsForLabel.length > 0 ? this.formatCiteNumbers(numsForLabel) : '';
|
numsForLabel.length > 0 ? this.formatCiteNumbers(numsForLabel) : '';
|
||||||
|
|
||||||
const lines = parts
|
|
||||||
.filter((p) => p.ref)
|
|
||||||
.map((p) => {
|
|
||||||
const ref = p.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 numLabel = p.num;
|
|
||||||
if (numLabel) {
|
|
||||||
return `[${numLabel}] ${content}\nDOI: ${doi}`;
|
|
||||||
}
|
|
||||||
return `${content}\nDOI: ${doi}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const escAttr = (s) =>
|
const escAttr = (s) =>
|
||||||
String(s || '')
|
String(s || '')
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -1624,8 +1674,7 @@ renderCiteLabels(html) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const dataIdAttr = escAttr(sortedIds.join(','));
|
const dataIdAttr = escAttr(sortedIds.join(','));
|
||||||
const titleAttr = escAttr(lines.join('\n'));
|
return `<mycite data-id="${dataIdAttr}">[${label}]</mycite>`;
|
||||||
return `<mycite data-id="${dataIdAttr}" title="${titleAttr}">[${label}]</mycite>`;
|
|
||||||
})
|
})
|
||||||
/** 库内遗留:空 data-id 或仅零宽字符的 mycite 整段去掉 */
|
/** 库内遗留:空 data-id 或仅零宽字符的 mycite 整段去掉 */
|
||||||
.replace(/<mycite[^>]*data-id=""[^>]*>[\s\S]*?<\/mycite>/gi, '');
|
.replace(/<mycite[^>]*data-id=""[^>]*>[\s\S]*?<\/mycite>/gi, '');
|
||||||
@@ -1765,6 +1814,7 @@ renderCiteLabels(html) {
|
|||||||
if (!hasSelection && !isClickOnParagraph) {
|
if (!hasSelection && !isClickOnParagraph) {
|
||||||
this.bubbleVisible = false;
|
this.bubbleVisible = false;
|
||||||
this.isMenuVisible = false;
|
this.isMenuVisible = false;
|
||||||
|
this.citePreviewVisible = false;
|
||||||
|
|
||||||
this.currentData = {};
|
this.currentData = {};
|
||||||
this.currentId = '';
|
this.currentId = '';
|
||||||
@@ -2222,8 +2272,67 @@ renderCiteLabels(html) {
|
|||||||
hideCitePreview() {
|
hideCitePreview() {
|
||||||
this.citePreviewVisible = false;
|
this.citePreviewVisible = false;
|
||||||
this.citePreviewItems = [];
|
this.citePreviewItems = [];
|
||||||
|
this.citePreviewActiveIndex = 0;
|
||||||
|
this._citePreviewMyciteEl = null;
|
||||||
|
this.citePreviewStyle = {
|
||||||
|
position: 'fixed',
|
||||||
|
left: '0px',
|
||||||
|
top: '0px',
|
||||||
|
bottom: 'auto',
|
||||||
|
width: '',
|
||||||
|
maxHeight: '',
|
||||||
|
zIndex: 10001
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/** 按视口与角标位置计算弹窗 maxHeight、贴下或贴上,避免靠页底时裁切 */
|
||||||
|
placeCitePreviewNear(triggerEl) {
|
||||||
|
if (!triggerEl || typeof triggerEl.getBoundingClientRect !== 'function') return;
|
||||||
|
const rect = triggerEl.getBoundingClientRect();
|
||||||
|
const pad = 12;
|
||||||
|
const gap = 8;
|
||||||
|
const popW = 640;
|
||||||
|
const widthPx = Math.min(popW, Math.max(280, window.innerWidth - pad * 2));
|
||||||
|
/** 略限制首选高度,避免弹窗占满屏;列表区靠更紧凑的样式展示更多条 */
|
||||||
|
const maxPref = Math.min(440, window.innerHeight - pad * 2);
|
||||||
|
const minReadable = 120;
|
||||||
|
|
||||||
|
const spaceBelow = window.innerHeight - rect.bottom - pad;
|
||||||
|
const spaceAbove = rect.top - pad;
|
||||||
|
|
||||||
|
let useAbove = false;
|
||||||
|
let maxH;
|
||||||
|
if (spaceBelow >= minReadable) {
|
||||||
|
maxH = Math.min(maxPref, spaceBelow - gap);
|
||||||
|
} else if (spaceAbove >= minReadable) {
|
||||||
|
useAbove = true;
|
||||||
|
maxH = Math.min(maxPref, spaceAbove - gap);
|
||||||
|
} else {
|
||||||
|
useAbove = spaceAbove > spaceBelow;
|
||||||
|
maxH = Math.min(maxPref, (useAbove ? spaceAbove : spaceBelow) - gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
maxH = Math.floor(Math.max(72, maxH));
|
||||||
|
const left = Math.max(pad, Math.min(rect.left, window.innerWidth - widthPx - pad));
|
||||||
|
|
||||||
|
const next = {
|
||||||
|
position: 'fixed',
|
||||||
|
left: `${left}px`,
|
||||||
|
zIndex: 10001,
|
||||||
|
width: `${widthPx}px`,
|
||||||
|
maxHeight: `${maxH}px`
|
||||||
|
};
|
||||||
|
if (useAbove) {
|
||||||
|
next.bottom = `${window.innerHeight - rect.top + gap}px`;
|
||||||
|
next.top = 'auto';
|
||||||
|
} else {
|
||||||
|
next.top = `${rect.bottom + gap}px`;
|
||||||
|
next.bottom = 'auto';
|
||||||
|
}
|
||||||
|
this.citePreviewStyle = next;
|
||||||
},
|
},
|
||||||
showCitePreviewByAutocite(el, dataId) {
|
showCitePreviewByAutocite(el, dataId) {
|
||||||
|
this._citePreviewMyciteEl = el || null;
|
||||||
|
this.citePreviewActiveIndex = 0;
|
||||||
const ids = String(dataId || '')
|
const ids = String(dataId || '')
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((s) => s.trim())
|
.map((s) => s.trim())
|
||||||
@@ -2243,26 +2352,77 @@ renderCiteLabels(html) {
|
|||||||
const ref = refMap[String(id)];
|
const ref = refMap[String(id)];
|
||||||
if (!ref) return null;
|
if (!ref) return null;
|
||||||
const no = citeMap[String(id)] || '?';
|
const no = citeMap[String(id)] || '?';
|
||||||
// 标题优先走 Crossref 解析出的 title;兜底 refer_frag
|
const author = (ref.author || '').toString().trim();
|
||||||
const title = (ref.title || ref.refer_frag || '').toString().trim() || '-';
|
const title =
|
||||||
const meta = [ref.author, ref.joura, ref.dateno].filter(Boolean).join(' ').trim();
|
(ref.title || '').toString().trim() ||
|
||||||
|
(ref.refer_frag || '').toString().trim() ||
|
||||||
|
'-';
|
||||||
|
const joura = (ref.joura || '').toString().trim();
|
||||||
|
const dateno = (ref.dateno || '').toString().trim();
|
||||||
const doi = (ref.doilink || ref.doi || ref.isbn || '').toString().trim();
|
const doi = (ref.doilink || ref.doi || ref.isbn || '').toString().trim();
|
||||||
return { id: String(id), no, title, meta, doi };
|
return { id: String(id), no, author, title, joura, dateno, doi };
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
this.hideCitePreview();
|
this.hideCitePreview();
|
||||||
return;
|
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.citePreviewItems = items;
|
||||||
this.citePreviewVisible = true;
|
this.citePreviewVisible = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.placeCitePreviewNear(el);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
scrollCitePreviewToBody() {
|
||||||
|
const el = this._citePreviewMyciteEl;
|
||||||
|
const sc = this.$refs.scrollDiv;
|
||||||
|
if (!el || !sc) return;
|
||||||
|
const top = this.topInScroll(el, sc) - 80;
|
||||||
|
sc.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });
|
||||||
|
if (el._citeFlashTimer) {
|
||||||
|
clearTimeout(el._citeFlashTimer);
|
||||||
|
el._citeFlashTimer = null;
|
||||||
|
}
|
||||||
|
el.classList.remove('cite-mycite-flash');
|
||||||
|
void el.offsetWidth;
|
||||||
|
el.classList.add('cite-mycite-flash');
|
||||||
|
el._citeFlashTimer = setTimeout(() => {
|
||||||
|
el.classList.remove('cite-mycite-flash');
|
||||||
|
el._citeFlashTimer = null;
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
scrollCitePreviewToRefRow(pReferId) {
|
||||||
|
if (pReferId == null || pReferId === '') return;
|
||||||
|
const target = document.querySelector(`[data-id="ref-${pReferId}"]`);
|
||||||
|
if (!target) return;
|
||||||
|
const sc = this.$refs.scrollDiv;
|
||||||
|
if (!sc) {
|
||||||
|
this.scrollContainerTo(target, 80);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const top = this.topInScroll(target, sc) - 80;
|
||||||
|
sc.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });
|
||||||
|
},
|
||||||
|
onCitePreviewChipClick(idx) {
|
||||||
|
this.citePreviewActiveIndex = idx;
|
||||||
|
this.$nextTick(() => this.scrollCitePreviewItemIntoView());
|
||||||
|
},
|
||||||
|
citePreviewStep(delta) {
|
||||||
|
const list = this.citePreviewSortedItems;
|
||||||
|
if (!list.length) return;
|
||||||
|
let i = this.citePreviewActiveIndex + delta;
|
||||||
|
i = Math.max(0, Math.min(list.length - 1, i));
|
||||||
|
if (i === this.citePreviewActiveIndex) return;
|
||||||
|
this.citePreviewActiveIndex = i;
|
||||||
|
this.$nextTick(() => this.scrollCitePreviewItemIntoView());
|
||||||
|
},
|
||||||
|
scrollCitePreviewItemIntoView() {
|
||||||
|
const body = this.$refs.citePreviewBody;
|
||||||
|
if (!body) return;
|
||||||
|
const row = body.querySelector('.cite-preview-item.is-active');
|
||||||
|
if (row && typeof row.scrollIntoView === 'function') {
|
||||||
|
row.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
escapeHtmlAttr(val) {
|
escapeHtmlAttr(val) {
|
||||||
return String(val == null ? '' : val).replace(/&/g, '&').replace(/"/g, '"');
|
return String(val == null ? '' : val).replace(/&/g, '&').replace(/"/g, '"');
|
||||||
@@ -3628,6 +3788,11 @@ renderCiteLabels(html) {
|
|||||||
if (Math.abs(newScrollTop - (this.lastScrollTop || 0)) < 10) return;
|
if (Math.abs(newScrollTop - (this.lastScrollTop || 0)) < 10) return;
|
||||||
this.lastScrollTop = newScrollTop;
|
this.lastScrollTop = newScrollTop;
|
||||||
|
|
||||||
|
// 与段落工具栏一致:主稿滚动时关闭 Reference 预览浮层
|
||||||
|
if (this.citePreviewVisible) {
|
||||||
|
this.hideCitePreview();
|
||||||
|
}
|
||||||
|
|
||||||
this.clearHighlight();
|
this.clearHighlight();
|
||||||
|
|
||||||
if (!this.isPreview) {
|
if (!this.isPreview) {
|
||||||
@@ -5204,56 +5369,265 @@ font-weight: bold !important;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cite-preview-pop {
|
.cite-preview-pop {
|
||||||
width: 440px;
|
width: 640px;
|
||||||
max-height: 220px;
|
max-width: calc(100vw - 24px);
|
||||||
|
/* max-height 由 placeCitePreviewNear 按视口与角标位置内联计算,避免贴底裁切 */
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #dcdfe6;
|
border: 1px solid #dcdfe6;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
.cite-preview-head {
|
.cite-preview-head {
|
||||||
height: 32px;
|
flex-shrink: 0;
|
||||||
padding: 0 10px;
|
height: 28px;
|
||||||
|
padding: 0 8px;
|
||||||
border-bottom: 1px solid #ebeef5;
|
border-bottom: 1px solid #ebeef5;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
color: #606266;
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
background: #f8f9fb;
|
background: #f8f9fb;
|
||||||
}
|
}
|
||||||
.cite-preview-head i {
|
.cite-preview-head i {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.cite-preview-toolbar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
background: #fafbfc;
|
||||||
|
}
|
||||||
|
.cite-preview-nums-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px 4px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
max-height: 52px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1px 0;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
.cite-preview-nums-wrap::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
.cite-preview-nums-wrap::-webkit-scrollbar-thumb {
|
||||||
|
background: #c0c4cc;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.cite-num-chip {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 14px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #303133;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.cite-num-chip:hover {
|
||||||
|
border-color: #409eff;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
.cite-num-chip.active {
|
||||||
|
background: #ecf5ff;
|
||||||
|
border-color: #409eff;
|
||||||
|
color: #409eff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.cite-preview-ref-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-left: 6px;
|
||||||
|
margin-left: 2px;
|
||||||
|
border-left: 1px solid #e4e7ed;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
.cite-preview-action-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: auto;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 11px;background: #ecf5ff;
|
||||||
|
color: #006699;
|
||||||
|
transition: background 0.15s ease, color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cite-preview-action-btn.cite-preview-action-danger {
|
||||||
|
background: #fef0f0;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.cite-preview-pager {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
.cite-preview-pager-text {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #606266;
|
||||||
|
min-width: 28px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.cite-preview-pager-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #303133;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
.cite-preview-pager-btn.disabled {
|
||||||
|
color: #c0c4cc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.cite-preview-current {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
}
|
||||||
|
.cite-preview-current:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
.cite-preview-current-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.cite-preview-current-no {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #303133;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.cite-preview-locate-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #409eff;
|
||||||
}
|
}
|
||||||
.cite-preview-body {
|
.cite-preview-body {
|
||||||
max-height: 188px;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 8px 10px;
|
padding: 4px 6px 6px;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
.cite-preview-body::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.cite-preview-body::-webkit-scrollbar-thumb {
|
||||||
|
background: #c0c4cc;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
.cite-preview-item {
|
.cite-preview-item {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
line-height: 18px;
|
line-height: 1.35;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 2px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
.cite-preview-item.is-active {
|
||||||
|
background: #e8f4ff;
|
||||||
|
border-color: #a0cfff;
|
||||||
}
|
}
|
||||||
.cite-preview-item:last-child {
|
.cite-preview-item:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.cite-preview-title {
|
/* 与稿末参考文献列表一致:序号(蓝) + 作者 → 题名 → 斜体期刊 + 年卷期 → DOI 独行 */
|
||||||
font-weight: 600;
|
.cite-preview-line {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
.cite-preview-meta {
|
.cite-preview-line:last-of-type {
|
||||||
color: #606266;
|
margin-bottom: 0;
|
||||||
word-break: break-word;
|
}
|
||||||
|
.cite-preview-num-auth {
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.cite-preview-num {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #006699;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.cite-preview-auth {
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.cite-preview-article-title {
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.cite-preview-journal-row {
|
||||||
|
color: #303133;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.cite-preview-journal {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.cite-preview-dateno {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
.cite-preview-doi {
|
.cite-preview-doi {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.3;
|
||||||
color: #006699;
|
color: #006699;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep mycite.cite-mycite-flash {
|
||||||
|
outline: 2px solid #409eff;
|
||||||
|
outline-offset: 2px;
|
||||||
|
animation: citeMyciteFlash 0.95s ease;
|
||||||
|
}
|
||||||
|
@keyframes citeMyciteFlash {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
outline-color: #409eff;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
outline-color: #67c23a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes blueGlow {
|
@keyframes blueGlow {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|||||||
@@ -96,11 +96,11 @@
|
|||||||
|
|
||||||
<div class="body-editor-container">
|
<div class="body-editor-container">
|
||||||
<div class="subject-label" style="margin-bottom: 10px;">{{ $t('mailboxMouldDetail.emailBody') }}:</div>
|
<div class="subject-label" style="margin-bottom: 10px;">{{ $t('mailboxMouldDetail.emailBody') }}:</div>
|
||||||
<TmrEmailEditor
|
<!-- <TmrEmailEditor
|
||||||
v-model="form.body"
|
v-model="form.body"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
/>
|
/> -->
|
||||||
<!-- <CkeditorMail v-model="form.body" /> -->
|
<CkeditorMail v-model="form.body" />
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user