1、审稿人14天超时 重新审稿邀请

2、nbsp 转换
3、H3标记为myh3
4、文章详情审稿和编委邮箱机构显示
5、稿号开头为Draft 状态为Reject->Awaiting Submission
6、作者端稿件重复(标题)
7、produce 增加 通讯作者详细地址
8、produce 增加 是否显示图文摘要1
This commit is contained in:
2026-02-10 09:36:35 +08:00
parent 9f108e7c81
commit 5edf128cfb
16 changed files with 680 additions and 546 deletions

View File

@@ -19,8 +19,8 @@ const service = axios.create({
// baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换
// baseURL: 'http://www.tougao.com/', //测试本地 记得切换
// baseURL: 'http://192.168.110.110/tougao/public/index.php/',
baseURL: '/api', //本地
// baseURL: '/', //正式
// baseURL: '/api', //本地
baseURL: '/', //正式
});

View File

@@ -13,9 +13,7 @@ const baseUrl = '/';
//测试环境
// const mediaUrl = 'http://tougaotest.tmrjournals.com/public/';
// // const mediaUrl = 'http://zmzm.tougao.dev.com/public/';
// const baseUrl = '/api';
////新正式环境
// const mediaUrl = 'http://mytest.tmrjournals.com/public/';

View File

@@ -290,12 +290,13 @@ const en = {
state1: 'With editor',
state2: 'Under review',
state3: 'Reject',
stateDraft: 'Awaiting Submission',
state4: 'Revision',
state5: 'Accept',
state6: 'Pre-accept',
state8: 'Final Decision',
act1: 'Dealing',
act2: 'Finished'
act2: 'Finished',
},
evaluationsis: {
EmploymentRate: 'Employment Rate',

View File

@@ -276,6 +276,7 @@ const zh = {
state1: '已受理',
state2: '送审中',
state3: '拒稿',
stateDraft: '待提交',
state4: '退修',
state5: '接收',
state6: '预接收',

View File

@@ -780,9 +780,8 @@ export default {
var that = this;
var str = content.replace(/^<p>\s*(.*?)\s*<\/p>$/, '$1').trim();
if (str.replace(/<br\s*\/?>/gi, '').trim() === '') {
str = '';
}
str = str.replace(/<br\s*\/?>/gi, '');
str = await that.$commonJS.decodeHtml(str);
await that.$api
@@ -1138,7 +1137,7 @@ export default {
});
},
async addCommentSetting(content) {
await this.$api
.post(this.urlList.addComment, {
am_id: content.am_id,
@@ -1161,8 +1160,10 @@ export default {
});
},
async addComment(content) {
var str = content.replace(/^<p>(.*?)<\/p>$/, '$1') ? content.replace(/^<p>(.*?)<\/p>$/, '$1') : '';
var str= this.$commonJS.transformHtmlString(content)
str=str.replace(/<br\s*\/?>/gi, '');
console.log("🚀 ~ addComment ~ content:", str);
if (str == '') {
this.$message({
type: 'warning',
@@ -1884,6 +1885,9 @@ export default {
// 确定保存图片
async savePic() {
var str = this.picStyle1.note ? await this.$commonJS.decodeHtml(this.picStyle1.note) : '';
var titleStr = this.picStyle1.title ? await this.$commonJS.decodeHtml(this.picStyle1.title) : '';
if (!this.picStyle.picUrl) {
@@ -1894,6 +1898,8 @@ export default {
this.$message.error('Please enter a title');
return;
}
str = str.replace(/<br\s*\/?>/gi, '');
titleStr = titleStr.replace(/<br\s*\/?>/gi, '');
const loading = this.$loading({
lock: true,
text: 'Loading...',
@@ -1969,28 +1975,42 @@ export default {
},
async saveTable(content) {
const cleanTableData = (tableList) => {
if (tableList.length == 0) {
return [];
} else {
// 假设第一行是表头,保存表头
const header = tableList[0];
if (tableList.length == 0) {
return [];
} else {
// 定义清理函数:去掉所有 br 标签和 TinyMCE 占位符
const cleanText = (text) => {
if (!text) return "";
return text.replace(/<br\s*\/?>/gi, '').trim();
};
// 从第二行开始,移除与表头相同的行
const cleanedTable = tableList.filter((row, index) => {
// 如果是第一行,则保留
if (index === 0) return true;
// 1. 获取处理后的干净表头
const header = tableList[0].map(cell => ({
...cell,
text: cleanText(cell.text)
}));
// 比较当前行的每个单元格与表头是否相同
const isHeaderRow = row.every((cell, cellIndex) => {
return cell.text === header[cellIndex].text;
});
// 2. 过滤逻辑
const cleanedTable = tableList.map((row) => {
// 首先:把每一行里的每个 cell.text 里的 <br> 都去掉
return row.map(cell => ({
...cell,
text: cleanText(cell.text)
}));
}).filter((row, index) => {
if (index === 0) return true;
return !isHeaderRow; // 只保留与表头不同的行
});
// 3. 此时比较的就是没有 <br> 的文本了
const isHeaderRow = row.every((cell, cellIndex) => {
return cell.text === header[cellIndex].text;
});
return cleanedTable;
}
};
return !isHeaderRow;
});
return cleanedTable;
}
};
var cleanedTableList = content.table ? content.table : [];
cleanedTableList = cleanTableData(content.table);
@@ -2014,11 +2034,15 @@ export default {
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
strNote = strNote.replace(/<br\s*\/?>/gi, '');
strTitle = strTitle.replace(/<br\s*\/?>/gi, '');
var tableStr=JSON.stringify(cleanedTableList)
if (this.lineStyle.visiTitle == 'Edit Table') {
this.$api
.post(this.urlList.editTable, {
amt_id: this.lineStyle.amt_id,
table_data: JSON.stringify(cleanedTableList),
table_data: tableStr,
html_data: content.html_data,
note: strNote,
title: strTitle

View File

@@ -928,7 +928,7 @@
<div>
<span>Status : </span>
<b>{{ articleState }}</b>
<el-button type="text" @click="testvis" icon="el-icon-edit">Change</el-button>
<el-button type="text" @click="testvis" icon="el-icon-edit" v-if="!(form.is_draft==1&&form.state==3)">Change</el-button>
</div>
<div style="display: flex; width: 100%">
<span>Remarks :</span>
@@ -1553,6 +1553,7 @@ export default {
remarks: '',
state: '',
ctime: '',
is_draft: '',
authorList: [],
transList: []
},
@@ -1788,10 +1789,17 @@ export default {
},
articleState: function () {
let str = '';
switch (this.form.state) {
if(this.form.is_draft==1&&this.form.state==3){
str = this.$t('artstate.stateDraft');
return str;
}else{
switch (this.form.state) {
case 0:
str = this.$t('artstate.state0');
break;
case 1:
str = this.$t('artstate.state1');
break;
@@ -1814,7 +1822,9 @@ export default {
str = this.$t('artstate.state6');
break;
}
return str;
return str;
}
}
},
methods: {
@@ -2540,6 +2550,7 @@ export default {
this.form.is_use_ai = res.article.is_use_ai;
this.form.use_ai_explain = res.article.use_ai_explain;
this.form.transList = res.transfer;
this.form.is_draft = res.article.is_draft;
this.msgs = res.msg;
var alist = res.authors;
let alist_name = [];

View File

@@ -87,9 +87,13 @@
</p> -->
<div class="man_state" :style="item.state,'3' | stateChange" >
<b :style="item.state,'4' | stateChange">
<b :style="item.state,'4' | stateChange" v-if="item.is_draft==1&&item.state==3">
{{$t('artstate.stateDraft')}}
</b>
<b :style="item.state,'4' | stateChange" v-else>
{{item.state,'tst' | stateChange}}
</b>
</div>
<div class="man_btn" style="overflow: hidden;">
@@ -1058,7 +1062,7 @@ this.$router.push({
border: 1px solid #fff;
color: #fff;
text-align: center;
padding: 6px 18px;
padding: 6px 12px;
letter-spacing: -0.5px;
border-top-right-radius: 3px;
/* border-top-left-radius: 3px; */

View File

@@ -125,7 +125,7 @@
<span style="float: right">
<span class="labelTitle" style="font-weight: 500; font-size: 13px">Status :</span>
<font style="margin-right: 15px; font-size: 13px; letter-spacing: -0.5px; font-weight: 700">
{{ stateFormat(item.state) }}
{{ stateFormat(item.is_draft==1&&item.state==3?'draft':item.state) }}
</font>
</span>
</p>
@@ -2629,6 +2629,9 @@ export default {
case 0:
str = this.$t('artstate.state0');
break;
case 'draft':
str = this.$t('artstate.stateDraft');
break;
case 1:
str = this.$t('artstate.state1');
break;

View File

@@ -96,6 +96,11 @@
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="Use the Picture as Graphic Abstract :" prop="is_graphical_abstract" label-width="260px">
<el-radio v-model="detailMes.is_graphical_abstract" :label="1">Yes</el-radio>
<el-radio v-model="detailMes.is_graphical_abstract" :label="2">No</el-radio>
</el-form-item>
<el-form-item label="Type :" prop="type">
<el-select v-model="detailMes.type" filterable placeholder="Please select an article type">
<el-option v-for="item in opMedical" :key="item.value" :label="item.label" :value="item.value"> </el-option>
@@ -108,65 +113,61 @@
<el-input v-model="detailMes.tradition_tag" placeholder="eg Highlights"></el-input>
</el-form-item>
<el-form-item label="Tradition Content :">
<tinymce
<div class="tinymce-container">
<div v-if="tradition==null" class="loading-status loadTinymceBox">
<i class="el-icon-loading"></i> Fetching highlights, please wait...
</div>
<tinymce v-else
type="tradition"
:height="160"
id="tiny-tradition"
ref="tinymceChild2"
:tinymceOtherInit="{
forced_root_block: '',
newline_behavior: 'linebreak',
newline_behavior: 'linebreak'
}"
valid_elements=",br"
:wordStyle="`p{font-size: 13px;}`"
:isAutomaticUpdate="true"
@getContent="getContent"
@updateChange="(content) => updateChange('tradition',content)"
@updateChange="(content) => updateChange('tradition', content)"
:value="tradition"
class="paste-area text-container"
:toolbar="[
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton'
]"
style="
line-height: 12px;
overflow: auto;
font-size: 13px; /* 字体大小 */
margin-top: 0pt; /* 段前间距 */
margin-bottom: 0pt; /* 段后间距 */
"
style="line-height: 12px; overflow: auto; font-size: 13px; margin-top: 0pt; margin-bottom: 0pt"
></tinymce>
</div>
<!-- <quill-editor ref="myTextEditor" v-model="detailMes.tradition" :options="editorOption"> </quill-editor> -->
</el-form-item>
<el-form-item label="Medical history of objective :">
<tinymce
<div class="tinymce-container">
<div v-if="mhooStr==null" class="loading-status loadTinymceBox">
<i class="el-icon-loading"></i> Fetching medical history of objective, please wait...
</div>
<tinymce v-else
type="mhooStr"
:height="160"
ref="tinymceChild3"
:tinymceOtherInit="{
forced_root_block: '',
newline_behavior: 'linebreak',
newline_behavior: 'linebreak'
}"
valid_elements=",br"
:wordStyle="`p{font-size: 13px;}`"
:isAutomaticUpdate="true"
@getContent="getContent"
@updateChange="(content) => updateChange('mhooStr',content)"
id="tiny-mhoo"
@updateChange="(content) => updateChange('mhooStr', content)"
:value="mhooStr"
class="paste-area text-container"
:toolbar="[
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton'
]"
style="
line-height: 12px;
overflow: auto;
font-size: 13px; /* 字体大小 */
margin-top: 0pt; /* 段前间距 */
margin-bottom: 0pt; /* 段后间距 */
"
style="line-height: 12px; overflow: auto; font-size: 13px; margin-top: 0pt; margin-bottom: 0pt"
></tinymce>
</div>
<!-- <quill-editor ref="myTextEditor" v-model="detailMes.mhoo" :options="editorOption"> </quill-editor> -->
</el-form-item>
<el-form-item label="keywords :" prop="keywords">
@@ -188,27 +189,27 @@
</el-form-item>
<el-form-item label="Abstract :" prop="abstract">
<template slot="label"> <span style="color: #f56c6c; margin-right: 4px">*</span>Abstract : </template>
<tinymce
type="Abstract"
:height="160"
ref="tinymceChild1"
:wordStyle="`p{font-size: 13px;}`"
:isAutomaticUpdate="true"
@getContent="getContent"
@updateChange="(content) => updateChange('abstract',content)"
:value="abstract"
class="paste-area text-container"
:toolbar="[
'bold italic |customBlue removeBlue|myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton'
]"
style="
line-height: 12px;
overflow: auto;
font-size: 13px; /* 字体大小 */
margin-top: 0pt; /* 段前间距 */
margin-bottom: 0pt; /* 段后间距 */
"
></tinymce>
<div class="tinymce-container">
<div v-if="abstract==null" class="loading-status loadTinymceBox">
<i class="el-icon-loading"></i> Fetching abstract, please wait...
</div>
<tinymce
v-else
type="Abstract"
:height="160"
ref="tinymceChild1"
id="tiny-abstract"
:wordStyle="`p{font-size: 13px;}`"
:isAutomaticUpdate="true"
@getContent="getContent"
@updateChange="(content) => updateChange('abstract', content)"
:value="abstract"
class="paste-area text-container"
:toolbar="['bold italic |customBlue removeBlue|myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton']"
style="line-height: 12px; overflow: auto; font-size: 13px; margin-top: 0pt; margin-bottom: 0pt"
></tinymce>
</div>
<!-- <quill-editor ref="myTextEditor" v-model="detailMes.abstract" :options="editorOption"> </quill-editor> -->
</el-form-item>
@@ -266,6 +267,7 @@
</el-table-column>
<el-table-column prop="author_country" label="Country" width="140px"></el-table-column>
<el-table-column prop="orcid" label="ORCID"></el-table-column>
<el-table-column prop="mailing_address" label="Corresponding author details" min-width="160px"></el-table-column>
<el-table-column label="" width="190" align="center">
<template slot-scope="scope">
<div class="operation">
@@ -292,7 +294,7 @@
</el-table>
<!-- 添加作者弹出框 -->
<el-dialog title="Add author" :visible.sync="addAuthor" width="650px" destroy-on-close :close-on-click-modal="false">
<el-form ref="add_Author" :model="addFomauthor" :rules="rules" label-width="165px">
<el-form ref="add_Author" :model="addFomauthor" :rules="rules" label-width="215px">
<el-form-item label="Author first name :" prop="first_name">
<el-input v-model="addFomauthor.first_name"></el-input>
</el-form-item>
@@ -312,7 +314,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="Email :" v-if="addFomauthor.is_report != '1'">
<el-input v-model="editFomauthor.email" type="email"></el-input>
<el-input v-model="addFomauthor.email" type="email"></el-input>
</el-form-item>
<el-form-item label="Email :" prop="email" v-if="addFomauthor.is_report == '1'">
<el-input v-model="addFomauthor.email" type="email"></el-input>
@@ -350,6 +352,17 @@
<template slot="prepend">https://orcid.org/</template>
</el-input>
</el-form-item>
<el-form-item
:required="addFomauthor.is_report == '1'"
label="Corresponding author details :"
prop="mailing_address"
>
<el-input
v-model="addFomauthor.mailing_address"
type="textarea"
placeholder="eg: Author full name. Affiliation address. Email."
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addAuthor = false">Canel</el-button>
@@ -358,7 +371,7 @@
</el-dialog>
<!-- 编辑作者弹出框 -->
<el-dialog title="Edit Author" :visible.sync="editAuthor" width="650px" :close-on-click-modal="false">
<el-form ref="edit_Author" :model="editFomauthor" :rules="rules" label-width="165px">
<el-form ref="edit_Author" :model="editFomauthor" :rules="rules" label-width="215px">
<el-form-item label="Author first name :" prop="first_name">
<el-input v-model="editFomauthor.first_name"></el-input>
</el-form-item>
@@ -415,6 +428,18 @@
<template slot="prepend">https://orcid.org/</template>
</el-input>
</el-form-item>
<el-form-item
:required="editFomauthor.is_report == '1'"
label="Corresponding author details :"
prop="mailing_address"
>
<el-input
v-model="editFomauthor.mailing_address"
type="textarea"
placeholder="eg: Author full name. Affiliation address. Email."
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editAuthor = false">Cancel</el-button>
@@ -688,7 +713,7 @@
<div class="bor_style_onli">
<h4>{{ tabsList[5].name }}</h4>
<div style="font-size: 14px">
<div style="margin: 30px 0px 0 0">
<div style="margin: 30px 0px 0 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Improve information</h5>
<!-- 时间Doi简介信息 -->
<el-form ref="Abs_Form" :model="detailMes" :rules="rules" label-width="150px" style="margin-top: 30px">
@@ -709,80 +734,97 @@
</el-form-item>
</el-form>
<div style="text-align: center; margin: 0px 0 0 0">
<el-button type="primary" @click="ZsSaveAbs" class="save-btn" style="margin-top: 10px;">
<el-button type="primary" @click="ZsSaveAbs" class="save-btn" style="margin-top: 10px">
<i class="el-icon-check"></i>
Save Essential Information
</el-button>
</div>
</div>
<div style="height: 1px; background-color: #c5e1f1; width: 100%; margin: 80px 0 40px"></div>
<div style="display: flex; /* 开启 flex 布局 */
justify-content: space-between;
padding: 20px;">
<div class="left-panel">
<div style="margin: 0 0 40px 0">
Choose Template :
<el-select v-model="shuTter.board" placeholder="Please select a template..." @change="select_tem($event)" style="width: 225px">
<el-option v-for="item in fol_low" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
<el-button type="primary" plain style="width: 150px; margin: 0 5px 0 15px" @click="EstaBlish">
<i class="el-icon-document"></i> Create Manuscript
</el-button>
</div>
<div style="display: flex; /* 开启 flex 布局 */ justify-content: space-between; padding: 20px">
<div class="left-panel">
<div style="margin: 0 0 40px 0">
Choose Template :
<el-select
v-model="shuTter.board"
placeholder="Please select a template..."
@change="select_tem($event)"
style="width: 225px"
>
<el-option v-for="item in fol_low" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-button type="primary" plain style="width: 150px; margin: 0 5px 0 15px" @click="EstaBlish">
<i class="el-icon-document"></i> Create Manuscript
</el-button>
</div>
<div style="margin: 30px 0 40px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Download list</h5>
<p v-if="DLfileList.length == 0" style="color: #666; font-size: 14px">No Manuscript</p>
<div v-for="(item, index) in DLfileList" :key="index" class="load_pdf">
<img src="../../assets/img/icon_0.png" />
<span style="color: #333">Typesetting {{ index + 1 }}</span>
<span style="margin-left: 40px; color: #888; font-size: 13px">Time : {{ modifDate(item.ctime * 1000) }}</span>
<a :href="'/tsfile/' + item.url" target="_blank" style="margin-left: 40px">
<i class="el-icon-download" style="color: #66b1ff; font-weight: bold"></i>
</a>
</div>
</div>
</div>
<div style="margin: 30px 0 40px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Download list</h5>
<p v-if="DLfileList.length == 0" style="color: #666; font-size: 14px">No Manuscript</p>
<div v-for="(item, index) in DLfileList" :key="index" class="load_pdf">
<img src="../../assets/img/icon_0.png" />
<span style="color: #333">Typesetting {{ index + 1 }}</span>
<span style="margin-left: 40px; color: #888; font-size: 13px"
>Time : {{ modifDate(item.ctime * 1000) }}</span
>
<a :href="'/tsfile/' + item.url" target="_blank" style="margin-left: 40px">
<i class="el-icon-download" style="color: #66b1ff; font-weight: bold"></i>
</a>
</div>
</div>
</div>
<div class="right-panel">
<h5 style="font-size: 14px; margin: 0 0 40px 0">
<el-button
type="success"
icon="el-icon-printer"
style="width: 150px; height: 32px; font-size: 12px; margin-right: 14px"
:loading="isGenerating"
@click="generatePDF"
>
{{ isGenerating ? 'Generating PDF...' : 'Generate PDF' }}
</el-button>
<span>Click the button below to start high-quality PDF typesetting.</span>
</h5>
<div style="margin: 30px 0 40px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px; font-weight: bold">
Download list
<span
><i
class="el-icon-refresh"
style="color: #006699; font-weight: bold; margin-left: 6px; cursor: pointer"
@click="refreshPdfList"
></i
></span>
<span
v-if="detailMes.file_sub_table"
style="color: #006699; font-size: 13px; font-weight: normal; float: right"
>Supplementary Material :<a
:href="'/public/articleSUBTAB/' + detailMes.file_sub_table"
target="_blank"
style="margin-left: 20px"
>
<i class="el-icon-download" style="color: #006699; font-weight: bold"></i> </a
></span>
</h5>
<p v-if="PDFfileList.length == 0" style="color: #666; font-size: 14px">No Manuscript</p>
<div v-for="(item, index) in PDFfileList" :key="index" class="load_pdf">
<img src="../../assets/img/icon_pdf.png" style="width: 26px; height: 26px" />
<span style="color: #333">Typesetting {{ index + 1 }}</span>
<span style="margin-left: 40px; color: #888; font-size: 13px"
>Time : {{ modifDate(item.create_time * 1000) }}</span
>
<a :href="'/public/' + item.url" target="_blank" style="margin-left: 40px">
<i class="el-icon-download" style="color: #66b1ff; font-weight: bold"></i>
</a>
</div>
</div>
</div>
</div>
<div class="right-panel">
<h5 style="font-size: 14px; margin: 0 0 40px 0;">
<el-button
type="success"
icon="el-icon-printer"
style="width: 150px; height: 32px; font-size: 12px;margin-right: 14px;"
:loading="isGenerating"
@click="generatePDF"
>
{{ isGenerating ? 'Generating PDF...' : 'Generate PDF' }}
</el-button>
<span>Click the button below to start high-quality PDF typesetting.</span>
</h5>
<div style="margin: 30px 0 40px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px;font-weight: bold;">Download list
<span><i class="el-icon-refresh" style="color: #006699; font-weight: bold;margin-left: 6px;cursor: pointer;" @click="refreshPdfList"></i></span>
<span v-if="detailMes.file_sub_table" style="color: #006699; font-size: 13px;font-weight: normal;float: right;">Supplementary Material :<a :href="'/public/articleSUBTAB/' +detailMes.file_sub_table" target="_blank" style="margin-left: 20px">
<i class="el-icon-download" style="color: #006699; font-weight: bold"></i>
</a></span>
</h5>
<p v-if="PDFfileList.length == 0" style="color: #666; font-size: 14px">No Manuscript</p>
<div v-for="(item, index) in PDFfileList" :key="index" class="load_pdf">
<img src="../../assets/img/icon_pdf.png" style="width: 26px; height: 26px;" />
<span style="color: #333">Typesetting {{ index + 1 }}</span>
<span style="margin-left: 40px; color: #888; font-size: 13px">Time : {{ modifDate(item.create_time * 1000) }}</span>
<a :href="'/public/' + item.url" target="_blank" style="margin-left: 40px">
<i class="el-icon-download" style="color: #66b1ff; font-weight: bold"></i>
</a>
</div>
</div>
</div>
</div>
<div style="height: 1px; background-color: #c5e1f1; width: 100%; margin: 10px 0"></div>
<div style="margin: 30px 0 30px 0">
<h5 style="font-size: 16px; margin: 0 0 30px 0; letter-spacing: -0.5px">Upload the final typeset file</h5>
@@ -829,7 +871,6 @@
</el-upload>
</div>
</div>
</div>
</div>
</div>
@@ -1030,7 +1071,7 @@ export default {
data() {
return {
isGenerating: false, // 按钮加载状态
percentage: 0, // 进度条
percentage: 0, // 进度条
finalReview: null,
baseUrl: this.Common.baseUrl,
mediaUrl: this.Common.mediaUrl,
@@ -1096,11 +1137,13 @@ export default {
],
detailMes: {
type: '',
is_graphical_abstract: '3',
journal_special_id: 'None'
},
abstract: '',
tradition: '',
mhooStr: '',
abstract: null,
tradition:null,
mhooStr: null,
opInstal: [],
// opMedical: [
// {
@@ -1475,6 +1518,40 @@ export default {
message: 'Please use English characters only',
trigger: 'blur'
}
],
is_graphical_abstract: [
{
required: true,
validator: function (rule, value, callback) {
if (value == 1 || value == 2) {
callback();
} else {
// 这里的英文提示更加符合地道的校验语境
callback(
new Error(
'Please select whether the uploaded picture should be shown as graphic abstract on the PDF title page.'
)
);
}
},
trigger: 'change' // 建议下拉框用 change失去焦点用 blur
}
],
mailing_address: [
{
validator: (rule, value, callback) => {
var formData = this.addAuthor ? this.addFomauthor : this.editFomauthor;
if (formData.is_report == '1') {
if (!value || value.trim() === '') {
// 更加正式的表达:请提供通讯作者的详细信息
return callback(new Error('Please provide the corresponding author details.'));
}
}
// 必须确保所有逻辑路径都能执行到 callback()
callback();
},
trigger: ['blur', 'change'] // 建议加上 change体验更好
}
]
}
};
@@ -1484,7 +1561,6 @@ export default {
this.getHight();
window.addEventListener('resize', this.getHight);
this.getData();
this.getAuthorJG();
this.getCount();
this.getWorldPdf();
@@ -1492,30 +1568,29 @@ export default {
},
methods: {
async generatePDF() {
// 1. 验证逻辑(保持原样)
// 1. 验证逻辑(保持原样)
try {
this.isGenerating = true; // 开启加载
try {
this.isGenerating = true; // 开启加载
// 注意:这里要加 await确保拿到结果后再往下走
const res = await this.$api.post('api/Production/createArticlePdf', {
p_article_id: this.p_article_id
});
// 注意:这里要加 await确保拿到结果后再往下走
const res = await this.$api.post('api/Production/createArticlePdf', {
p_article_id: this.p_article_id
});
if (res.status == 1) {
this.$message.success("The PDF document is being processed. Please wait and refresh the page in one minute.");
} else {
this.$message.error("Generation failed, please try again.");
}
} catch (error) {
console.error(error);
this.$message.error("Network error, please try again.");
} finally {
// 只有请求真正完成后,才会执行这里
this.isGenerating = false;
}
},
if (res.status == 1) {
this.$message.success('The PDF document is being processed. Please wait and refresh the page in one minute.');
} else {
this.$message.error('Generation failed, please try again.');
}
} catch (error) {
console.error(error);
this.$message.error('Network error, please try again.');
} finally {
// 只有请求真正完成后,才会执行这里
this.isGenerating = false;
}
},
getArticleFinal(id) {
// api/Finalreview/getRecord
this.$api
@@ -1524,7 +1599,6 @@ export default {
// article_id: 6075
})
.then((res) => {
if (res.status == 1) {
this.finalReview = res.data;
} else {
@@ -1533,17 +1607,13 @@ export default {
});
},
updateChange(type, content) {
this[type] = content;
},
getTinymceContent(type) {
this.$refs.tinymceChild1.getContent(type);
},
getContent(type, content) {
},
getContent(type, content) {},
// 跳转文章详情
showdetaileditor(data) {
this.$router.push({
@@ -1583,45 +1653,39 @@ export default {
}
}
},
getData() {
this.idform.p_article_id = this.p_article_id;
this.detailMes.p_article_id = this.p_article_id;
this.addFomauthor.p_article_id = this.p_article_id;
this.addFomschool.p_article_id = this.p_article_id;
this.UpTypeFile.p_article_id = this.p_article_id;
this.abstract = '';
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.idform.p_article_id = this.p_article_id;
this.detailMes.p_article_id = this.p_article_id;
this.detailMes.is_graphical_abstract = 3;
this.addFomauthor.p_article_id = this.p_article_id;
this.addFomschool.p_article_id = this.p_article_id;
this.UpTypeFile.p_article_id = this.p_article_id;
this.abstract = null;
this.tradition = null;
this.mhooStr = null;
// 获取文章信息
this.$api
.post('api/Production/getProductionDetail', this.idform)
.then((res) => {
if (res.code == 0) {
if (res.code == 0) {
this.abstract = res.data.production.abstract ? res.data.production.abstract : '';
this.tradition = res.data.production.tradition ? res.data.production.tradition : '';
this.mhooStr = res.data.production.mhoo ? res.data.production.mhoo : '';
this.detailMes = res.data.production;
this.abstract = res.data.production.abstract?res.data.production.abstract:'';
this.tradition = res.data.production.tradition?res.data.production.tradition:'';
this.mhooStr = res.data.production.mhoo?res.data.production.mhoo:'';
if (this.abstract != '') {
this.$nextTick(() => {
this.$refs.tinymceChild1.setContent(this.abstract);
});
}
if (this.tradition != '') {
this.$nextTick(() => {
this.$refs.tinymceChild2.setContent(this.tradition);
});
}
if (this.mhooStr != '') {
this.$nextTick(() => {
this.$refs.tinymceChild3.setContent(this.mhooStr);
});
}
this.UpTyFIle = res.data.production.file_pdf;
this.getArticleFinal(res.data.production.article_id);
if (res.data.production.icon != '') {
@@ -1785,7 +1849,8 @@ export default {
this.$message.error(err);
});
},
refreshPdfList(){ const loading = this.$loading({
refreshPdfList() {
const loading = this.$loading({
lock: true,
text: 'Loading...',
spinner: 'el-icon-loading',
@@ -1800,22 +1865,19 @@ export default {
},
getPdfList() {
// 可以下载的word列表
this.$api
.post('/api/Production/getProductionArticlePdf', {
p_article_id: this.p_article_id
})
.then((res) => {
if (res.status == 1) {
this.PDFfileList = res.data;
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
this.$message.error(err);
});
},
@@ -1944,7 +2006,7 @@ export default {
return e.stage_year + ' Vol.' + e.stage_vol + ' issue.' + e.stage_no + e.stage_pagename + e.stage_page;
},
async abstractFormat(content) {
content = this.$commonJS.transformHtmlString(content,'content',{keepBr:true});
content = this.$commonJS.transformHtmlString(content, 'content', { keepBr: true });
var div = document.createElement('div');
div.innerHTML = content;
var imgTags = div.getElementsByTagName('img');
@@ -1969,41 +2031,46 @@ export default {
}
// 处理 p 标签 + 解码
content = div.innerHTML;
content = content.replace(/^<p>\s*(.*?)\s*<\/p>$/, '$1').trim();
content = await this.$commonJS.decodeHtml(content);
content = content.replace(/&nbsp;|\u00A0/g, ' ');
return content;
},
// 1----保存稿件信息
async ZsSaveMes() {
var abstractStr = '';
var mhooStr = '';
var traditionStr = '';
if (this.detailMes.journal_stage_id == 0) {
this.$message.error('Please select an installment!');
return;
} if (this.tradition!='') {
}
if (this.tradition != '') {
traditionStr = await this.abstractFormat(this.tradition);
}
if (this.mhooStr!='') {
}
if (this.mhooStr != '') {
mhooStr = await this.abstractFormat(this.mhooStr);
}
if (this.abstract == '') {
this.$message.error('Please input abstract!');
return false;
} else {
abstractStr = await this.abstractFormat(this.abstract);
}
this.$refs.Mes_Form.validate((valid) => {
if (valid) {
this.$api
.post('api/Production/editProduction', { ...this.detailMes, abstract: abstractStr, mhoo: mhooStr, tradition: traditionStr })
.post('api/Production/editProduction', {
...this.detailMes,
abstract: abstractStr,
mhoo: mhooStr,
tradition: traditionStr
})
.then((res) => {
if (res.code == 0) {
this.$message.success(`Successfully save the article!`);
@@ -2025,6 +2092,10 @@ export default {
// 2----添加作者操作
add_Authorclick(index, row) {
if (this.schoolData.length == 0) {
this.$message.error('Please provide at least one institutional affiliation.');
return;
}
this.aid = index;
this.addFomauthor = {
is_first: '0',
@@ -2035,7 +2106,6 @@ export default {
this.addAuthor = true;
},
authorAdd(addFomauthor) {
console.log(this.addFomauthor);
this.$refs.add_Author.validate((valid) => {
if (valid) {
this.$api
@@ -2982,7 +3052,6 @@ export default {
// 获取文章信息
this.$api
.post('api/Preaccept/getArticleMains', {
article_id: this.detailMes.article_id
})
.then((res) => {
@@ -3171,35 +3240,43 @@ export default {
min-height: 60px;
}
.left-panel {
flex: 1; /* 左侧占据剩余空间 */
margin-right: 40px;
border-right: 1px dashed #eee; /* 加一条虚线分隔线 */
padding-right: 20px;
flex: 1; /* 左侧占据剩余空间 */
margin-right: 40px;
border-right: 1px dashed #eee; /* 加一条虚线分隔线 */
padding-right: 20px;
}
.right-panel {
width: 48%; /* 右侧固定宽度 */
width: 48%; /* 右侧固定宽度 */
}
.right-panel h5 {
font-weight: normal;
}
.console-card {
background: #f9f9f9;
border-radius: 8px;
padding: 20px;
border: 1px solid #ebeef5;
background: #f9f9f9;
border-radius: 8px;
padding: 20px;
border: 1px solid #ebeef5;
}
.load_pdf {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 10px;
transition: background 0.3s;
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 10px;
transition: background 0.3s;
}
.load_pdf:hover {
background: #f5f7fa;
background: #f5f7fa;
}
.loadTinymceBox {
height: 160px; line-height: 160px; text-align: center; border: 2px solid #eee;
border-radius: 10px;
color: #c0c4cc !important;
}
.loadTinymceBox *{
color: #c0c4cc !important;
}
</style>
<style>

View File

@@ -19,7 +19,7 @@
</p>
<!-- <p style="color: #7f8790;margin: 10px 0 0 0;">Follow the progress of your submission.</p> -->
<div class="tit_head">
Status : <b style="margin: 0 50px 0 0">{{ statetostr(artMes.laststate) }}</b> Journal : <b>{{ artMes.journalname }}</b>
Status : <b style="margin: 0 50px 0 0">{{ artMes.is_draft==1&&(artMes.laststate==3||artMes.laststate==-1) ? $t('artstate.stateDraft') : statetostr(artMes.laststate)}}</b><span v-if="artMes.journalname">Journal : <b>{{ artMes.journalname }}</b></span>
</div>
<div class="tit_head" v-if="artMes.majors && artMes.majors.length > 0">
<b style="margin: 0 50px 0 0; font-size: 20px">Research areas</b>

View File

@@ -441,7 +441,7 @@ export default {
},
async created() {
this.isCollapse = localStorage.getItem('isCollapse') == 'true' ? true : false;
console.log('localStorage.getItem', typeof localStorage.getItem('isCollapse'));
this.isShowEditComment();
this.isFresh = false;
this.$nextTick(async () => {

View File

@@ -1,7 +1,5 @@
<template>
<div class="tinymce-container editor-container">
<textarea class="tinymce-textarea" :id="tinymceId"></textarea>
</div>
</template>
@@ -125,21 +123,23 @@ export default {
formLabelWidth: '120px',
hasChange: false,
hasInit: false,
editorInstance: null,
tinymceId: this.id || 'vue-tinymce-' + +new Date()
};
},
watch: {
value(val) {
// console.log('val at line 208:', val);
value: {
handler(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() => {
window.tinymce.get(this.tinymceId).setContent(val);
// window.renderMathJax(); // 主动触发 MathJax 渲染
});
}
},
immediate: true
}
},
mounted() {
@@ -163,13 +163,31 @@ export default {
this.destroyTinymce();
},
methods: {
handleSetContent(val) {
if (!this.editorInstance) return;
let finalContent = val || '';
// 你的业务逻辑:自动包裹 <p> 标签
if (!finalContent.includes('wordTableHtml') && !finalContent.startsWith('<p>')) {
finalContent = '<p>' + finalContent + '</p>';
}
this.editorInstance.setContent(finalContent);
// 渲染数学公式
this.$nextTick(() => {
if (window.renderMathJax) {
window.renderMathJax(this.tinymceId);
}
});
},
onClear() {
if (this.uploadNotificationInstance) {
this.uploadNotificationInstance.close(); this.uploadNotificationInstance = null;
this.uploadedImageCount = 0;
this.totalUploadImages = 0;}
this.uploadNotificationInstance.close();
this.uploadNotificationInstance = null;
this.uploadedImageCount = 0;
this.totalUploadImages = 0;
}
},
updateUploadProgressNotification(imgIndex, status = 'processing', message = '') {
// 快捷调用 $t
@@ -184,7 +202,7 @@ export default {
<p id="total-status-text" style="margin: 0 0 12px 0; font-size: 14px; color: #606266; font-weight: 500;">
${t('preparing', { total: this.totalUploadImages })}
</p>
<div class="el-progress el-progress--line" style="margin-bottom: 15px;">
<div class="el-progress-bar">
<div class="el-progress-bar__outer" style="height: 10px; background: #ebeef5; border-radius: 5px;">
@@ -351,46 +369,46 @@ export default {
}
},
// 按照你要求的 XMLHttpRequest 格式编写
// 直接接收已经转好的 blob
uploadSingleImage(blob, index) {
const _this = this;
const xhr = new XMLHttpRequest();
const formData = new FormData();
// 直接把传进来的 blob 丢进表单
formData.append('file_name', blob, `word_img_${index}.png`);
formData.append('article_id', this.articleId);
// 直接接收已经转好的 blob
uploadSingleImage(blob, index) {
const _this = this;
const xhr = new XMLHttpRequest();
const formData = new FormData();
xhr.withCredentials = false;
xhr.open('POST', _this.baseUrl + 'api/Articlemain/uploadTableImage');
xhr.onload = function () {
if (xhr.status !== 200) {
_this.removePlaceholder(index); // 失败清理
return;
}
try {
const json = JSON.parse(xhr.responseText);
if (json.status == 1) {
const finalUrl = _this.mediaUrl + 'articleTableImage/' + json.data;
const doc = tinymce.activeEditor.getDoc();
const placeholder = doc.querySelector(`img[data-idx="${index}"]`);
if (placeholder) {
placeholder.src = finalUrl;
placeholder.removeAttribute('data-idx');
// 直接把传进来的 blob 丢进表单
formData.append('file_name', blob, `word_img_${index}.png`);
formData.append('article_id', this.articleId);
xhr.withCredentials = false;
xhr.open('POST', _this.baseUrl + 'api/Articlemain/uploadTableImage');
xhr.onload = function () {
if (xhr.status !== 200) {
_this.removePlaceholder(index); // 失败清理
return;
}
} else {
_this.removePlaceholder(index);
}
} catch (e) {
console.error('解析响应失败', e);
_this.removePlaceholder(index);
}
};
xhr.onerror = () => _this.removePlaceholder(index);
xhr.send(formData);
},
try {
const json = JSON.parse(xhr.responseText);
if (json.status == 1) {
const finalUrl = _this.mediaUrl + 'articleTableImage/' + json.data;
const doc = tinymce.activeEditor.getDoc();
const placeholder = doc.querySelector(`img[data-idx="${index}"]`);
if (placeholder) {
placeholder.src = finalUrl;
placeholder.removeAttribute('data-idx');
}
} else {
_this.removePlaceholder(index);
}
} catch (e) {
console.error('解析响应失败', e);
_this.removePlaceholder(index);
}
};
xhr.onerror = () => _this.removePlaceholder(index);
xhr.send(formData);
},
removePlaceholder(idx) {
const doc = tinymce.activeEditor.getDoc();
@@ -412,35 +430,71 @@ uploadSingleImage(blob, index) {
}
return new Blob([u8arr], { type: mime });
},
formatHtml(val) {
const rawValue = val || ''; // 处理 null
const cleanEmptyTags = /<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g;
const replaceSpaces = /\s+(?=<)|(?<=>)\s+/g;
const removeBr = /<br\s*\/?>/gi; // 移除所有 br 标签
if (rawValue.includes('wordTableHtml')) {
const parser = new DOMParser();
const doc = parser.parseFromString(rawValue, 'text/html');
const cells = doc.querySelectorAll('td, th');
cells.forEach((cell) => {
cell.innerHTML = cell.innerHTML
.replace(cleanEmptyTags, '')
.replace(removeBr, '') // 针对你“不想要br”的需求
.replace(replaceSpaces, '&nbsp;');
});
return doc.body.innerHTML;
} else {
return rawValue
.replace(cleanEmptyTags, '')
.replace(removeBr, '')
.replace(replaceSpaces, '&nbsp;');
}
},
getSafeContent(val) {
const rawValue = val || '';
const cleanEmptyTags = /<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g;
const replaceSpaces = /\s+(?=<)|(?<=>)\s+/g;
const escapeIllegalLT = (str) => {
return str.replace(/<(?!(\/?(p|div|span|table|tr|td|th|b|i|strong|em|ul|ol|li|br|img)))/gi, '&lt;');
};
let processedHtml = '';
if (rawValue.includes('wordTableHtml')) {
const parser = new DOMParser();
const doc = parser.parseFromString(rawValue, 'text/html');
const cells = doc.querySelectorAll('td, th');
cells.forEach((cell) => {
// 1. 先把单元格内的非法 < 转义
let cellText = cell.innerHTML;
// let cellText = escapeIllegalLT(cell.innerHTML);
// 2. 再清理空标签和多余空格
cell.innerHTML = cellText
.replace(cleanEmptyTags, '')
.replace(replaceSpaces, '&nbsp;');
});
processedHtml = doc.body.innerHTML;
} else {
// 非表格逻辑也同样处理转义
// processedHtml = escapeIllegalLT(rawValue)
processedHtml = rawValue
.replace(cleanEmptyTags, '')
.replace(replaceSpaces, '&nbsp;');
}
return processedHtml;
},
initTinymce() {
if (this.value.includes('wordTableHtml')) {
const parser = new DOMParser();
const doc = parser.parseFromString(this.value, 'text/html');
// 1. 只针对单元格 (td, th) 进行处理
const cells = doc.querySelectorAll('td, th');
cells.forEach(cell => {
let html = cell.innerHTML;
// 执行你原来的清理逻辑,但仅限单元格内部
html = html.replace(/<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g, '') // 清理空标签
.replace(/\s+(?=<)|(?<=>)\s+/g, '&nbsp;'); // 补全空格
cell.innerHTML = html;
});
// 2. 将处理后的完整 HTML 重新赋回,此时 table 外部的换行符保持原样(纯文本)
this.content = doc.body.innerHTML;
} else {
// 非表格模式,维持你原有的全局逻辑
this.content = this.value
.replace(/<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g, '')
.replace(/\s+(?=<)|(?<=>)\s+/g, '&nbsp;');
}
let globalImgCounter = 0; // 放在 init 闭包内,确保计数器持续增长
let currentPasteBase64Images = [];
var _this = this;
window.tinymce.init({
..._this.tinymceOtherInit,
@@ -470,37 +524,37 @@ uploadSingleImage(blob, index) {
blue{
display: inline;
}
myfigure,
mytable {
pointer-events: auto !important; /* 强制允许鼠标点击 */
display: inline-block;
cursor: pointer;
color: rgb(0, 130, 170) !important;
text-shadow: 0 0 3px #09c2fb, 0 0 4px rgba(0, 130, 170, 0.3);
}
myfigure,
mytable {
pointer-events: auto !important; /* 强制允许鼠标点击 */
display: inline-block;
cursor: pointer;
color: rgb(0, 130, 170) !important;
text-shadow: 0 0 3px #09c2fb, 0 0 4px rgba(0, 130, 170, 0.3);
}
myfigure *,
mytable * {
color: inherit !important;
text-shadow: inherit !important;
background: transparent !important; /* 防止内部标签背景干扰 */
}
myh3 ,myh3 *{
font-weight: bold !important;
}
myfigure *,
mytable * {
color: inherit !important;
text-shadow: inherit !important;
background: transparent !important; /* 防止内部标签背景干扰 */
}
myh3 ,myh3 *{
font-weight: bold !important;
}
@keyframes blueGlow {
0%,
100% {
transform: scale(1);
opacity: 0.9;
}
50% {
transform: scale(1.02);
opacity: 1;
}
}
`,
@keyframes blueGlow {
0%,
100% {
transform: scale(1);
opacity: 0.9;
}
50% {
transform: scale(1.02);
opacity: 1;
}
}
`,
formats: {
bold: { inline: 'b' },
italic: { inline: 'i' }
@@ -575,42 +629,39 @@ font-weight: bold !important;
_this.$commonJS.initEditorButton(_this, ed);
var currentWmathElement = null;
ed.on('click', function (e) {
const wmathElement = e.target.closest('wmath');
if (wmathElement) {
currentWmathElement = wmathElement; // 保存当前点击的元素
// 1. 提取内容:获取 LaTeX
const latexContentRaw = wmathElement.getAttribute('data-latex') || '';
const latexContent = latexContentRaw.replace(/\$/g, '').trim();
const encoded = encodeURIComponent(latexContent);
const wmathElement = e.target.closest('wmath');
if (wmathElement) {
currentWmathElement = wmathElement; // 保存当前点击的元素
// 2. 提取状态:获取之前的 wrap 模式 👈 重要新增
const wrapMode = wmathElement.getAttribute('data-wrap') || 'block';
// 1. 提取内容:获取 LaTeX
const latexContentRaw = wmathElement.getAttribute('data-latex') || '';
const latexContent = latexContentRaw.replace(/\$/g, '').trim();
const encoded = encodeURIComponent(latexContent);
// 3. 处理唯一 ID
let wmathId = wmathElement.getAttribute('data-id');
if (!wmathId) {
wmathId = 'wmath-' + Math.random().toString(36).substr(2, 9);
wmathElement.setAttribute('data-id', wmathId);
}
// 2. 提取状态:获取之前的 wrap 模式 👈 重要新增
const wrapMode = wmathElement.getAttribute('data-wrap') || 'block';
const editorId = ed.id;
// 3. 处理唯一 ID
let wmathId = wmathElement.getAttribute('data-id');
if (!wmathId) {
wmathId = 'wmath-' + Math.random().toString(36).substr(2, 9);
wmathElement.setAttribute('data-id', wmathId);
}
// 4. 打开窗口:在 URL 参数中增加 wrap 模式 👈 这样弹窗就知道该默认选哪个了
window.open(
`/LateX?id=${encoded}&wmathId=${wmathId}&editorId=${editorId}&wrap=${wrapMode}`,
'_blank',
'width=1000,height=800,scrollbars=no,resizable=no'
);
}
});
const editorId = ed.id;
ed.on('paste', async (event) => {
// 4. 打开窗口:在 URL 参数中增加 wrap 模式 👈 这样弹窗就知道该默认选哪个了
window.open(
`/LateX?id=${encoded}&wmathId=${wmathId}&editorId=${editorId}&wrap=${wrapMode}`,
'_blank',
'width=1000,height=800,scrollbars=no,resizable=no'
);
}
});
ed.on('paste', async (event) => {
const rtf = event.clipboardData.getData('text/rtf');
if (rtf && rtf.includes('\\pict')) {
const extracted = extractHexImagesFromRTF(rtf);
_this.totalUploadImages = extracted.length; // 设置总数
_this.uploadedImageCount = 0; // 重置已上传数
@@ -630,22 +681,21 @@ font-weight: bold !important;
return; // 跳过此图片
}
try {
const imageBlob = _this.hexToBlob(img.hex, img.mimeType);
_this.updateUploadProgressNotification(i, 'uploading');
const imageBlob = _this.hexToBlob(img.hex, img.mimeType);
// 2. 【核心优化】直接上传这个 Blob 文件
// uploadSingleImage 内部需要改用 FormData 发送
await _this.uploadSingleImage(imageBlob, i);
_this.updateUploadProgressNotification(i, 'success');
} catch (err) {
_this.updateUploadProgressNotification(i, 'uploading');
// 2. 【核心优化】直接上传这个 Blob 文件
// uploadSingleImage 内部需要改用 FormData 发送
await _this.uploadSingleImage(imageBlob, i);
_this.updateUploadProgressNotification(i, 'success');
} catch (err) {
console.error('Upload failed:', err);
_this.updateUploadProgressNotification(i, 'fail', err.message || 'Network error'); // 上传失败
}
});
await Promise.all(uploadPromises);
// Promise.all 完成后, updateUploadProgressNotification 会自动计算 totalCount === uploadedCount 并关闭
}
@@ -676,36 +726,40 @@ font-weight: bold !important;
}
});
ed.on('init', function () {
_this.$commonJS.inTinymceButtonClass();
const editorBody = ed.getBody();
_this.editorInstance = ed;
_this.hasInit = true;
_this.$commonJS.inTinymceButtonClass();
if (_this.isAutomaticUpdate) {
_this.$emit('updateChange', _this.value);
const observer = new MutationObserver(() => {
const currentContent = ed.getContent({ format: 'raw' });
if (_this.isAutomaticUpdate) {
_this.$emit('updateChange', currentContent);
// _this.$emit('updateChange', _this.$commonJS.decodeHtml(currentContent));
//暂时注释掉数字公式
// _this.$commonJS.replaceWMathContent(currentContent, (res) => {
// console.log('res at line 451:', res);
// _this.$emit('updateChange', res);
// });
}
});
// 监听子节点和内容的变化
observer.observe(editorBody, { childList: true, subtree: true, characterData: true });
});
// // 定义自定义按钮
// ed.ui.registry.addButton('clearButton', {
// text: 'Empty',
}
_this.content = _this.getSafeContent(_this.value);
// onAction: () => {
// // 插入自定义表格到编辑器中
// ed.setContent('');
// }
// });
_this.handleSetContent(_this.content || '');
// 3. 监听内容变化
ed.on('NodeChange Change KeyUp SetContent', () => {
_this.hasChange = true;
_this.$emit('input', ed.getContent({ format: 'raw' }));
});
// 4. 监听 DOM 变化
const observer = new MutationObserver(() => {
const currentContent = ed.getContent({ format: 'raw' });
if (_this.isAutomaticUpdate) {
_this.$emit('updateChange', currentContent);
}
});
observer.observe(ed.getBody(), {
childList: true,
subtree: true,
characterData: true
});
});
// 定义自定义按钮
ed.ui.registry.addButton('customButtonExportWord', {
@@ -726,7 +780,7 @@ font-weight: bold !important;
text: _this.$t('commonTable.exportImg'),
onAction: () => {
// 插入自定义表格到编辑器中
_this.export('image', ed.getContent({ format: 'raw' }));
_this.export('image', ed.getContent({ format: 'raw' }));
}
});
ed.ui.registry.addContextToolbar('spacer', {
@@ -740,10 +794,9 @@ font-weight: bold !important;
ed.dom.select('wmath', editorBody).forEach(function (wmathElement) {
ed.dom.setAttrib(wmathElement, 'contenteditable', 'false');
// ed.dom.addClass(wmathElement, 'non-editable-wmath');
});
});
e.content = e.content.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>');
e.content = e.content.replace(/<em>/g, '<i>').replace(/<\/em>/g, '</i>');
});
ed.on('GetContent', function (e) {
e.content = e.content.replace(/<b>/g, '<strong>').replace(/<\/b>/g, '</strong>');
@@ -763,7 +816,6 @@ font-weight: bold !important;
tempDiv.innerHTML = content;
if (tempDiv.querySelector('table')) {
if (_this.type == 'table') {
// 3. 在这里直接消费外部变量 currentPasteBase64Images
// content = content.replace(new RegExp(`src="${silentPlaceholder}"`, 'gi'), () => {
@@ -804,40 +856,29 @@ font-weight: bold !important;
args.content = container.innerHTML; // 更新处理后的内容
});
} else {
// _this.$confirm('检测到粘贴内容包含表格,是否需要以表格形式添加?', '提示', {
// confirmButtonText: '添加表格',
// cancelButtonText: '纯文本添加',
// type: 'info'
// }).then(() => {
// _this.$emit('openAddTable', content);
// return false
// }).catch(() => {
// });
// _this.$confirm('检测到粘贴内容包含表格,是否需要以表格形式添加?', '提示', {
// confirmButtonText: '添加表格',
// cancelButtonText: '纯文本添加',
// type: 'info'
// }).then(() => {
// _this.$emit('openAddTable', content);
// return false
// }).catch(() => {
// });
}
} else {
console.log('Original content:', content); // 输出原始粘贴内容
const mathRegex = /\$\$([\s\S]+?)\$\$|\$([\s\S]+?)\$/g;
content = content.replace(mathRegex, function (match, blockFormula, inlineFormula) {
const formula = blockFormula || inlineFormula;
const mode = blockFormula ? 'block' : 'inline';
console.log(`Matched ${mode} formula:`, formula);
// 1. 修改正则:同时匹配 $$...$$ (块级) 和 $...$ (行内)
// 注意:先匹配双美元符,再匹配单美元符,防止冲突
const mathRegex = /\$\$([\s\S]+?)\$\$|\$([\s\S]+?)\$/g;
return `<wmath data-wrap="${mode}" data-latex="${formula.trim()}">${formula.trim()}</wmath>`;
});
content = content.replace(mathRegex, function (match, blockFormula, inlineFormula) {
// 判断是块级还是行内
const formula = blockFormula || inlineFormula;
const mode = blockFormula ? 'block' : 'inline';
console.log(`Matched ${mode} formula:`, formula);
// 2. 统一改造:标签内只放纯 formula不带 $ 符号
// 属性中保存 data-latex 和 data-wrap
return `<wmath data-wrap="${mode}" data-latex="${formula.trim()}">${formula.trim()}</wmath>`;
});
console.log('Processed content:', content); // 输出处理后的内容
}
// 更新 args.content 为处理后的内容
// 阻止默认的粘贴行为,确保自定义处理优先执行
if (args.event) {
args.event.preventDefault();
args.event.stopPropagation();
@@ -853,111 +894,79 @@ console.log('Processed content:', content); // 输出处理后的内容
}, 10);
},
clear_custom_action: (editor, vm) => {
vm.onClear();
},
init_instance_callback: (editor) => {
if (_this.content) {
let finalContent = _this.content;
if (finalContent.includes('wordTableHtml')) {
editor.setContent(finalContent);
} else {
editor.setContent('<p>' + finalContent + '</p>');
}
setTimeout(() => {
if (window.renderMathJax) {
window.renderMathJax(_this.tinymceId);
}
}, 10);
}
_this.hasInit = true;
editor.on('NodeChange Change KeyUp SetContent', () => {
this.hasChange = true;
this.$emit('input', editor.getContent({ format: 'raw' }));
});
}
// init_instance_callback: (editor) => {
// }
});
if (!window._wmath_listener_registered) {
// 💾 新增公式插入点记录
let latexEditorBookmark = null;
let activeEditorId = null;
// 👂 message 监听器:处理编辑 + 新增两种情况
window.addEventListener('message', function (event) {
const data = event.data;
if (data && (data.type === 'update-wmath' || data.type === 'insert-wmath')) {
const { editorId, wmathId, latex } = data;
const newLatex = latex ? latex.trim() : '';
if (!editorId) return;
const data = event.data;
const targetEditor = tinymce.get(editorId);
if (!targetEditor) return;
if (data && (data.type === 'update-wmath' || data.type === 'insert-wmath')) {
const { editorId, wmathId, latex } = data;
const newLatex = latex ? latex.trim() : '';
if (!editorId) return;
// 尝试寻找现有标签
const targetWmath = wmathId ? targetEditor.dom.select(`wmath[data-id="${wmathId}"]`, targetEditor.getBody())[0] : null;
const targetEditor = tinymce.get(editorId);
if (!targetEditor) return;
if (targetWmath) {
// --- 原有的更新/删除逻辑 ---
if (!newLatex) {
targetEditor.dom.remove(targetWmath);
} else {
targetWmath.setAttribute('data-latex', newLatex);
if (data.wrapMode === 'inline') {
targetWmath.setAttribute('data-wrap', 'inline');
} else {
targetWmath.setAttribute('data-wrap', 'block');
}
targetWmath.innerHTML = newLatex;
}
} else if (newLatex) {
// --- ✨ 新增逻辑:如果找不到现有标签且有 latex 内容 ---
// 生成一个新的唯一 ID (如果你后端或插件没给的话)
const newId = wmathId || 'wmath_' + Date.now();
// 构建新的 wmath 标签字符串
// 这里你可以根据之前选的 wrapMode (块级/行内) 来决定样式
const htmlToInsert = `<wmath data-id="${newId}" data-latex="${newLatex}" data-wrap="${data.wrapMode === 'inline' ? 'inline' : 'block'}">${newLatex}</wmath>`;
// 在当前光标位置插入内容
targetEditor.insertContent(htmlToInsert);
}
const targetWmath = wmathId
? targetEditor.dom.select(`wmath[data-id="${wmathId}"]`, targetEditor.getBody())[0]
: null;
// 统一渲染
setTimeout(() => {
if (typeof renderMathJax === 'function') {
renderMathJax(editorId);
}
}, 10);
}
});
if (targetWmath) {
if (!newLatex) {
targetEditor.dom.remove(targetWmath);
} else {
targetWmath.setAttribute('data-latex', newLatex);
if (data.wrapMode === 'inline') {
targetWmath.setAttribute('data-wrap', 'inline');
} else {
targetWmath.setAttribute('data-wrap', 'block');
}
targetWmath.innerHTML = newLatex;
}
} else if (newLatex) {
const newId = wmathId || 'wmath_' + Date.now();
const htmlToInsert = `<wmath data-id="${newId}" data-latex="${newLatex}" data-wrap="${
data.wrapMode === 'inline' ? 'inline' : 'block'
}">${newLatex}</wmath>`;
targetEditor.insertContent(htmlToInsert);
}
// 统一渲染
setTimeout(() => {
if (typeof renderMathJax === 'function') {
renderMathJax(editorId);
}
}, 10);
}
});
// 🚩 标记为已注册,防止重复
window._wmath_listener_registered = true;
// 导出保存位置函数(你可以在按钮点击时调用它)
window._recordLatexInsertContext = function (editorInstance) {
latexEditorBookmark = editorInstance.selection.getBookmark(2);
activeEditorId = editorInstance.id;
};
}
},
hexToBlob(hex, mimeType) {
const len = hex.length / 2;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return new Blob([bytes], { type: mimeType });
},
const len = hex.length / 2;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return new Blob([bytes], { type: mimeType });
},
updateTableStyles(container) {
var html = this.$commonJS.updateTableStyles(container, this.typesettingType);
var editor = window.tinymce.activeEditor; // 将外部 DOM 内容更新到编辑器
@@ -967,11 +976,11 @@ console.log('Processed content:', content); // 输出处理后的内容
editor.focus(); // 聚焦到编辑器// 触发编辑器内容变化后,如果需要,可能还要设置编辑器的样式
},
//销毁富文本
destroyTinymce() {this.onClear();
destroyTinymce() {
this.onClear();
if (window.tinymce.get(this.tinymceId)) {
window.tinymce.get(this.tinymceId).destroy();
}
},
//设置内容
setContent(value) {
@@ -980,13 +989,12 @@ console.log('Processed content:', content); // 输出处理后的内容
//获取内容
async getContent(type) {
var content = window.tinymce.get(this.tinymceId).getContent({ format: 'raw' });
content = content.replace(/<span[^>]*>/g, '').replace(/<\/span>/g, ''); // 去除span标签
content = content.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>');
content = content.replace(/<em>/g, '<i>').replace(/<\/em>/g, '</i>');
content = content.replace(/&nbsp;/g, ' '); // 将所有 &nbsp; 替换为空格
this.$emit('getContent', type, content);
},

View File

@@ -365,6 +365,7 @@
:id="'editor' + item.am_id"
v-html="highlightText({ am_id: item.am_id, text: item.content }, item.checks ? item.checks : [], item.type)"
></div>
</div>
</div>
</template>
@@ -3021,7 +3022,10 @@ export default {
text = this.highlightText3(item.text, [], type, item.am_id);
}
}
return text;
// 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;');
return text;
},
// 改造版DOM 安全高亮
// 仅保留 <wmath>…</wmath> 内部,其他用正则匹配高亮
@@ -3303,6 +3307,8 @@ export default {
html += `<img contenteditable="false" src="${this.imagePath || ''}" alt="" style="width:20px;height:20px;opacity:.6;">`;
}
return html;
},
getProofreadingList(arr) {

View File

@@ -930,7 +930,7 @@ if(newData.ami_id) {
},
async created() {
this.isCollapse = localStorage.getItem('isCollapse') == 'true' ? true : false;
console.log('localStorage.getItem', typeof localStorage.getItem('isCollapse'));
this.isShowEditComment();
this.isFresh = false;
this.$nextTick(async () => {

View File

@@ -361,7 +361,7 @@ export default {
this.add_apply = 0;
break;
case 7||16:
case 7:
// 7文章状态不在审稿中
this.add_apply = 1;
break;
@@ -369,7 +369,8 @@ export default {
//拒绝审稿
this.add_apply = -1;
break;
case 13:
case 13:
case 16:
//13 邀请审稿超过5天未同意邀请16同意审稿后14天未进行审稿
this.add_apply = 2;
break;

View File

@@ -71,8 +71,8 @@ module.exports = {
proxy: {
'/api': {
// target: 'http://zmzm.tougao.dev.com/',//晓玲本地
// target: 'https://submission.tmrjournals.com/',//正式
target: 'http://tougaotest.tmrjournals.com/public/index.php/',//测试环境
target: 'https://submission.tmrjournals.com/',//正式
// target: 'http://tougaotest.tmrjournals.com/public/index.php/',//测试环境
// target: 'http://mytest.tmrjournals.com/public/index.php/',//新测试环境
changeOrigin: true,
pathRewrite: {