This commit is contained in:
2026-04-08 17:25:19 +08:00
parent 4fc78e1fe7
commit c4b86be0d5
5 changed files with 604 additions and 206 deletions

View File

@@ -1169,7 +1169,7 @@ colTitle: 'Template title',
remove: 'Remove', remove: 'Remove',
selected: 'Selected', selected: 'Selected',
modifyRef: 'Edit citation', modifyRef: 'Edit citation',
removeRefTag: 'Reference remove', removeRefTag: 'Remove citation',
citeUpdateFail: 'Could not update the citation in the text. Try again or use Edit.', citeUpdateFail: 'Could not update the citation in the text. Try again or use Edit.',
matchBracketRefs: 'Auto-link References', matchBracketRefs: 'Auto-link References',
matchBracketRefsDone: 'Converted {n} bracket citation(s) to autocite.', matchBracketRefsDone: 'Converted {n} bracket citation(s) to autocite.',

View File

@@ -1154,7 +1154,7 @@ const zh = {
remove: '移除', remove: '移除',
selected: '已选择', selected: '已选择',
modifyRef: '修改引用', modifyRef: '修改引用',
removeRefTag: '移除参考文献', removeRefTag: '移除引用',
citeUpdateFail: '未能更新正文中的引用标签,请重试或进入编辑修改', citeUpdateFail: '未能更新正文中的引用标签,请重试或进入编辑修改',
matchBracketRefs: '自动链接参考文献', matchBracketRefs: '自动链接参考文献',
matchBracketRefsDone: '已转换 {n} 处 [n] 为可点击角标', matchBracketRefsDone: '已转换 {n} 处 [n] 为可点击角标',

View File

@@ -454,7 +454,6 @@
ref="addContent" ref="addContent"
style="margin-left: -115px" style="margin-left: -115px"
></common-content> ></common-content>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -477,19 +476,41 @@
:close-on-click-modal="false" :close-on-click-modal="false"
@closed="onRefSelectorDialogClosed" @closed="onRefSelectorDialogClosed"
> >
<div v-if="refSelectedRows.length > 0" style="margin-bottom: 10px; font-size: 13px; color: #606266;">
{{ $t('wordCite.selected') }}:
<b v-if="refPreviewLabel" style="color: #0082aa">[{{ refPreviewLabel }}]</b>
</div>
<div class="duplicateRefBox" v-if="chanFerFormRepeatList.length > 0"> <div class="duplicateRefBox" v-if="chanFerFormRepeatList.length > 0">
<el-tooltip class="item" effect="dark" content="Duplicate references" placement="top"> <el-tooltip class="item" effect="dark" content="Duplicate references" placement="top">
<img src="../../assets/img/repeat.png" alt="" style="width: 22px; height: 22px" /> <img src="../../assets/img/repeat.png" alt="" style="width: 22px; height: 22px" />
</el-tooltip> </el-tooltip>
<span @click="handleContainerClick" style="margin-left: 5px" v-html="getRepeatRefHtml()"> </span> <span @click="handleContainerClick" style="margin-left: 5px" v-html="getRepeatRefHtml()"> </span>
</div> </div>
<el-table :row-style="tableRowStyle" <div>
<el-input
v-model="refSelectorQuickPick"
size="small"
clearable
:placeholder="$t('wordCite.quickPickPlaceholder')"
style="width: calc(100% - 340px);"
/>
<el-button size="small" type="primary" plain @click="applyRefSelectorQuickPick" style="margin-left: 20px;">
{{ $t('wordCite.quickPickApply') }}
</el-button>
<el-button
style="float: right; margin-bottom: 30px"
icon="el-icon-plus"
@mousedown.stop
@click.stop="addLine('Add')"
type="success"
size="small"
plain
>Cite new reference</el-button
>
</div>
<el-table
:row-style="tableRowStyle"
ref="refSelectorTable" ref="refSelectorTable"
:data="refSelectorTableData" :data="refSelectorTableData"
@selection-change="handleRefSelectionChange" @selection-change="handleRefSelectionChange"
@@ -501,114 +522,205 @@
> >
<el-table-column type="selection" width="45" :reserve-selection="true"></el-table-column> <el-table-column type="selection" width="45" :reserve-selection="true"></el-table-column>
<el-table-column type="index" label="No." width="60" align="center"> <el-table-column type="index" label="No." width="60" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<i v-if="scope.row.is_change == 1" class="itemChanged"></i> <i v-if="scope.row.is_change == 1" class="itemChanged"></i>
<!-- <el-tooltip <!-- <el-tooltip
class="Duplicate-item" class="Duplicate-item"
effect="dark" effect="dark"
:content="getParsingInfo(scope.row, scope.$index)" :content="getParsingInfo(scope.row, scope.$index)"
placement="top" placement="top"
> --> > -->
<img v-if="scope.row.is_repeat == 1" src="../../assets/img/repeat.png" alt="" style="width: 22px; height: 22px" /> <img v-if="scope.row.is_repeat == 1" src="../../assets/img/repeat.png" alt="" style="width: 22px; height: 22px" />
<!-- </el-tooltip> --> <!-- </el-tooltip> -->
<span>{{ scope.$index + 1 }}</span> <span>{{ scope.$index + 1 }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="state" width="55" align="center"> <el-table-column label="State" width="55" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<span <span
class="status ok" class="status ok"
:class="scope.row.refer_type == 'journal' ? getJournalDateno(scope.row.dateno, 'status') : ''" :class="scope.row.refer_type == 'journal' ? getJournalDateno(scope.row.dateno, 'status') : ''"
v-if=" v-if="
( ((scope.row.refer_type == 'journal' && scope.row.doilink != '' && scope.row.cs == 1) ||
(scope.row.refer_type == 'journal' && scope.row.doilink != '' && scope.row.cs == 1) || (scope.row.refer_type == 'book' && scope.row.isbn != '' && scope.row.cs == 1)) &&
(scope.row.refer_type == 'book' && scope.row.isbn != '' && scope.row.cs == 1) scope.row.retract == 0
) && scope.row.retract == 0 "
" >
> <i class="el-icon-circle-check"></i>
<i class="el-icon-circle-check"></i> </span>
</span> <span class="status warn" v-else>
<span class="status warn" v-else> <i class="el-icon-warning-outline"></i>
<i class="el-icon-warning-outline"></i> </span>
</span> </template>
</template> </el-table-column>
</el-table-column>
<el-table-column :label="$t('wordCite.reference')"> <el-table-column :label="$t('wordCite.reference')">
<template slot-scope="scope"> <template slot-scope="scope">
<!-- journal 形式 --> <!-- journal 形式 -->
<div style="text-align: left" v-if="scope.row.refer_type == 'journal'" class="reference-item"> <div style="text-align: left" v-if="scope.row.refer_type == 'journal'" class="reference-item">
<p> <p>
{{ scope.row.author }}&nbsp;<span v-html="formatTitle(scope.row.title)"></span>. &nbsp;<em>{{ scope.row.joura }}</em {{ scope.row.author }}&nbsp;<span v-html="formatTitle(scope.row.title)"></span>. &nbsp;<em>{{
>.&nbsp;<span :class="getJournalDateno(scope.row.dateno, 'title')">{{ scope.row.dateno }}</span scope.row.joura
>.<br /> }}</em
</p> >.&nbsp;<span :class="getJournalDateno(scope.row.dateno, 'title')">{{ scope.row.dateno }}</span
<a class="doiLink" :href="scope.row.doilink" target="_blank">{{ scope.row.doilink }}</a> >.<br />
</div> </p>
<!-- book 形式 --> <a class="doiLink" :href="scope.row.doilink" target="_blank">{{ scope.row.doilink }}</a>
<div style="text-align: left" v-if="scope.row.refer_type == 'book'" class="reference-item"> </div>
<p>{{ scope.row.author }}&nbsp;<span v-html="formatTitle(scope.row.title)"></span>.&nbsp;{{ scope.row.dateno }}.&nbsp;<br /></p> <!-- book 形式 -->
<a class="doiLink" :href="scope.row.isbn" target="_blank">{{ scope.row.isbn }}</a> <div style="text-align: left" v-if="scope.row.refer_type == 'book'" class="reference-item">
</div> <p>
<!-- other 形式 --> {{ scope.row.author }}&nbsp;<span v-html="formatTitle(scope.row.title)"></span>.&nbsp;{{
<p class="wrongLine reference-item" style="text-align: left" v-if="scope.row.refer_type == 'other'"> scope.row.dateno
<span v-html="formatTitle(scope.row.refer_frag)"></span> }}.&nbsp;<br />
</p>
</p> <a class="doiLink" :href="scope.row.isbn" target="_blank">{{ scope.row.isbn }}</a>
</template> </div>
<!-- other 形式 -->
<p class="wrongLine reference-item" style="text-align: left" v-if="scope.row.refer_type == 'other'">
<span v-html="formatTitle(scope.row.refer_frag)"></span>
</p>
</template>
</el-table-column>
<el-table-column align="center" :width="'200'">
<div slot-scope="scope">
<div class="operation" style="">
<el-button
style="margin-left: 10px"
@mousedown.stop
@click.stop="change(scope.row, 'Edit')"
plain
type="primary"
size="mini"
icon="el-icon-edit"
>edit</el-button
>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
style=""
plain
@mousedown.stop
@click.stop="deleteLine(scope.row)"
>delete</el-button
>
</div>
</div>
</el-table-column> </el-table-column>
<el-table-column align="center" :width="'290'">
<div slot-scope="scope">
<div class="operation" style="">
<el-button
style="margin-left: 10px"
@click="change(scope.row, 'Edit')"
plain
type="primary"
size="mini"
icon="el-icon-edit"
>edit</el-button
>
<el-tooltip
popper-class="tps"
class="item"
effect="light"
content="Add one under this line"
placement="top"
>
<el-button @click="addLine(scope.row, 'Add')" type="success" size="mini" plain>Add</el-button>
</el-tooltip>
<el-button type="danger" icon="el-icon-delete" size="mini" style="" plain @click="deleteLine(scope.row)"
>delete</el-button
>
<img
v-if="role == 'editor' && scope.row.is_ai_check == 1"
src="@/assets/img/ai.png"
class="beautiful-gradient"
style="width: 22px; height: 22px; position: absolute; right: 0px; top: 2px"
/>
</div>
</div>
</el-table-column>
</el-table> </el-table>
<div style="display: flex; gap: 10px; align-items: center; margin: 10px 0 10px;">
<el-input <div style="display: flex; gap: 10px; align-items: center; margin: 10px 0;">
v-model="refSelectorQuickPick"
size="small" <div v-if="refSelectedRows.length > 0" style="margin-bottom: 10px; font-size: 13px; color: #606266">
clearable {{ $t('wordCite.selected') }}:
:placeholder="$t('wordCite.quickPickPlaceholder')" <b v-if="refPreviewLabel" style="color: #0082aa">[{{ refPreviewLabel }}]</b>
/>
<el-button size="small" type="primary" plain @click="applyRefSelectorQuickPick">
{{ $t('wordCite.quickPickApply') }}
</el-button>
</div> </div>
<div style="flex: 1;"></div>
<el-button size="small" @click="refSelectorVisible = false">
{{ $t('wordCite.cancel') }}
</el-button>
<el-button
size="small"
type="primary"
:disabled="refSelectedRows.length === 0"
@click="handleConfirmRefCite"
>
{{ $t('wordCite.confirm') }}
</el-button>
</div>
</el-dialog>
<!-- 引用:编辑 / 新增 / 删除(复用 editPublicRefRdit.vue 逻辑) -->
<el-dialog
:close-on-click-modal="false"
destroy-on-close
:append-to-body="true"
v-loading="addLoading"
:title="dialogTitle + ' References'"
:visible.sync="editboxVisible"
width="1200px"
@close="cancelSave"
>
<p class="yinyongPre c888" style="margin-bottom: 20px; color: #888; line-height: 24px">
You may add or modify the references. The system will automatically identify and retrieve the reference information. Please
note that if the reference type is set to “Other”, the “Parse” button will not be available.
</p>
<el-form :model="refenceForm" :rules="refenceFormrules" ref="refenceForm" label-width="150px" class="editForm mt10">
<el-form-item label="Source:">
<el-select v-model="SourceType" placeholder="please pick">
<el-option v-for="item in sourceOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="Content:" required prop="content">
<div class="automatic-parsing-box">
<el-input
style="border: none"
class="automaticParsing"
type="textarea"
rows="5"
v-model="refenceForm.content"
placeholder=""
></el-input>
<span v-if="isShowParsing" style="color: #409eff"
>We have detected updates to the reference content. You need to click the "Automatic parsing" button to
recognize them.</span
>
</div>
</el-form-item>
<div v-if="!isShowParsing && isShowParsingData && SourceType != 'other'">
<el-form-item style="margin: 0 0 30px">
<div class="line" style="border: 1px dashed #dcdfe6"></div>
</el-form-item>
<div v-show="SourceType == 'journal'">
<el-form-item label="Doi:" prop="doi">
<el-input v-model="refenceForm.doi"></el-input>
</el-form-item>
</div>
<div v-show="SourceType != 'other'">
<el-form-item label="Author(s):" required prop="author">
<el-input v-model="refenceForm.author"></el-input>
</el-form-item>
<el-form-item :label="SourceType == 'journal' ? 'Title:' : 'Book'" required prop="title">
<el-input v-model="refenceForm.title"></el-input>
</el-form-item>
<el-form-item label="Publication Details:" required prop="dateno">
<el-input v-model="refenceForm.dateno"></el-input>
</el-form-item>
</div>
<div v-show="SourceType == 'journal'">
<el-form-item label="Journal:" required prop="joura">
<el-input v-model="refenceForm.joura"></el-input>
</el-form-item>
<el-form-item label="DOI/URL:" required prop="doilink">
<el-input v-model="refenceForm.doilink"></el-input>
</el-form-item>
</div>
<div v-show="SourceType == 'book'">
<el-form-item label="ISBN:" required prop="isbn">
<el-input v-model="refenceForm.isbn"></el-input>
</el-form-item>
</div>
</div>
</el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="refSelectorVisible = false">{{ $t('wordCite.cancel') }}</el-button> <el-button @click="cancelSave">Cancel</el-button>
<el-button
<el-button type="primary" :disabled="refSelectedRows.length === 0" @click="handleConfirmRefCite">{{ $t('wordCite.confirm') }}</el-button> v-if="isShowParsing"
type="primary"
style="background-color: #409eff !important; border-color: #409eff !important"
@click="getParsingData()"
>Automatic parsing</el-button
>
<el-button v-else type="primary" @click="dialogTitle == 'Edit' ? saveChange() : saveAdd()">Save</el-button>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>
@@ -765,7 +877,46 @@ export default {
/** 打开选择器时若带 currentRefIds编辑已有引用排序用「传值计算的顺序」关闭后清空 */ /** 打开选择器时若带 currentRefIds编辑已有引用排序用「传值计算的顺序」关闭后清空 */
refSelectorContextIds: [], refSelectorContextIds: [],
/** 选择参考文献弹窗:按 # 序号快速勾选,如 [5, 6, 10-15] */ /** 选择参考文献弹窗:按 # 序号快速勾选,如 [5, 6, 10-15] */
refSelectorQuickPick: '' refSelectorQuickPick: '',
/** 引用编辑弹窗(复用 editPublicRefRdit.vue 的新增/编辑/删除逻辑) */
editboxVisible: false,
dialogTitle: 'Edit',
SourceType: 'journal',
sourceOptions: [
{ label: 'Journal', value: 'journal' },
{ label: 'Book', value: 'book' },
{ label: 'Other', value: 'other' }
],
refenceForm: {
doi: '',
p_article_id: null,
p_refer_id: null,
pre_p_refer_id: null,
refer_type: '',
author: '',
title: '',
joura: '',
dateno: '',
doilink: '',
isbn: '',
content: ''
},
old_refence_content: '',
old_refence_type: '',
isShowParsing: false,
isShowParsingData: false,
addLoading: false,
refenceFormrules: {
doi: [{ required: true, message: 'The Doi cannot be empty', trigger: 'blur' }],
author: [{ required: true, message: 'The Author(s) cannot be empty', trigger: 'blur' }],
title: [{ required: true, message: 'The Title cannot be empty', trigger: 'blur' }],
joura: [{ required: true, message: 'The Journal cannot be empty', trigger: 'blur' }],
doilink: [{ required: true, message: 'The DOI/URL cannot be empty', trigger: 'blur' }],
dateno: [{ required: true, message: 'The Publication Details cannot be empty', trigger: 'blur' }],
isbn: [{ required: true, message: 'The ISBN cannot be empty', trigger: 'blur' }],
content: [{ required: true, message: 'The Content cannot be empty', trigger: 'blur' }]
}
}; };
}, },
components: { components: {
@@ -900,7 +1051,7 @@ export default {
}, },
threeVisible(val) { threeVisible(val) {
if (!val) this.tableModalDraftHtml = ''; if (!val) this.tableModalDraftHtml = '';
}, }
// 监听计算属性 // 监听计算属性
// combinedValue(newVal, oldVal) { // combinedValue(newVal, oldVal) {
// console.log('value1 或 value2 发生变化'); // console.log('value1 或 value2 发生变化');
@@ -960,16 +1111,237 @@ export default {
}, },
methods: { methods: {
/** 参考文献:刷新(供编辑/新增/删除后更新选择器列表) */
changeRefer() {
return this.fetchReferList();
},
cancelSave() {
this.editboxVisible = false;
this.isShowParsing = false;
this.isShowParsingData = false;
this.old_refence_content = '';
this.old_refence_type = '';
this.$nextTick(() => {
if (this.$refs.refenceForm) {
try {
this.$refs.refenceForm.clearValidate();
} catch (e) {
/* ignore */
}
}
});
},
getParsingData() {
if (!this.refenceForm || !this.refenceForm.content) {
this.$message.error('The Content cannot be empty');
return;
}
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.$api
.post('api/References/dealContent', {
content: this.refenceForm.content
})
.then((res) => {
loading.close();
this.isShowParsing = false;
this.isShowParsingData = true;
this.refenceForm.joura = res.data.joura;
this.refenceForm.author = res.data.author;
this.refenceForm.dateno = res.data.dateno;
this.refenceForm.doilink = res.data.doilink;
this.refenceForm.doi = res.data.doi;
this.refenceForm.title = res.data.title;
this.refenceForm.isbn = this.refenceForm.refer_type == 'book' ? res.data.doilink : res.data.isbn;
})
.catch((err) => {
loading.close();
this.$message.error(err);
});
},
getRefData(id) {
return this.$api
.post('api/References/get', {
account: localStorage.getItem('U_name'),
p_refer_id: id
})
.then((res) => {
if (res && res.status == 1) return res.data;
throw (res && res.msg) || 'get ref failed';
});
},
/** 引用:编辑 */
async change(row, optitle) {
this.dialogTitle = optitle;
this.isShowParsing = false;
this.isShowParsingData = false;
const loading = this.$loading({
lock: true,
text: 'Loading...',
background: 'rgba(0, 0, 0, 0.7)'
});
try {
const data = await this.getRefData(row.p_refer_id);
this.SourceType = data.refer_type ? data.refer_type : '';
this.refenceForm = {
doi: '',
p_article_id: this.p_article_id,
p_refer_id: row.p_refer_id,
pre_p_refer_id: null,
refer_type: data.refer_type ? data.refer_type : '',
author: data.author ? data.author : '',
doilink: data.doilink ? data.doilink : '',
dateno: data.dateno ? data.dateno : '',
isbn: data.isbn ? data.isbn : '',
joura: data.joura ? data.joura : '',
content: data.deal_content ? data.deal_content : '',
title: data.title ? data.title : ''
};
this.old_refence_content = JSON.parse(JSON.stringify(data.deal_content));
this.old_refence_type = JSON.parse(JSON.stringify(data.refer_type));
if (this.SourceType === 'book') {
this.refenceForm.isbn = data.doilink ? data.doilink : '';
}
this.editboxVisible = true;
} catch (e) {
this.$message.error(e);
} finally {
loading.close();
}
},
/** 引用:新增(在当前行下方插入) */
addLine(optitle) {
this.dialogTitle = optitle;
this.old_refence_type = '';
this.old_refence_content = '';
this.SourceType = 'journal';
this.refenceForm = {
doi: '',
p_article_id: this.p_article_id,
p_refer_id: null,
pre_p_refer_id: this.chanFerForm.length > 0 ? this.chanFerForm[this.chanFerForm.length - 1].p_refer_id : null,
refer_type: '',
joura: '',
author: '',
doilink: '',
dateno: '',
isbn: '',
content: '',
title: ''
};
this.isShowParsing = true;
this.isShowParsingData = false;
this.editboxVisible = true;
this.$nextTick(() => {
if (this.$refs.refenceForm) {
this.$refs.refenceForm.clearValidate(['content']);
this.$refs.refenceForm.clearValidate(['doi']);
}
});
},
/** 引用:删除 */
deleteLine(row) {
this.$confirm('Are you sure you want to remove this reference?', 'Tips', {
confirmButtonText: 'Sure',
cancelButtonText: 'Cancel',
type: 'warning'
})
.then(() => {
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.$api
.post('api/Preaccept/delRefer', {
p_refer_id: row.p_refer_id
})
.then((res) => {
loading.close();
if (res.code == 0) {
this.$message.success('remove successed!');
this.changeRefer();
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err);
});
})
.catch(() => {});
},
saveChange() {
this.editRefSave();
},
editRefSave() {
this.refenceForm.refer_type = this.SourceType;
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const form = { ...this.refenceForm };
this.$api
.post('api/Preaccept/editRefer', form)
.then((res) => {
loading.close();
if (res.code == 0) {
this.$message.success('successed');
this.changeRefer();
this.cancelSave();
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err);
});
},
saveAdd() {
this.refenceForm.refer_type = this.SourceType;
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const form = { ...this.refenceForm };
this.$api
.post('api/Preaccept/addReferByParticleid', form)
.then((res) => {
loading.close();
if (res.code == 0) {
this.$message.success('successed');
this.changeRefer();
this.cancelSave();
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
loading.close();
this.$message.error(err);
});
},
formatTitle(title) { formatTitle(title) {
if (!title) return ''; if (!title) return '';
// 使用正则匹配,'gi' 表示全局匹配且不区分大小写 // 使用正则匹配,'gi' 表示全局匹配且不区分大小写
// \b 确保是完整单词匹配,防止误伤含有这些字母的其他单词 // \b 确保是完整单词匹配,防止误伤含有这些字母的其他单词
const reg = /\b(Retracted|Retraction)\b/gi; const reg = /\b(Retracted|Retraction)\b/gi;
return title.replace(reg, (match) => { return title.replace(reg, (match) => {
return `<span style="color: red; font-weight: bold;">${match}</span>`; return `<span style="color: red; font-weight: bold;">${match}</span>`;
}); });
}, },
getJournalDateno(dateno, type) { getJournalDateno(dateno, type) {
if (dateno && typeof dateno === 'string') { if (dateno && typeof dateno === 'string') {
const hasInvalidColon = !dateno.includes(':') || (dateno.includes(':') && dateno.split(':').pop().trim() === ''); const hasInvalidColon = !dateno.includes(':') || (dateno.includes(':') && dateno.split(':').pop().trim() === '');
@@ -1091,10 +1463,7 @@ export default {
if (this.editVisible && this.currentContent && this.currentContent.am_id != null) { if (this.editVisible && this.currentContent && this.currentContent.am_id != null) {
let html = this.editModalDraftHtml; let html = this.editModalDraftHtml;
if (html === '' || html == null) { if (html === '' || html == null) {
const t = const t = this.$refs.commonContent && this.$refs.commonContent.$refs && this.$refs.commonContent.$refs.tinymceChild1;
this.$refs.commonContent &&
this.$refs.commonContent.$refs &&
this.$refs.commonContent.$refs.tinymceChild1;
if (t && t.editorInstance) { if (t && t.editorInstance) {
html = t.editorInstance.getContent({ format: 'raw' }) || ''; html = t.editorInstance.getContent({ format: 'raw' }) || '';
} else { } else {
@@ -1133,27 +1502,20 @@ export default {
getTableDrawerMergedDraftHtml() { getTableDrawerMergedDraftHtml() {
const parts = []; const parts = [];
const titleInst = const titleInst =
this.$refs.tinymceChildTitle && this.$refs.tinymceChildTitle && this.$refs.tinymceChildTitle.$refs && this.$refs.tinymceChildTitle.$refs.tinymceChild1;
this.$refs.tinymceChildTitle.$refs &&
this.$refs.tinymceChildTitle.$refs.tinymceChild1;
if (titleInst && titleInst.editorInstance) { if (titleInst && titleInst.editorInstance) {
parts.push(titleInst.editorInstance.getContent({ format: 'raw' }) || ''); parts.push(titleInst.editorInstance.getContent({ format: 'raw' }) || '');
} else if (this.lineStyle && typeof this.lineStyle.title === 'string') { } else if (this.lineStyle && typeof this.lineStyle.title === 'string') {
parts.push(this.lineStyle.title); parts.push(this.lineStyle.title);
} }
const tableInst = const tableInst = this.$refs.commonTable && this.$refs.commonTable.$refs && this.$refs.commonTable.$refs.tinymceChild1;
this.$refs.commonTable &&
this.$refs.commonTable.$refs &&
this.$refs.commonTable.$refs.tinymceChild1;
if (tableInst && tableInst.editorInstance) { if (tableInst && tableInst.editorInstance) {
parts.push(tableInst.editorInstance.getContent({ format: 'raw' }) || ''); parts.push(tableInst.editorInstance.getContent({ format: 'raw' }) || '');
} else if (this.lineStyle && typeof this.lineStyle.html_data === 'string') { } else if (this.lineStyle && typeof this.lineStyle.html_data === 'string') {
parts.push(this.lineStyle.html_data); parts.push(this.lineStyle.html_data);
} }
const noteInst = const noteInst =
this.$refs.tinymceChildNote && this.$refs.tinymceChildNote && this.$refs.tinymceChildNote.$refs && this.$refs.tinymceChildNote.$refs.tinymceChild1;
this.$refs.tinymceChildNote.$refs &&
this.$refs.tinymceChildNote.$refs.tinymceChild1;
if (noteInst && noteInst.editorInstance) { if (noteInst && noteInst.editorInstance) {
parts.push(noteInst.editorInstance.getContent({ format: 'raw' }) || ''); parts.push(noteInst.editorInstance.getContent({ format: 'raw' }) || '');
} else if (this.lineStyle && typeof this.lineStyle.note === 'string') { } else if (this.lineStyle && typeof this.lineStyle.note === 'string') {
@@ -1241,9 +1603,7 @@ export default {
*/ */
buildDisplayCiteMap(orderArray) { buildDisplayCiteMap(orderArray) {
const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : []; const refs = Array.isArray(this.chanFerForm) ? this.chanFerForm : [];
const refIdSet = new Set( const refIdSet = new Set(refs.map((r) => (r && r.p_refer_id != null ? String(r.p_refer_id) : '')).filter(Boolean));
refs.map((r) => (r && r.p_refer_id != null ? String(r.p_refer_id) : '')).filter(Boolean)
);
const map = {}; const map = {};
if (!Array.isArray(orderArray) || orderArray.length === 0) { if (!Array.isArray(orderArray) || orderArray.length === 0) {
refs.forEach((row, idx) => { refs.forEach((row, idx) => {
@@ -1308,8 +1668,7 @@ export default {
if (!Array.isArray(orderedIds) || orderedIds.length === 0) return; if (!Array.isArray(orderedIds) || orderedIds.length === 0) return;
const ids = orderedIds.map(String); const ids = orderedIds.map(String);
const prevOrder = this.articleCiteIdOrder || []; const prevOrder = this.articleCiteIdOrder || [];
const sameOrder = const sameOrder = ids.length === prevOrder.length && ids.every((id, i) => String(id) === String(prevOrder[i]));
ids.length === prevOrder.length && ids.every((id, i) => String(id) === String(prevOrder[i]));
if (!sameOrder) { if (!sameOrder) {
this.articleCiteIdOrder = ids.slice(); this.articleCiteIdOrder = ids.slice();
} }
@@ -1500,7 +1859,10 @@ export default {
if (!s) return []; if (!s) return [];
let t = s.replace(/^\s*\[/, '').replace(/\]\s*$/, ''); let t = s.replace(/^\s*\[/, '').replace(/\]\s*$/, '');
t = t.replace(/[]/g, ','); t = t.replace(/[]/g, ',');
const parts = t.split(',').map((x) => x.trim()).filter(Boolean); const parts = t
.split(',')
.map((x) => x.trim())
.filter(Boolean);
const out = new Set(); const out = new Set();
parts.forEach((p) => { parts.forEach((p) => {
const m = p.match(/^(\d+)\s*[-–—]\s*(\d+)$/); const m = p.match(/^(\d+)\s*[-–—]\s*(\d+)$/);
@@ -1546,13 +1908,17 @@ export default {
handleRefSelectorRowClick(row, column, event) { handleRefSelectorRowClick(row, column, event) {
if (column && column.type === 'selection') return; if (column && column.type === 'selection') return;
if (event && event.target && typeof event.target.closest === 'function') { if (event && event.target && typeof event.target.closest === 'function') {
if (event.target.closest('.el-checkbox') || event.target.closest('a[href]')) return; if (
event.target.closest('.el-checkbox') ||
event.target.closest('a[href]') ||
event.target.closest('.operation') ||
event.target.closest('.el-button')
)
return;
} }
const table = this.$refs.refSelectorTable; const table = this.$refs.refSelectorTable;
if (!table || !row) return; if (!table || !row) return;
const isSelected = (this.refSelectedRows || []).some( const isSelected = (this.refSelectedRows || []).some((r) => r && String(r.p_refer_id) === String(row.p_refer_id));
(r) => r && String(r.p_refer_id) === String(row.p_refer_id)
);
table.toggleRowSelection(row, !isSelected); table.toggleRowSelection(row, !isSelected);
}, },
handleConfirmRefCite() { handleConfirmRefCite() {
@@ -1595,16 +1961,32 @@ export default {
this.$refs.commonContent.refreshAutociteDisplay(); this.$refs.commonContent.refreshAutociteDisplay();
} }
} }
if (this.addContentVisible && this.$refs.addContent && typeof this.$refs.addContent.refreshAutociteDisplay === 'function') { if (
this.addContentVisible &&
this.$refs.addContent &&
typeof this.$refs.addContent.refreshAutociteDisplay === 'function'
) {
this.$refs.addContent.refreshAutociteDisplay(); this.$refs.addContent.refreshAutociteDisplay();
} }
if (this.threeVisible && this.$refs.commonTable && typeof this.$refs.commonTable.refreshAutociteDisplay === 'function') { if (
this.threeVisible &&
this.$refs.commonTable &&
typeof this.$refs.commonTable.refreshAutociteDisplay === 'function'
) {
this.$refs.commonTable.refreshAutociteDisplay(); this.$refs.commonTable.refreshAutociteDisplay();
} }
if (this.threeVisible && this.$refs.tinymceChildNote && typeof this.$refs.tinymceChildNote.refreshAutociteDisplay === 'function') { if (
this.threeVisible &&
this.$refs.tinymceChildNote &&
typeof this.$refs.tinymceChildNote.refreshAutociteDisplay === 'function'
) {
this.$refs.tinymceChildNote.refreshAutociteDisplay(); this.$refs.tinymceChildNote.refreshAutociteDisplay();
} }
if (this.pictVisible && this.$refs.tinymceChildImgNote && typeof this.$refs.tinymceChildImgNote.refreshAutociteDisplay === 'function') { if (
this.pictVisible &&
this.$refs.tinymceChildImgNote &&
typeof this.$refs.tinymceChildImgNote.refreshAutociteDisplay === 'function'
) {
this.$refs.tinymceChildImgNote.refreshAutociteDisplay(); this.$refs.tinymceChildImgNote.refreshAutociteDisplay();
} }
}); });
@@ -1752,7 +2134,7 @@ export default {
// 获取最终修改后的 HTML // 获取最终修改后的 HTML
content = div.innerHTML; content = div.innerHTML;
this.saveContent(content, this.currentContent.am_id); this.saveContent(content, this.currentContent.am_id);
} else if (type == 'addcontent') { } else if (type == 'addcontent') {
var hasTable = /<table[\s\S]*?>[\s\S]*?<\/table>/i.test(content); var hasTable = /<table[\s\S]*?>[\s\S]*?<\/table>/i.test(content);
@@ -1765,7 +2147,6 @@ export default {
return false; return false;
} }
var list = this.$commonJS.cleanAndParseWordContent(content); var list = this.$commonJS.cleanAndParseWordContent(content);
this.saveContentList(list, this.currentId); this.saveContentList(list, this.currentId);
} else if (type == 'table') { } else if (type == 'table') {
@@ -1814,8 +2195,6 @@ export default {
}, },
async saveContent(content, am_id) { async saveContent(content, am_id) {
const loading = this.$loading({ const loading = this.$loading({
lock: true, lock: true,
text: 'Loading...', text: 'Loading...',
@@ -1825,8 +2204,8 @@ export default {
var that = this; var that = this;
var str = content.replace(/^<p>\s*(.*?)\s*<\/p>$/, '$1').trim(); var str = content.replace(/^<p>\s*(.*?)\s*<\/p>$/, '$1').trim();
str = str.replace(/<br\s*\/?>/gi, ''); str = str.replace(/<br\s*\/?>/gi, '');
str = await that.$commonJS.decodeHtml(str); str = await that.$commonJS.decodeHtml(str);
/** 点 Save 立即按将写入的正文重排下方列表(不等待接口),与弹窗内 [n] 一致 */ /** 点 Save 立即按将写入的正文重排下方列表(不等待接口),与弹窗内 [n] 一致 */
@@ -1935,7 +2314,6 @@ export default {
} }
}, },
saveLateX(data) { saveLateX(data) {
// 1. 从 data 中解构出 wrap (或者你命名的模式变量) // 1. 从 data 中解构出 wrap (或者你命名的模式变量)
const { editorId, wmathId, latex, wrap } = data; const { editorId, wmathId, latex, wrap } = data;
const newLatex = latex ? latex.trim() : ''; const newLatex = latex ? latex.trim() : '';
@@ -2191,7 +2569,6 @@ export default {
}); });
}, },
async addCommentSetting(content) { async addCommentSetting(content) {
await this.$api await this.$api
.post(this.urlList.addComment, { .post(this.urlList.addComment, {
am_id: content.am_id, am_id: content.am_id,
@@ -2214,10 +2591,9 @@ export default {
}); });
}, },
async addComment(content) { async addComment(content) {
var str = this.$commonJS.transformHtmlString(content);
var str= this.$commonJS.transformHtmlString(content) str = str.replace(/<br\s*\/?>/gi, '');
str=str.replace(/<br\s*\/?>/gi, ''); console.log('🚀 ~ addComment ~ content:', str);
console.log("🚀 ~ addComment ~ content:", str);
if (str == '') { if (str == '') {
this.$message({ this.$message({
type: 'warning', type: 'warning',
@@ -2299,13 +2675,12 @@ export default {
this.getCommentList(); this.getCommentList();
}, },
editComment(comment, type) { editComment(comment, type) {
this.commentForm = { this.commentForm = {
...comment, ...comment,
type: type, type: type,
remark: type == 'user' ? comment.author_remark : comment.remark remark: type == 'user' ? comment.author_remark : comment.remark
}; };
this.commentVisible = true; this.commentVisible = true;
}, },
async onAddComment(data) { async onAddComment(data) {
@@ -2425,7 +2800,6 @@ export default {
}); });
// 处理文件上传并传递回调函数 // 处理文件上传并传递回调函数
this.$commonJS.handleFileUpload(event, function (tables) { this.$commonJS.handleFileUpload(event, function (tables) {
if (tables.length == 0) { if (tables.length == 0) {
loading.close(); loading.close();
that.$message({ that.$message({
@@ -2675,7 +3049,6 @@ export default {
} }
}, },
updateChange(content, type) { updateChange(content, type) {
var str = this.$commonJS.transformHtmlString(content); var str = this.$commonJS.transformHtmlString(content);
if (type == 'imgNote') { if (type == 'imgNote') {
this.picStyle1.note = str; this.picStyle1.note = str;
@@ -2965,8 +3338,6 @@ export default {
// 确定保存图片 // 确定保存图片
async savePic() { async savePic() {
var str = this.picStyle1.note ? await this.$commonJS.decodeHtml(this.picStyle1.note) : ''; var str = this.picStyle1.note ? await this.$commonJS.decodeHtml(this.picStyle1.note) : '';
var titleStr = this.picStyle1.title ? await this.$commonJS.decodeHtml(this.picStyle1.title) : ''; var titleStr = this.picStyle1.title ? await this.$commonJS.decodeHtml(this.picStyle1.title) : '';
@@ -3157,45 +3528,46 @@ export default {
} }
}, },
async saveTable(content) { async saveTable(content) {
const cleanTableData = (tableList) => { const cleanTableData = (tableList) => {
if (tableList.length == 0) { if (tableList.length == 0) {
return []; return [];
} else { } else {
// 定义清理函数:去掉所有 br 标签和 TinyMCE 占位符 // 定义清理函数:去掉所有 br 标签和 TinyMCE 占位符
const cleanText = (text) => { const cleanText = (text) => {
if (!text) return ""; if (!text) return '';
// return text.replace(/<br\s*\/?>/gi, '').trim(); // return text.replace(/<br\s*\/?>/gi, '').trim();
return text return text;
}; };
// 1. 获取处理后的干净表头 // 1. 获取处理后的干净表头
const header = tableList[0].map(cell => ({ const header = tableList[0].map((cell) => ({
...cell, ...cell,
text: cleanText(cell.text) text: cleanText(cell.text)
})); }));
// 2. 过滤逻辑 // 2. 过滤逻辑
const cleanedTable = tableList.map((row) => { const cleanedTable = tableList
// 首先:把每一行里的每个 cell.text 里的 <br> 都去掉 .map((row) => {
return row.map(cell => ({ // 首先:把每一行里的每个 cell.text 里的 <br> 都去掉
...cell, return row.map((cell) => ({
text: cleanText(cell.text) ...cell,
})); text: cleanText(cell.text)
}).filter((row, index) => { }));
if (index === 0) return true; })
.filter((row, index) => {
if (index === 0) return true;
// 3. 此时比较的就是没有 <br> 的文本了 // 3. 此时比较的就是没有 <br> 的文本了
const isHeaderRow = row.every((cell, cellIndex) => { const isHeaderRow = row.every((cell, cellIndex) => {
return cell.text === header[cellIndex].text; return cell.text === header[cellIndex].text;
}); });
return !isHeaderRow; return !isHeaderRow;
}); });
return cleanedTable; return cleanedTable;
} }
}; };
var cleanedTableList = content.table ? content.table : []; var cleanedTableList = content.table ? content.table : [];
cleanedTableList = cleanTableData(content.table); cleanedTableList = cleanTableData(content.table);
@@ -3221,8 +3593,8 @@ export default {
}); });
strNote = strNote.replace(/<br\s*\/?>/gi, ''); strNote = strNote.replace(/<br\s*\/?>/gi, '');
strTitle = strTitle.replace(/<br\s*\/?>/gi, ''); strTitle = strTitle.replace(/<br\s*\/?>/gi, '');
var tableStr=JSON.stringify(cleanedTableList) var tableStr = JSON.stringify(cleanedTableList);
if (this.lineStyle.visiTitle == 'Edit Table') { if (this.lineStyle.visiTitle == 'Edit Table') {
this.$api this.$api
.post(this.urlList.editTable, { .post(this.urlList.editTable, {
@@ -3808,7 +4180,8 @@ wmath[data-wrap='inline'] {
background-color: rgb(252, 98, 93); background-color: rgb(252, 98, 93);
background-color: rgb(252 98 93 / 68%); background-color: rgb(252 98 93 / 68%);
color: #333; color: #333;
}.status { }
.status {
display: block; display: block;
width: 36px; width: 36px;
height: 36px; height: 36px;
@@ -3828,8 +4201,7 @@ wmath[data-wrap='inline'] {
} }
.status.float { .status.float {
display: display: inline-block;
inline-block;
} }
.doiLink { .doiLink {
color: #409eff; color: #409eff;

View File

@@ -432,8 +432,17 @@ export default {
let lastIndex = 0; let lastIndex = 0;
const pieces = []; const pieces = [];
let replaced = 0; let replaced = 0;
let skippedSpecial = 0;
while ((m = re.exec(text)) !== null) { while ((m = re.exec(text)) !== null) {
const nums = this.parseBracketInnerToNumbers(m[1]); const nums = this.parseBracketInnerToNumbers(m[1]);
// 规则:只要括号里包含 0 或 -1这一段不做解析替换但不影响其它正常 [1][2]
if (nums.some((n) => n === 0 || n === -1)) {
pieces.push({ type: 'text', s: text.slice(lastIndex, m.index) });
pieces.push({ type: 'text', s: m[0] });
lastIndex = m.index + m[0].length;
skippedSpecial++;
continue;
}
const ids = []; const ids = [];
const seen = new Set(); const seen = new Set();
nums.forEach((n) => { nums.forEach((n) => {
@@ -470,6 +479,13 @@ export default {
} }
}); });
parent.replaceChild(frag, textNode); parent.replaceChild(frag, textNode);
if (skippedSpecial > 0) {
// 提示:仅提示一次即可;这里在节点级别提示可能重复,放到宏任务末尾合并展示
clearTimeout(this._autoLinkSkipToastTimer);
this._autoLinkSkipToastTimer = setTimeout(() => {
this.$message.info(`Skipped ${skippedSpecial} bracket cite(s) containing 0/-1.`);
}, 0);
}
return replaced; return replaced;
}, },
renderAutociteInEditor(ed) { renderAutociteInEditor(ed) {

View File

@@ -3679,8 +3679,8 @@ renderCiteLabels(html) {
text = this.highlightText3(raw, [], type, item.am_id); text = this.highlightText3(raw, [], type, item.am_id);
} }
} else { } else {
// 预览与编辑「显示批注」同源:先切 wmath/autocite、再 renderCiteLabels避免仅跑片段导致角标/公式不一致 // 预览:仅正文+角标+公式不传批注highlightText1 内 isPreview 会跳过 remarkbg
text = this.highlightText1(String(raw || ''), annotations || [], type); text = this.highlightText1(String(raw || ''), [], type);
} }
// const finalHtml = text.replace(/<(?!(\/?(span|p|div|table|tr|td|th|b|i|strong|em|ul|ol|li|br|img|myh3|myfigure|mytable|blue|wmath)))/gi, '&lt;'); // const finalHtml = text.replace(/<(?!(\/?(span|p|div|table|tr|td|th|b|i|strong|em|ul|ol|li|br|img|myh3|myfigure|mytable|blue|wmath)))/gi, '&lt;');
@@ -3821,6 +3821,16 @@ renderCiteLabels(html) {
} }
if (lastIdx < src.length) parts.push({ type: 'plain', html: src.slice(lastIdx) }); if (lastIdx < src.length) parts.push({ type: 'plain', html: src.slice(lastIdx) });
// 预览:只拼回正文与公式块,再 renderCiteLabels不批注高亮、不做 AI/批注占位
if (this.isPreview) {
let htmlPrev = parts.map((p) => p.html).join('');
htmlPrev = this.renderCiteLabels(htmlPrev);
if (type === 0 && htmlPrev.trim() === '') {
htmlPrev += `<img contenteditable="false" src="${this.imagePath || ''}" alt="" style="width:20px;height:20px;opacity:.6;">`;
}
return htmlPrev;
}
// 2) 预处理批注(用 annotation.content // 2) 预处理批注(用 annotation.content
const anns = (annotations || []) const anns = (annotations || [])
.map((a, i) => ({ .map((a, i) => ({
@@ -4945,7 +4955,7 @@ renderCiteLabels(html) {
} }
.proofreading-num { .proofreading-num {
z-index: 2; z-index: 2;
background: #0082aa; background: #e61a12;
color: rgb(255, 255, 255); color: rgb(255, 255, 255);
border-radius: 20px; border-radius: 20px;
padding: 0px 6px; padding: 0px 6px;