This commit is contained in:
2025-10-27 14:48:16 +08:00
parent db00f22155
commit 5d4339905e
6 changed files with 738 additions and 544 deletions

View File

@@ -114,99 +114,7 @@ export default {
handleFileUpload(event, callback) {
const file = event.target.files[0];
if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
alert('Please upload a valid Word file !');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const arrayBuffer = e.target.result;
const zip = new JSZip();
zip.loadAsync(arrayBuffer).then(async (zip) => {
const relsXml = await zip.files['word/_rels/document.xml.rels'].async('string');
const docXml = await zip.files['word/document.xml'].async('string');
const parser = new DOMParser();
const relDoc = parser.parseFromString(relsXml, "text/xml");
const docDom = parser.parseFromString(docXml, "text/xml");
const rels = {};
Array.from(relDoc.getElementsByTagName('Relationship')).forEach((rel) => {
const id = rel.getAttribute('Id');
const target = rel.getAttribute('Target');
rels[id] = target;
});
const imageInfoMap = {};
const blips = docDom.getElementsByTagName('a:blip');
Array.from(blips).forEach((blip) => {
const embedId = blip.getAttribute('r:embed');
const extent = findExtentElement(blip);
if (embedId && extent) {
const cx = extent.getAttribute('cx');
const cy = extent.getAttribute('cy');
if (cx && cy) {
const width = emuToPixels(cx);
const height = emuToPixels(cy);
imageInfoMap[embedId] = { width, height };
}
}
});
mammoth.convertToHtml({ arrayBuffer }, {
convertImage: mammoth.images.inline(async function (image) {
console.log('image at line 163:', image)
const contentType = image.contentType.toLowerCase();
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
if (!allowedTypes.includes(contentType)) {
return { src: '' };
}
const embedId = image.relationshipId || image.refId || '';
const imageBuffer = await image.read("base64");
const base64Src = `data:${contentType};base64,${imageBuffer}`;
let width = '', height = '';
if (embedId && imageInfoMap[embedId]) {
width = imageInfoMap[embedId].width;
height = imageInfoMap[embedId].height;
}
return {
src: base64Src,
alt: '',
width,
height,
refId: embedId,
'content-type': contentType
};
})
}).then((result) => {
let html = result.value;
// 提取合法表格
const tableContent = html.match(/<table[\s\S]*?<\/table>/g);
const validTables = tableContent
? tableContent.filter(table => /<td[\s\S]*?>/.test(table))
: [];
callback(validTables);
}).catch(err => {
console.error('mammoth 转换失败:', err);
});
}).catch(err => {
console.error("Zip 读取失败:", err);
});
};
reader.readAsArrayBuffer(file);
},
extractLatexFromMathJax() {
@@ -350,7 +258,6 @@ export default {
// 创建临时 DOM 容器
const tempDiv = document.createElement("div");
tempDiv.innerHTML = pastedHtml; // 插入粘贴的 HTML 内容
// 获取表格
const tables = tempDiv.querySelectorAll("table");
if (tables.length === 0) {
@@ -358,31 +265,23 @@ export default {
callback([]);
return;
}
const allTables = []; // 存储所有表格的二维数组
for (const table of tables) {
const rows = table.querySelectorAll("tr");
const tableArray = []; // 当前表格的二维数组
// 存储合并单元格信息
const mergeMap = {};
rows.forEach((row, rowIndex) => {
const cells = row.querySelectorAll("td, th");
const rowArray = [];
let colIndex = 0;
cells.forEach((cell) => {
// 跳过被合并的单元格
while (mergeMap[`${rowIndex},${colIndex}`]) {
colIndex++;
}
// 获取单元格内容,如果为空则设置为默认值
let cellText = cell.innerText.trim() || "&nbsp;"; // 处理空值
// 处理样式
if (cell.style.fontWeight === "bold") {
cellText = `<b>${cellText}</b>`;
@@ -400,7 +299,6 @@ export default {
// 检查合并单元格属性
const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
const rowspan = parseInt(cell.getAttribute("rowspan") || "1", 10);
// 保存当前单元格信息
rowArray[colIndex] = {
text: cellText,
@@ -417,17 +315,12 @@ export default {
}
}
}
colIndex++; // 移动到下一列
});
tableArray.push(rowArray); // 添加当前行到表格数组
});
allTables.push(tableArray); // 添加当前表格到所有表格数组
}
callback(allTables); // 返回处理后的数组
} catch (error) {
console.error("解析粘贴内容失败:", error);
@@ -439,18 +332,14 @@ export default {
try {
const Zip = new JSZip();
const zip = await Zip.loadAsync(file);
const documentFile = zip.file("word/document.xml");
if (!documentFile) {
console.error("❌ 找不到 word/document.xml无法解析 Word 文件");
return;
}
const documentXml = await documentFile.async("string");
const parser = new DOMParser();
const documentDoc = parser.parseFromString(documentXml, "application/xml");
const numberingFile = zip.file("word/numbering.xml");
let numberingMap = {};
if (numberingFile) {
@@ -460,45 +349,34 @@ export default {
} else {
console.warn("⚠️ word/numbering.xml 不存在,跳过编号解析");
}
const tables = documentDoc.getElementsByTagNameNS(namespace, "tbl");
const allTables = [];
if (!tables || tables.length === 0) {
console.warn("未找到表格内容,请检查 XML 结构");
return [];
}
for (const table of tables) {
const rows = table.getElementsByTagNameNS(namespace, "tr");
const tableArray = [];
let rowSpanMap = [];
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
const row = rows[rowIndex];
const cells = row.getElementsByTagNameNS(namespace, "tc");
const rowArray = [];
if (!rowSpanMap[rowIndex]) {
rowSpanMap[rowIndex] = [];
}
let cellIndex = 0;
for (let i = 0; i < cells.length; i++) {
while (rowSpanMap[rowIndex][cellIndex]) {
rowArray.push(null);
cellIndex++;
}
const cell = cells[i];
let cellText = "";
const paragraphs = cell.getElementsByTagName("w:p");
const gridSpan = cell.getElementsByTagNameNS(namespace, "gridSpan")[0];
const vMerge = cell.getElementsByTagNameNS(namespace, "vMerge")[0];
var colspan = gridSpan ? parseInt(gridSpan.getAttribute("w:val"), 10) : 1;
var rowspan = 1;
if (vMerge) {
@@ -506,49 +384,39 @@ export default {
rowspan = 1; // 初始化 rowspan
let nextRowIdx = rowIndex + 1;
let maxRowspan = rows.length - rowIndex; // 确保 rowspan 不会超过剩余行数
while (nextRowIdx < rows.length) {
const nextRowCells = rows[nextRowIdx].getElementsByTagNameNS(namespace, "tc");
// console.log(`🔍 检查下一行单元格 at row ${nextRowIdx}, col ${cellIndex}:`, nextRowCells);
if (nextRowCells.length > cellIndex) {
const nextCell = nextRowCells[cellIndex];
if (!nextCell) {
// console.warn(`⚠️ nextCell 未定义 at row ${nextRowIdx}, col ${cellIndex}`);
break;
}
const nextVMerge = nextCell.getElementsByTagNameNS(namespace, "vMerge")[0];
// console.log(`🔍 检查 nextVMerge at row ${nextRowIdx}, col ${cellIndex}:`, nextVMerge);
// **如果 nextVMerge 为空,则不应继续增长 rowspan**
if (!nextVMerge) {
// console.log(`⚠️ nextVMerge 为空 at row ${nextRowIdx}, col ${cellIndex} - 停止扩展`);
break;
}
// **解析 nextVMerge 的值**
const vMergeVal = nextVMerge.getAttribute("w:val");
if (!vMergeVal || vMergeVal === "continue") {
if (rowspan < maxRowspan) { // 限制 rowspan 最大值
rowspan++;
// console.log(`✅ rowspan 扩展到: ${rowspan} (row: ${nextRowIdx}, col: ${cellIndex})`);
nextRowIdx++;
} else {
// console.log(`⛔ 最大 rowspan 限制 ${rowspan},在 row ${nextRowIdx} 停止`);
break;
}
} else if (vMergeVal === "restart") {
// console.log(`⛔ 停止 rowspan 扩展 at row ${nextRowIdx}, 因为 w:val="restart"`);
break;
} else {
// console.log(`⚠️ 未知 w:val="${vMergeVal}" at row ${nextRowIdx},停止合并`);
break;
}
} else {
// console.warn(`⚠️ Row ${nextRowIdx} 没有足够的列 cellIndex ${cellIndex}`);
break;
}
}
@@ -556,8 +424,6 @@ export default {
continue;
}
}
const currentLevelNumbers = {};
for (const paragraph of paragraphs) {
let listPrefix = "";
@@ -571,7 +437,6 @@ export default {
listPrefix = this.getListNumber(numId, ilvl, numberingMap, currentLevelNumbers);
}
}
let paragraphText = listPrefix ? `${listPrefix} ` : "";
const runs = paragraph.getElementsByTagName("w:r");
@@ -580,7 +445,6 @@ export default {
const drawings = run.getElementsByTagName("w:drawing");
for (let d = 0; d < drawings.length; d++) {
const drawing = drawings[d];
// 使用命名空间提取 a:blip
const blips = drawing.getElementsByTagNameNS("http://schemas.openxmlformats.org/drawingml/2006/main", "blip");
for (let b = 0; b < blips.length; b++) {
@@ -592,24 +456,16 @@ export default {
}
}
}
const texts = run.getElementsByTagName("w:t");
for (const text of texts) {
textContent += text.textContent;
}
const rPr = run.getElementsByTagName("w:rPr")[0];
let formattedText = textContent;
if (rPr) {
const bold = rPr.getElementsByTagName("w:b").length > 0;
const italic = rPr.getElementsByTagName("w:i").length > 0;
const vertAlignElement = rPr.getElementsByTagName("w:vertAlign")[0];
if (bold) {
formattedText = `<b>${formattedText}</b>`;
}
@@ -625,42 +481,32 @@ export default {
}
}
}
formattedText = replaceNegativeSign(formattedText);
formattedText = capitalizeFirstLetter(formattedText);
const regex = /\[(\d+(?:\d+)?(?:, ?\d+(?:\d+)?)*)\]/g;
formattedText = formattedText.replace(/<blue>/g, '').replace(/<\/blue>/g, '');
if (regex.test(formattedText)) {
formattedText = formattedText.replace(regex, function (match) {
const content = match.slice(1, match.length - 1);
if (/^\d+$/.test(content) || /, ?/.test(content) || //.test(content)) {
return `<blue>${match}</blue>`;
}
return match;
});
}
paragraphText += formattedText;
}
const breaks = paragraph.getElementsByTagName("w:br");
for (const br of breaks) {
paragraphText += "<br/>";
}
cellText += paragraphText;
}
rowArray.push({
text: cellText,
colspan: colspan,
rowspan: rowspan
});
if (rowspan > 1) {
for (let j = 1; j < rowspan; j++) {
if (!rowSpanMap[rowIndex + j]) {
@@ -669,25 +515,102 @@ export default {
rowSpanMap[rowIndex + j][cellIndex] = true;
}
}
cellIndex++;
}
tableArray.push(rowArray.filter(item => item !== null));
}
allTables.push(tableArray);
}
callback(allTables);
} catch (error) {
console.error("解析 Word 文件失败:", error);
callback([]);
}
},
handleFileUpload(event, callback) {
const file = event.target.files[0];
if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
alert('Please upload a valid Word file !');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const arrayBuffer = e.target.result;
const zip = new JSZip();
zip.loadAsync(arrayBuffer).then(async (zip) => {
const relsXml = await zip.files['word/_rels/document.xml.rels'].async('string');
const docXml = await zip.files['word/document.xml'].async('string');
const parser = new DOMParser();
const relDoc = parser.parseFromString(relsXml, "text/xml");
const docDom = parser.parseFromString(docXml, "text/xml");
const rels = {};
Array.from(relDoc.getElementsByTagName('Relationship')).forEach((rel) => {
const id = rel.getAttribute('Id');
const target = rel.getAttribute('Target');
rels[id] = target;
});
const imageInfoMap = {};
const blips = docDom.getElementsByTagName('a:blip');
Array.from(blips).forEach((blip) => {
const embedId = blip.getAttribute('r:embed');
const extent = findExtentElement(blip);
if (embedId && extent) {
const cx = extent.getAttribute('cx');
const cy = extent.getAttribute('cy');
if (cx && cy) {
const width = emuToPixels(cx);
const height = emuToPixels(cy);
imageInfoMap[embedId] = { width, height };
}
}
});
mammoth.convertToHtml({ arrayBuffer }, {
convertImage: mammoth.images.inline(async function (image) {
console.log('image at line 163:', image)
const contentType = image.contentType.toLowerCase();
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
if (!allowedTypes.includes(contentType)) {
return { src: '' };
}
const embedId = image.relationshipId || image.refId || '';
const imageBuffer = await image.read("base64");
const base64Src = `data:${contentType};base64,${imageBuffer}`;
let width = '', height = '';
if (embedId && imageInfoMap[embedId]) {
width = imageInfoMap[embedId].width;
height = imageInfoMap[embedId].height;
}
return {
src: base64Src,
alt: '',
width,
height,
refId: embedId,
'content-type': contentType
};
})
}).then((result) => {
let html = result.value;
// 提取合法表格
const tableContent = html.match(/<table[\s\S]*?<\/table>/g);
const validTables = tableContent
? tableContent.filter(table => /<td[\s\S]*?>/.test(table))
: [];
callback(validTables);
}).catch(err => {
console.error('mammoth 转换失败:', err);
});
}).catch(err => {
console.error("Zip 读取失败:", err);
});
};
reader.readAsArrayBuffer(file);
},
// async extractWordTablesToArrays(file, callback) {
// const namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";

View File

@@ -3,25 +3,44 @@
<div style="display: flex; justify-content: space-between">
<div style="width: 100%; min-width: 1020px; position: relative">
<div class="step_list_new" >
<el-steps :active="0" align-center>
<el-step
style="cursor: pointer"
v-for="item in listStep"
:key="item.index"
:class="{ C_style: show_step === item.index }"
<el-steps align-center >
<!-- 状态 已完成 未完成 正在填写 -->
<el-step @click.native="StepCode(item)"
style="cursor: pointer; padding: 20px 0 10px"
v-for="(item, i) in listStep"
:key="item.index" :data-step-index="item.index"
:class="{
C_style: show_step === item.index,
'step-success': stepStatus[i] && stepStatus[i].status == 1,
'step-edit': stepStatus[i] && stepStatus[i].status == 2
}"
>
<template #icon>
<i :class="item.icon" style="font-size: 36px" @click.stop="StepCode(item)"></i>
<template #icon v-if="stepStatus && stepStatus.length > 0" @click.stop="StepCode(item)">
<span v-if="stepStatus[i].status == 0"> </span>
<span class="step_icon" v-if="stepStatus[i].status == 1">
<i class="el-icon-check" style="font-size: 22px"></i>
</span>
<span class="step_icon" v-if="stepStatus[i].status == 2">
<i class="el-icon-edit-outline" style="font-size: 22px"></i>
</span>
</template>
<template #icon v-else>
<span @click.stop="StepCode(item)"> </span>
</template>
<template #title>
<span @click.stop="StepCode(item)" style="font-size: 18px">{{ item.title }}</span>
<div style="" class="step_title" @click.stop="StepCode(item)">
<div style="">
<i :class="item.icon" style="font-size: 22px; margin-right: 4px"></i>
<span style="font-size: 17px">{{ item.title }}</span>
</div>
</div>
</template>
</el-step>
</el-steps>
</div>
<ProgressBar :progress="uploadPercentage" v-if="isShowProgress" />
<!-- <ProgressBar :progress="uploadPercentage" v-if="isShowProgress" /> -->
<div class="manu_add" style="width: calc(100% - 20px)" v-loading="loading">
<el-form ref="articleform" :model="form" :rules="rules" label-width="120px">
<div class="bag_color" v-if="show_step == 1">
@@ -47,7 +66,8 @@
</el-select>
</el-form-item>
<el-form-item label="Manuscirpt :" prop="manuscirpt" label-width="120px">
<el-upload :on-progress="handleProgress"
<el-upload
:on-progress="handleProgress"
ref="uploadFileManuscirpt"
class="upload-demo up_newstyle"
:action="upload_manuscirpt"
@@ -121,7 +141,7 @@
label="Explain the reason clearly :"
prop="approval_content"
v-if="form.approval === 0"
label-width="300px"
label-width="290px"
>
<el-input type="textarea" rows="2" v-model="form.approval_content"></el-input>
</el-form-item>
@@ -136,14 +156,22 @@
:props="topicsProps"
clearable></el-cascader>
</el-form-item> -->
<el-form-item label="Research areas :" prop="major" label-width="120px">
<template slot="label">
<span style="color: #f56c6c; margin-right: 4px">*</span>Research area :
</template>
<common-major-list :list="majorValueList" @load="(e) => (this.majorValueList = e)"></common-major-list>
</el-form-item>
<el-form-item label="Key words :">
<el-input
v-for="(item, index) in keywordsList"
:key="index"
:name="index + 1"
v-model="item.ke"
clearable
style="width: 150px; margin-right: 15px; margin-bottom: 3px"
></el-input>
>
</el-input>
<el-button type="text" @click="addfund">
<i class="el-icon-circle-plus-outline">Add</i>
</el-button>
@@ -258,16 +286,16 @@
<p style="text-align: right; margin-bottom: 10px; border-bottom: 1px solid #dae8f7">
<span style="float: left; margin-top: 5px">Author {{ index + 1 }}</span>
<el-button @click="editAuthor(item, index)" type="text">
<i class="el-icon-edit" style="font-weight: bold"></i>
<i class="el-icon-edit" style="font-weight: bold;font-size: 14px"></i>
</el-button>
<el-button type="text" @click="gaPaiXu(item, 'top')" v-if="index != 0">
<i class="el-icon-top" style="font-weight: bold"></i>
<i class="el-icon-top" style="font-weight: bold;font-size: 14px"></i>
</el-button>
<el-button type="text" @click="gaPaiXu(item, 'bot')" v-if="index != form.authorList.length - 1">
<i class="el-icon-bottom" style="font-weight: bold"></i>
<i class="el-icon-bottom" style="font-weight: bold;font-size: 14px"></i>
</el-button>
<el-button @click="deleteAuthor(item, index)" type="text" class="Del_btn">
<i class="el-icon-delete" style="font-weight: bold"></i>
<i class="el-icon-delete" style="font-weight: bold;font-size: 14px"></i>
</el-button>
</p>
<p style="font-weight: bold; margin-bottom: 10px">
@@ -441,9 +469,6 @@
<!-- 提交 submit -->
<div class="bag_color" v-if="show_step == 4">
<div>
<el-form-item label="Research areas :" prop="major" label-width="160px">
<common-major-list :list="majorValueList" @load="(e) => (this.majorValueList = e)"></common-major-list>
</el-form-item>
<h3>Co-submitting</h3>
<p style="line-height: 25px; margin: 0 10px 0 55px; font-size: 14px">
TMR Publishing Group publishes multiple journals and offers you the opportunity to co-submit your paper.
@@ -477,7 +502,7 @@
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<p style="line-height: 25px; margin: 50px 10px 0 55px; font-size: 14px" v-if="checkReviewerof == 0">
<p style="line-height: 25px; margin: 30px 10px 0 55px; font-size: 14px" v-if="checkReviewerof == 0">
Would you be interested in serving as a reviewer for this journal?
<br /><br />
<el-switch
@@ -648,7 +673,6 @@
investigation in line with our misconduct policy.
</p>
<div style="margin: 30px 0 30px 0px">
<el-form-item label-width="180px">
<span slot="label">
@@ -692,7 +716,7 @@
</el-form-item>
<el-form-item label="Figure Copyright Declaration :" label-width="220px" prop="qualification">
<div style="color: #333">
<el-radio v-model="form.qualification" label="1"
<el-radio v-model="form.qualification" label="1" style="width: 100%"
>I confirm that all figures in this manuscript are original.</el-radio
>
<el-radio v-model="form.qualification" label="2"
@@ -704,19 +728,19 @@
<el-upload
ref="uploadFile"
class="upload-demo up_newstyle"
:action="upload_articleApproval"
accept=".pdf"
name="articleApproval"
:before-upload="beforeupload_articleApproval"
:action="upload_articleCopyright"
accept=".pdf,.rar,.zip"
name="articleCopyright"
:before-upload="beforeupload_articleCopyright"
:on-error="uperr_coverLetter"
:on-success="upSuccess_articleApproval"
:on-success="upSuccess_articleCopyright"
:limit="1"
:on-exceed="alertlimit"
:on-remove="removefilearticleApproval"
:file-list="fileL_articleApproval"
:on-remove="removefilearticleCopyright"
:file-list="fileL_articleCopyright"
:on-preview="dowloadFile"
>
<div class="el-upload__text" @click="clearUploadedFile">
<div class="el-upload__text" @click="clearUploadedCopyrightFile">
<em>Upload</em>
</div>
<div class="el-upload__tip" slot="tip" style="color: #888; left: 80px; font-size: 13px">
@@ -763,7 +787,7 @@
style="
padding: 0;
background: none;
margin-top: 30px;
margin-top: 20px;
line-height: 20px;
color: #999;
left: 990px;
@@ -798,7 +822,6 @@
attention.
</p>
<common-word-html
:articleId="stagingID"
imgHeight="120px"
v-if="isShowCommonWord && stagingID"
@@ -946,6 +969,9 @@
<script>
import JournalSelector from '@/components/page/components/article/journal-selector.vue';
import ProgressBar from '@/components/page/components/article/progress.vue';
import updateStepStatus from '@/components/page/components/article/checkStepCompletion.js';
import deepEqual from '@/components/page/components/article/deepEqual.js';
import { set } from 'vue';
export default {
components: {
JournalSelector,
@@ -956,7 +982,8 @@ export default {
const validateNotQQEmail = (rule, value, callback) => {
// 正则匹配QQ邮箱以@qq.com结尾不区分大小写
const qqEmailReg = /@qq\.com$/i;
if (value) { // 若有值必填校验已通过则检查是否为QQ邮箱
if (value) {
// 若有值必填校验已通过则检查是否为QQ邮箱
if (qqEmailReg.test(value)) {
callback(new Error('QQ email is not allowed')); // 不允许QQ邮箱
} else {
@@ -967,6 +994,8 @@ export default {
}
};
return {
formList: [],
stepStatus: [],
isShowProgress: false,
hasAIContent: ['1'],
isHasAI: '0',
@@ -1080,8 +1109,7 @@ export default {
department: '',
affiliation: '',
major_all: ''
},
}
],
countrys: [],
fileL_articleApproval: [],
@@ -1395,9 +1423,7 @@ export default {
immediate: true
},
form: {
handler(e) {
// console.log(e)
},
handler(e) {},
deep: true
}
},
@@ -1421,6 +1447,10 @@ export default {
upload_articleApproval: function () {
return this.baseUrl + 'api/Article/up_approval_file';
},
//图片版权
upload_articleCopyright: function () {
return this.baseUrl + 'api/Article/up_approval_file';
},
upload_coverLetter: function () {
return this.baseUrl + 'api/Article/up_file/type/coverLetter';
},
@@ -1441,6 +1471,30 @@ export default {
}
},
methods: {
handleStepClick(e) {
console.log('e at line 1469:', e)
// 通过事件源找到点击的步骤项
const stepEl = e.target.closest('.el-step');
if (stepEl) {
// 获取步骤标识item.index
const stepIndex = stepEl.getAttribute('data-step-index');
// 找到对应的item
const item = this.listStep.find(item => item.index === stepIndex);
if (item) {
this.StepCode(item); // 调用原逻辑
}
}
},
async initStepStatus() {
this.stepStatus = [];
const step1Incomplete = await updateStepStatus({ ...this.form, majorValueList: this.majorValueList });
console.log('step1Incomplete at line 1412:', step1Incomplete);
this.$nextTick(() => {
this.stepStatus = step1Incomplete;
});
this.$forceUpdate();
},
// 提交前校验所有评审人表单
async validateAllReviewers() {
// 遍历所有表单实例
@@ -1705,6 +1759,18 @@ export default {
.catch((err) => {
console.log(err);
});
this.$api
.post('api/Reviewer/getAllMajor')
.then((res) => {
this.jl_major = res.data.majors;
})
.catch((err) => {
this.$message.error(err);
});
this.$api.post('api/Major/getMajorList').then((res) => {
console.log(res, 99);
this.step4MajorList = res.data.majors;
});
this.$api.post('api/Admin/getCountrys').then((res) => {
this.countrys = res;
});
@@ -1727,19 +1793,8 @@ export default {
});
}
});
this.$api
.post('api/Reviewer/getAllMajor')
.then((res) => {
this.jl_major = res.data.majors;
})
.catch((err) => {
this.$message.error(err);
});
this.checkReviewer();
this.$api.post('api/Major/getMajorList').then((res) => {
console.log(res, 99);
this.step4MajorList = res.data.majors;
});
},
// 邮箱用户模糊搜索
@@ -1816,7 +1871,7 @@ export default {
},
// 选择推荐作者
checkAuthor(value, num) {
async checkAuthor(value, num) {
console.log('🚀 ~ checkAuthor ~ value:', value);
var isHaveAuthor = this.form.authorList.findIndex((e) => {
if (e.email == value.email) {
@@ -1857,8 +1912,9 @@ export default {
value.load = false;
value.sort = num;
value.article_id = this.stagingID;
this.$api.post('api/Article/addAuthor', value).then((res) => {
this.TempoAuthor();
await this.$api.post('api/Article/addAuthor', value).then(async (res) => {
await this.TempoAuthor();
this.$message.success('Added successfully');
});
},
@@ -2213,6 +2269,10 @@ export default {
// }
// return ismau;
},
beforeupload_articleCopyright(file) {
},
beforeupload_coverLetter(file) {
// const isWORd =
// file.type === 'application/msword' ||
@@ -2288,6 +2348,18 @@ export default {
}
console.log(this.form);
},
//图片版权
upSuccess_articleCopyright(res, file) {
if (res.code == 0) {
this.form.approval_file = 'articleApproval/' + res.upurl;
this.fileL_articleApproval = [{}];
this.fileL_articleApproval[0].name = 'Ethical approval File';
this.fileL_articleApproval[0].url = 'articleApproval/' + res.upurl;
} else {
this.$message.error('service error: ' + res.msg);
}
console.log(this.form);
},
upSuccess_coverLetter(res, file) {
if (res.code == 0) {
this.form.coverLetter = 'coverLetter/' + res.upurl;
@@ -2319,12 +2391,10 @@ export default {
},
// 进度回调:实时获取进度
handleProgress(event, file, fileList) {
this.isShowProgress=true
this.isShowProgress = true;
// event.percentage 就是当前进度(百分比,浮点数)
this.uploadPercentage = Math.round(event.percent); // 取整数
},
addWordTablesList(tables) {
var data = {
@@ -2340,19 +2410,17 @@ export default {
this.isShowCommonWord = true;
setTimeout(() => {
this.isShowProgress = false;
},500)
}, 500);
});
},
upLoadWordTables() {},
upSuccess_manuscirpt(res, File) {
// console.log('file at line 2174:', file.raw)
if (File) {
var that = this;
const reader = new FileReader();
reader.onload = function (e) {
that.$commonJS.extractWordTablesToArrays(File.raw, function (wordTables) {
that.addWordTablesList(wordTables);
});
};
@@ -2384,6 +2452,9 @@ export default {
clearUploadedFile() {
this.$refs['uploadFile'].clearFiles();
},
clearUploadedCopyrightFile() {
this.$refs['articleCopyright'].clearFiles();
},
//超出传送文件个数限制
alertlimit() {
this.$message.error('The maximum number of uploaded files has been exceeded');
@@ -2393,6 +2464,10 @@ export default {
this.form.approval_file = '';
this.fileL_articleApproval = [];
},
removefilearticleCopyright(file, fileList) {
this.form.copyright_file = '';
this.fileL_articleCopyright = [];
},
removefilecoverLetter(file, fileList) {
this.form.coverLetter = '';
this.fileL_coverLetter = [];
@@ -2604,6 +2679,7 @@ export default {
} else if (this.move_step < e) {
this.$refs.articleform.validate((valid) => {
if (valid) {
// deepEqual(this.form, this.oldForm);
if (this.move_step == 1) {
//暂时注销 start
// var flist = this.keywordsList;
@@ -2618,6 +2694,7 @@ export default {
// if (res.code == 0) {
// this.stagingID = res.data.article_id;
// this.form.article_id = res.data.article_id;
// this.$message.success('Saving succeeded!');
//暂时注销 End
this.move_step = 2; //进行步骤
@@ -2625,7 +2702,7 @@ export default {
//暂时注销 start
// } else {
// this.$message.error(res.msg);
// // this.$message.error(res.msg);
// }
// });
@@ -2692,15 +2769,10 @@ export default {
// 点击进行下一步
onStep(e) {
console.log('this.majorValueList at line 2604:', this.majorValueList);
console.log('this.form at line 2622:', this.form.abstrart);
this.$refs.articleform.validate((valid) => {
if (valid) {
if (this.isAbstractTooShort(this.form.abstrart)) {
this.$message.error('The abstract should not be less than 200 Chinese characters or English words!');
return false;
}
if (this.form.is_use_ai == 1 && this.form.use_ai_explain == '') {
this.$message.error('Please describe how artificial intelligence is utilized in this article');
return false;
@@ -2710,12 +2782,6 @@ export default {
this.$message.error('Please select the Journal');
return false;
}
// this.onStaging(1)
// setTimeout(() => {
// console.log('456')
// this.move_step = 2 //进行步骤
// this.show_step = 2 //显示内容
// }, 1000);
var flist = this.keywordsList;
var fstr = '';
@@ -2730,10 +2796,7 @@ export default {
this.form.major = this.majorValueList
.map((item) => (item.selectedValue.length > 0 ? item.selectedValue[item.selectedValue.length - 1] : []))
.toString(',');
// if (this.form.major == '') {
// this.$message.error('Please select the Research areas');
// return false;
// }
}
this.$api.post('api/Article/addArticlePart1', this.form).then((res) => {
@@ -2743,6 +2806,7 @@ export default {
this.$message.success('Saving succeeded!');
this.move_step = 2; //进行步骤
this.show_step = 2; //显示内容
this.initStepStatus();
} else {
this.$message.error(res.msg);
}
@@ -2819,6 +2883,16 @@ export default {
this.$message.error('Please select the Journal');
return false;
}
//标题
// if(!this.form.type) {
// this.$message.error('Please select type');
// return false;
// }
if(!this.form.title) {
this.$message.error('Please enter a title');
return false;
}
var flist = this.keywordsList;
var fstr = '';
for (var fu in flist) {
@@ -2826,7 +2900,6 @@ export default {
fstr += flist[fu].ke.trim() + ',';
}
}
console.log('this.form at line 2707:', this.form.abstrart);
this.form.keyWords = fstr == '' ? '' : fstr.substring(0, fstr.length - 1);
this.form.major = this.majorValueList.map((item) => item.selectedValue[item.selectedValue.length - 1]).toString(',');
@@ -2842,6 +2915,7 @@ export default {
if (res.code == 0) {
this.stagingID = res.data.article_id;
this.form.article_id = res.data.article_id;
this.initStepStatus();
this.$message.success('Saving succeeded!');
} else {
this.$message.error(res.msg);
@@ -2985,12 +3059,12 @@ export default {
}
},
// 读取
Temporary() {
async Temporary() {
this.$api
.post('api/Article/getSaveArticleDetail', {
article_id: this.stagingID
})
.then((res) => {
.then(async (res) => {
console.log(res, '已经保存的值');
if (res.code == 0) {
// 基本信息
@@ -3041,21 +3115,22 @@ export default {
}
this.showFiles();
}
// await this.initStepStatus();
} else {
this.$message.error(res.msg);
}
});
this.TempoAuthor();
await this.TempoAuthor();
},
// 读取作者
TempoAuthor() {
async TempoAuthor() {
this.$api
.post('api/Article/getAuthors', {
article_id: this.stagingID
})
.then((res) => {
.then(async(res) => {
if (res.code == 0) {
// 作者
if (res.data.authors.length > 0) {
@@ -3078,9 +3153,11 @@ export default {
} else {
this.form.authorList = [];
}
} else {
this.$message.error(res.msg);
}
await this.initStepStatus();
});
},
@@ -3116,23 +3193,23 @@ export default {
.formTopics {
width: 100%;
}
/* f8fbff */
.step_list_new {
background-color: #f8fbff;
box-shadow: 2px 30px 15px -20px #ebf5ff inset;
padding-top: 26px;
padding-bottom: 10px;
background-color: #f8f8f8;
box-shadow: 2px 30px 15px -20px #eee inset;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
width: calc(100% - 20px);
/* margin-bottom: 10px; */
margin-top: 20px;
}
::v-deep .step_list_new .el-step__icon.is-text {
border-radius: 0 !important;
border: none !important;
border: 3px solid !important;
}
::v-deep .step_list_new .el-step__icon {
background: transparent !important;
width: 34px !important;
height: 34px !important;
line-height: 34px !important;
font-size: 18px !important;
}
::v-deep .step_list_new .el-step__head.is-process {
color: #006699 !important;
@@ -3143,14 +3220,58 @@ export default {
font-weight: bold !important;
}
::v-deep .step_list_new .el-step.is-center .el-step__line {
left: 58% !important;
left: 57% !important;
right: -43% !important;
z-index: 2 !important;
}
.manu_add h3 {
font-size: 16px;
/* margin-bottom: 15px; */
margin-top: 0px;
}
::v-deep .step-success .el-step__head {
color: #67c23a !important;
border-color: #67c23a !important;
}
::v-deep .step-edit .el-step__head {
color: #006699 !important;
border-color: #006699 !important;
}
::v-deep .step-success .step_title {
color: #67c23a !important;
border-color: #67c23a !important;
}
::v-deep .step-edit .step_title {
color: #006699 !important;
border-color: #006699 !important;
}
::v-deep .el-step.is-horizontal .el-step__line {
top: 18px !important;
}
::v-deep .step_title {
display: flex;
align-items: center;
justify-content: center;
}
::v-deep .step_title > div {
display: flex;
align-items: center;
width: auto;
margin: 0 auto;
}
.step_icon {
display: flex;
align-items: center;
justify-content: center;
}
.step_icon i {
font-weight: bold;
}
.step_list_new .C_style {
background-color: #f8fbff !important;
-webkit-box-shadow: 2px 30px 15px -20px #ebf5ff inset !important;
box-shadow: 2px 30px 15px -20px #ebf5ff inset !important;
}
</style>
<style>
@@ -3269,15 +3390,14 @@ export default {
.manu_add .bag_color {
border-top: 1px solid #88888830;
padding-top: 15px;
padding-top: 30px;
background-color: #fff;
/* box-shadow: 2px 30px 15px -20px #ebf5ff inset; */
}
.manu_add .bag_color > div {
padding: 0px 15px;
/* background-color: #fff; */
/* box-shadow: 2px -30px 15px -20px #ebf5ff inset; */
}
.manu_add .pro_ceed {
@@ -3340,8 +3460,8 @@ export default {
}
.step_list > div.C_style {
background-color: #f8fbff;
box-shadow: 2px 30px 15px -20px #ebf5ff inset;
background-color: #f8fbff !important;
box-shadow: 2px 30px 15px -20px #ebf5ff inset !important;
}
.step_list > div.C_style > div {

View File

@@ -0,0 +1,125 @@
// 关键:让 condition 接收当前表单 f
var stepStatus=[]
var stepFields = {
1: [
{ field: 'journal', required: true },
{ field: 'type', required: true },
{ field: 'manuscirpt', required: true },
{ field: 'title', required: true },
{ field: 'approval', required: true },
// ✅ 这里改:用 (f)=> f.approval === 0
{ field: 'approval_content', required: false, condition: (f) => f.approval === 0 },
{ field: 'abstrart', required: true },
{ field: 'majorValueList', required: false, check: (f) => Array.isArray(f.majorValueList) && f.majorValueList.length > 0 }
],
2: [
{ field: 'authorList', required: true, check: (f) => Array.isArray(f.authorList) && f.authorList.length > 0 },
{ field: 'authorFirstname', required: true, check: (f) => f.authorList.every(a => a.firstname) },
{ field: 'authorLastname', required: true, check: (f) => f.authorList.every(a => a.lastname) },
{ field: 'authorEmail', required: true, check: (f) => f.authorList.every(a => a.email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(a.email)) },
{ field: 'isSuper', required: true, check: (f) => f.authorList.some(a => a.isSuper) },
{ field: 'isReport', required: true, check: (f) => f.authorList.some(a => a.isReport) }
],
3: [
{ field: 'coverLetter', required: true },
{ field: 'picturesAndTables', required: true },
{ field: 'supplementary', required: false }
],
4: [
{ field: 'istransfer', required: true },
{ field: 'checkedjours', required: false, condition: (f) => !!f.istransfer },
{ field: 'becomeRev', required: true },
// 注意this.usercap 在这里可能没有 this 环境,建议改成从 form 里取,如 f.usercap
{ field: 'reviewerInfo', required: false, condition: (f) => f.becomeRev && !(Array.isArray(f.usercap) && f.usercap.includes('reviewer')) },
{ field: 'qualification', required: true },
{ field: 'qualificationFile', required: false, condition: (f) => f.qualification === '2' },
{ field: 'isUseAi', required: true },
{ field: 'useAiExplain', required: false, condition: (f) => f.isUseAi === '1' },
{ field: 'agreechecked', required: true }
]
};
async function updateStepStatus(formInput) {
const form = { ...formInput }; // 使用局部 form避免被外部覆盖
console.log('form at line 43:', form)
stepStatus = [
{ step: 1, status: '0' },
{ step: 2, status: '0' },
{ step: 3, status: '0' },
{ step: 4, status: '0' }
];
function checkField(field) {
// 1) 条件非必填:若没有 required 且没有 condition直接通过
if (!field.required && !field.condition) return true;
// 2) 有 condition若条件不满足即不需要填直接通过
if (typeof field.condition === 'function' && !field.condition(form)) {
return true;
}
// 3) 自定义检查:优先
if (typeof field.check === 'function') {
return field.check(form);
}
// 4) 常规值检查
const value = form[field.field];
// null/undefined -> 未填
if (value === undefined || value === null) return false;
// 数组 -> 必须非空
if (Array.isArray(value)) return value.length > 0;
// 字符串 -> 非空白
if (typeof value === 'string') return value.trim() !== '';
// 数字 -> 只要有值即可0 也算已填)
if (typeof value === 'number') return true;
// 布尔 -> 只要存在即可
if (typeof value === 'boolean') return true;
// 文件或对象:简单规则,存在即视为已填(可按需加更细规则)
if (typeof value === 'object') return true;
// 其他类型默认未填
return false;
}
function checkStepStatus(step) {
const fields = stepFields[step];
let allFilled = true;
let hasFilled = false;
for (const field of fields) {
const isFilled = checkField(field);
if (!isFilled) {
allFilled = false;
} else {
hasFilled = true;
}
}
//0 未完成 1已完成 2正在填写
if (allFilled) return '1';
if (hasFilled) return '2';
return '0';
}
for (let i = 1; i <= 4; i++) {
if (i !== 2) {
stepStatus[i - 1].status = await checkStepStatus(i);
} else if (i === 2 && Array.isArray(form.authorList)&&form.authorList.length>0) {
console.log('form.authorList at line 115:', form.authorList)
stepStatus[i - 1].status = await checkStepStatus(i);
}
}
return stepStatus;
}
export default updateStepStatus;

View File

@@ -0,0 +1,41 @@
/**
* 深度对比两个对象是否相同(包括嵌套的数组和对象)
* @param {Object} obj1 - 第一个对象
* @param {Object} obj2 - 第二个对象
* @returns {boolean} - 如果两个对象相同则返回 true否则返回 false
*/
function deepEqual(obj1, obj2) {
// 如果两个对象是同一个引用,直接返回 true
if (obj1 === obj2) return true;
// 如果一个是 null 或 undefined另一个不是返回 false
if (obj1 == null || obj2 == null) return false;
// 如果两个对象类型不同(一个是对象,另一个不是),返回 false
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
// 比较数组长度或对象的键数量
if (Array.isArray(obj1) !== Array.isArray(obj2)) return false;
if (Array.isArray(obj1)) {
if (obj1.length !== obj2.length) return false;
// 对比数组的每个元素
for (let i = 0; i < obj1.length; i++) {
if (!deepEqual(obj1[i], obj2[i])) return false;
}
} else {
// 对比对象的每个键和值
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!keys2.includes(key)) return false;
if (!deepEqual(obj1[key], obj2[key])) return false;
}
}
// 所有检查通过,返回 true
return true;
}
export default deepEqual;

View File

@@ -1,71 +1,59 @@
<template>
<div class="journal-selector relative w-full max-w-md" ref="journalSelector">
<!-- 使用el-input作为输入框 -->
<div v-if="!selectedJournal">
<el-input clearable
<el-input
clearable
v-model="searchTerm"
@focus="showDropdown = true"
size="medium"
class="journal-input" suffix-icon="el-icon-search"
class="journal-input"
suffix-icon="el-icon-search"
>
<!-- 搜索图标插槽 -->
<!-- 已选择时显示清除图标 -->
<i
slot="suffix"
v-if="selectedJournal"
style="line-height: 38px"
class="fas fa-times text-gray-400 hover:text-red-500 cursor-pointer el-input__icon"
@click.stop="clearSelection"
></i>
</el-input>
<!-- 下拉列表 -->
<div
class="journal-dropdown "
v-if="showDropdown && filteredJournals.length"
>
<div
class="journal-item "
v-for="journal in filteredJournals"
:key="journal.id"
@click="selectJournal(journal)"
>
<div class="journal-dropdown" v-if="showDropdown && filteredJournals.length">
<div class="journal-item" v-for="journal in filteredJournals" :key="journal.id" @click="selectJournal(journal)">
<div class="flexBox alignCenter justBetween">
<img src="https://picsum.photos/40/40?random=1" :alt="journal.title" class="">
<img
src="https://www.tmrjournals.com/public/journalicon/20251010/28267562ad187206ca77d64bcd7a138f.jpg"
:alt="journal.title"
class=""
style="width: 40px; height: 50px"
/>
<div class="journal-title">{{ journal.title }}</div>
</div>
</div>
</div>
<!-- 无结果提示 -->
<div
class="journal-dropdown " style="padding: 0;"
v-if="showDropdown && !filteredJournals.length && searchTerm"
>
<div class="journal-dropdown" style="padding: 0" v-if="showDropdown && !filteredJournals.length && searchTerm">
<div class="">
<p class="nofound">No matching journals found
Please try other keywords or spellings</p>
<p class="nofound">No matching journals found Please try other keywords or spellings</p>
</div>
</div>
</div>
<!-- 选中的期刊 -->
<div
class="journal-selected"
v-if="selectedJournal&&!showDropdown"
>
<div class="journal-selected" v-if="selectedJournal && !showDropdown">
<div class="flexBox alignCenter">
<img src="https://picsum.photos/40/40?random=1" :alt="selectedJournal.name" class="object-cover">
<img
src="https://www.tmrjournals.com/public/journalicon/20251010/28267562ad187206ca77d64bcd7a138f.jpg"
:alt="selectedJournal.name"
class="object-cover"
style="width: 30px; height: 40px"
/>
<span class="journal-selected-title">{{ selectedJournal.title }}</span>
</div>
<div class="journal-info">
@@ -89,7 +77,7 @@ export default {
check_item: {
type: Object,
default: () => ({})
},
}
},
data() {
return {
@@ -115,7 +103,7 @@ export default {
} else {
this.selectedJournal = null;
}
console.log('this.selectedJournal at line 113:', this.selectedJournal)
console.log('this.selectedJournal at line 113:', this.selectedJournal);
// }
},
@@ -128,21 +116,19 @@ export default {
if (!this.searchTerm) {
return this.journals;
}
const term = this.searchTerm.toLowerCase();
console.log('term at line 109:', term)
return this.journals.filter(journal => journal.abbr.toLowerCase().includes(term)||
return this.journals.filter(
(journal) =>
journal.abbr.toLowerCase().includes(term) ||
journal.title.toLowerCase().includes(term) ||
journal.title.toLowerCase().includes(this.searchTerm) ||
journal.title.includes(this.searchTerm)
);
}
},
methods: {
getJournalList() {
this.$api
.post('api/Article/getJournal')
@@ -154,11 +140,10 @@ export default {
});
},
closeSelection() {
this.selectedJournal=null
this.selectedJournal = null;
this.searchTerm = '';
this.showDropdown = false;
this.$emit('selected', '')
this.$emit('selected', '');
},
// 选择期刊
selectJournal(journal) {
@@ -167,7 +152,7 @@ export default {
this.showDropdown = false;
// 触发选中事件,方便父组件获取数据
this.$emit('selected', journal);
this.$emit('selected', journal)
this.$emit('selected', journal);
},
// 清除选择
@@ -202,7 +187,8 @@ export default {
<style scoped>
/* 适配el-input的样式 */
.journal-input {
--el-input-height: 40px; cursor: pointer;
--el-input-height: 40px;
cursor: pointer;
}
.journal-selector .title {
font-size: 14px;
@@ -237,8 +223,9 @@ export default {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
border-radius: 8px;
margin-top: 6px;
padding: 12px 0;
box-sizing: border-box; cursor: pointer;
padding: 12px 0 0;
box-sizing: border-box;
cursor: pointer;
}
/* 列表项样式 */
@@ -260,12 +247,12 @@ export default {
.journal-selected {
border-radius: 6px;
/* margin-top: 8px; */
border: 1px solid #DCDFE6;
font-size: 14px;color: #8c8d8f;
border: 1px solid #dcdfe6;
font-size: 14px;
color: #8c8d8f;
padding: 10px;
cursor: pointer;
position: relative;
}
.journal-selected-title {
font-size: 14px;
@@ -280,7 +267,6 @@ export default {
}
.flexBox {
display: flex;
}
.alignCenter {
align-items: center;
@@ -293,7 +279,7 @@ export default {
}
.journal-info {
margin-top: 8px;
line-height: 20px;
line-height: 16px;
text-align: justify;
}
.journal-close {
@@ -303,7 +289,6 @@ export default {
font-size: 16px;
color: #8c8d8f;
cursor: pointer;
}
.nofound {
color: #8c8d8f;

View File

@@ -136,7 +136,7 @@
<!-- 表格缩略图区域 -->
<li >
<div class="go-content-charts-item-box" style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: start; margin-top: 20px">
<div class="go-content-charts-item-box" style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: start; ">
<div