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

@@ -52,7 +52,7 @@ const capitalizeFirstLetter = function (text) {
//px //px
function emuToPixels(emu) { function emuToPixels(emu) {
// 将 EMU 转换为厘米,并进一步转换为像素 // 将 EMU 转换为厘米,并进一步转换为像素
const emuToPixels = emu * 96 / 914400; const emuToPixels = emu * 96 / 914400;
// return parseFloat((emu * 96 / 914400).toFixed(2)); // ✅ // return parseFloat((emu * 96 / 914400).toFixed(2)); // ✅
// 四舍五入并保留两位小数 // 四舍五入并保留两位小数
return (Math.round(emuToPixels * 100) / 100).toFixed(0); return (Math.round(emuToPixels * 100) / 100).toFixed(0);
@@ -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() { extractLatexFromMathJax() {
@@ -350,7 +258,6 @@ export default {
// 创建临时 DOM 容器 // 创建临时 DOM 容器
const tempDiv = document.createElement("div"); const tempDiv = document.createElement("div");
tempDiv.innerHTML = pastedHtml; // 插入粘贴的 HTML 内容 tempDiv.innerHTML = pastedHtml; // 插入粘贴的 HTML 内容
// 获取表格 // 获取表格
const tables = tempDiv.querySelectorAll("table"); const tables = tempDiv.querySelectorAll("table");
if (tables.length === 0) { if (tables.length === 0) {
@@ -358,31 +265,23 @@ export default {
callback([]); callback([]);
return; return;
} }
const allTables = []; // 存储所有表格的二维数组 const allTables = []; // 存储所有表格的二维数组
for (const table of tables) { for (const table of tables) {
const rows = table.querySelectorAll("tr"); const rows = table.querySelectorAll("tr");
const tableArray = []; // 当前表格的二维数组 const tableArray = []; // 当前表格的二维数组
// 存储合并单元格信息 // 存储合并单元格信息
const mergeMap = {}; const mergeMap = {};
rows.forEach((row, rowIndex) => { rows.forEach((row, rowIndex) => {
const cells = row.querySelectorAll("td, th"); const cells = row.querySelectorAll("td, th");
const rowArray = []; const rowArray = [];
let colIndex = 0; let colIndex = 0;
cells.forEach((cell) => { cells.forEach((cell) => {
// 跳过被合并的单元格 // 跳过被合并的单元格
while (mergeMap[`${rowIndex},${colIndex}`]) { while (mergeMap[`${rowIndex},${colIndex}`]) {
colIndex++; colIndex++;
} }
// 获取单元格内容,如果为空则设置为默认值 // 获取单元格内容,如果为空则设置为默认值
let cellText = cell.innerText.trim() || "&nbsp;"; // 处理空值 let cellText = cell.innerText.trim() || "&nbsp;"; // 处理空值
// 处理样式 // 处理样式
if (cell.style.fontWeight === "bold") { if (cell.style.fontWeight === "bold") {
cellText = `<b>${cellText}</b>`; cellText = `<b>${cellText}</b>`;
@@ -400,7 +299,6 @@ export default {
// 检查合并单元格属性 // 检查合并单元格属性
const colspan = parseInt(cell.getAttribute("colspan") || "1", 10); const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
const rowspan = parseInt(cell.getAttribute("rowspan") || "1", 10); const rowspan = parseInt(cell.getAttribute("rowspan") || "1", 10);
// 保存当前单元格信息 // 保存当前单元格信息
rowArray[colIndex] = { rowArray[colIndex] = {
text: cellText, text: cellText,
@@ -417,17 +315,12 @@ export default {
} }
} }
} }
colIndex++; // 移动到下一列 colIndex++; // 移动到下一列
}); });
tableArray.push(rowArray); // 添加当前行到表格数组 tableArray.push(rowArray); // 添加当前行到表格数组
}); });
allTables.push(tableArray); // 添加当前表格到所有表格数组 allTables.push(tableArray); // 添加当前表格到所有表格数组
} }
callback(allTables); // 返回处理后的数组 callback(allTables); // 返回处理后的数组
} catch (error) { } catch (error) {
console.error("解析粘贴内容失败:", error); console.error("解析粘贴内容失败:", error);
@@ -439,18 +332,14 @@ export default {
try { try {
const Zip = new JSZip(); const Zip = new JSZip();
const zip = await Zip.loadAsync(file); const zip = await Zip.loadAsync(file);
const documentFile = zip.file("word/document.xml"); const documentFile = zip.file("word/document.xml");
if (!documentFile) { if (!documentFile) {
console.error("❌ 找不到 word/document.xml无法解析 Word 文件"); console.error("❌ 找不到 word/document.xml无法解析 Word 文件");
return; return;
} }
const documentXml = await documentFile.async("string"); const documentXml = await documentFile.async("string");
const parser = new DOMParser(); const parser = new DOMParser();
const documentDoc = parser.parseFromString(documentXml, "application/xml"); const documentDoc = parser.parseFromString(documentXml, "application/xml");
const numberingFile = zip.file("word/numbering.xml"); const numberingFile = zip.file("word/numbering.xml");
let numberingMap = {}; let numberingMap = {};
if (numberingFile) { if (numberingFile) {
@@ -460,45 +349,34 @@ export default {
} else { } else {
console.warn("⚠️ word/numbering.xml 不存在,跳过编号解析"); console.warn("⚠️ word/numbering.xml 不存在,跳过编号解析");
} }
const tables = documentDoc.getElementsByTagNameNS(namespace, "tbl"); const tables = documentDoc.getElementsByTagNameNS(namespace, "tbl");
const allTables = []; const allTables = [];
if (!tables || tables.length === 0) { if (!tables || tables.length === 0) {
console.warn("未找到表格内容,请检查 XML 结构"); console.warn("未找到表格内容,请检查 XML 结构");
return []; return [];
} }
for (const table of tables) { for (const table of tables) {
const rows = table.getElementsByTagNameNS(namespace, "tr"); const rows = table.getElementsByTagNameNS(namespace, "tr");
const tableArray = []; const tableArray = [];
let rowSpanMap = []; let rowSpanMap = [];
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
const row = rows[rowIndex]; const row = rows[rowIndex];
const cells = row.getElementsByTagNameNS(namespace, "tc"); const cells = row.getElementsByTagNameNS(namespace, "tc");
const rowArray = []; const rowArray = [];
if (!rowSpanMap[rowIndex]) { if (!rowSpanMap[rowIndex]) {
rowSpanMap[rowIndex] = []; rowSpanMap[rowIndex] = [];
} }
let cellIndex = 0; let cellIndex = 0;
for (let i = 0; i < cells.length; i++) { for (let i = 0; i < cells.length; i++) {
while (rowSpanMap[rowIndex][cellIndex]) { while (rowSpanMap[rowIndex][cellIndex]) {
rowArray.push(null); rowArray.push(null);
cellIndex++; cellIndex++;
} }
const cell = cells[i]; const cell = cells[i];
let cellText = ""; let cellText = "";
const paragraphs = cell.getElementsByTagName("w:p"); const paragraphs = cell.getElementsByTagName("w:p");
const gridSpan = cell.getElementsByTagNameNS(namespace, "gridSpan")[0]; const gridSpan = cell.getElementsByTagNameNS(namespace, "gridSpan")[0];
const vMerge = cell.getElementsByTagNameNS(namespace, "vMerge")[0]; const vMerge = cell.getElementsByTagNameNS(namespace, "vMerge")[0];
var colspan = gridSpan ? parseInt(gridSpan.getAttribute("w:val"), 10) : 1; var colspan = gridSpan ? parseInt(gridSpan.getAttribute("w:val"), 10) : 1;
var rowspan = 1; var rowspan = 1;
if (vMerge) { if (vMerge) {
@@ -506,49 +384,39 @@ export default {
rowspan = 1; // 初始化 rowspan rowspan = 1; // 初始化 rowspan
let nextRowIdx = rowIndex + 1; let nextRowIdx = rowIndex + 1;
let maxRowspan = rows.length - rowIndex; // 确保 rowspan 不会超过剩余行数 let maxRowspan = rows.length - rowIndex; // 确保 rowspan 不会超过剩余行数
while (nextRowIdx < rows.length) { while (nextRowIdx < rows.length) {
const nextRowCells = rows[nextRowIdx].getElementsByTagNameNS(namespace, "tc"); const nextRowCells = rows[nextRowIdx].getElementsByTagNameNS(namespace, "tc");
// console.log(`🔍 检查下一行单元格 at row ${nextRowIdx}, col ${cellIndex}:`, nextRowCells); // console.log(`🔍 检查下一行单元格 at row ${nextRowIdx}, col ${cellIndex}:`, nextRowCells);
if (nextRowCells.length > cellIndex) { if (nextRowCells.length > cellIndex) {
const nextCell = nextRowCells[cellIndex]; const nextCell = nextRowCells[cellIndex];
if (!nextCell) { if (!nextCell) {
// console.warn(`⚠️ nextCell 未定义 at row ${nextRowIdx}, col ${cellIndex}`); // console.warn(`⚠️ nextCell 未定义 at row ${nextRowIdx}, col ${cellIndex}`);
break; break;
} }
const nextVMerge = nextCell.getElementsByTagNameNS(namespace, "vMerge")[0]; const nextVMerge = nextCell.getElementsByTagNameNS(namespace, "vMerge")[0];
// console.log(`🔍 检查 nextVMerge at row ${nextRowIdx}, col ${cellIndex}:`, nextVMerge); // console.log(`🔍 检查 nextVMerge at row ${nextRowIdx}, col ${cellIndex}:`, nextVMerge);
// **如果 nextVMerge 为空,则不应继续增长 rowspan** // **如果 nextVMerge 为空,则不应继续增长 rowspan**
if (!nextVMerge) { if (!nextVMerge) {
// console.log(`⚠️ nextVMerge 为空 at row ${nextRowIdx}, col ${cellIndex} - 停止扩展`); // console.log(`⚠️ nextVMerge 为空 at row ${nextRowIdx}, col ${cellIndex} - 停止扩展`);
break; break;
} }
// **解析 nextVMerge 的值** // **解析 nextVMerge 的值**
const vMergeVal = nextVMerge.getAttribute("w:val"); const vMergeVal = nextVMerge.getAttribute("w:val");
if (!vMergeVal || vMergeVal === "continue") { if (!vMergeVal || vMergeVal === "continue") {
if (rowspan < maxRowspan) { // 限制 rowspan 最大值 if (rowspan < maxRowspan) { // 限制 rowspan 最大值
rowspan++; rowspan++;
// console.log(`✅ rowspan 扩展到: ${rowspan} (row: ${nextRowIdx}, col: ${cellIndex})`);
nextRowIdx++; nextRowIdx++;
} else { } else {
// console.log(`⛔ 最大 rowspan 限制 ${rowspan},在 row ${nextRowIdx} 停止`);
break; break;
} }
} else if (vMergeVal === "restart") { } else if (vMergeVal === "restart") {
// console.log(`⛔ 停止 rowspan 扩展 at row ${nextRowIdx}, 因为 w:val="restart"`);
break; break;
} else { } else {
// console.log(`⚠️ 未知 w:val="${vMergeVal}" at row ${nextRowIdx},停止合并`);
break; break;
} }
} else { } else {
// console.warn(`⚠️ Row ${nextRowIdx} 没有足够的列 cellIndex ${cellIndex}`);
break; break;
} }
} }
@@ -556,8 +424,6 @@ export default {
continue; continue;
} }
} }
const currentLevelNumbers = {}; const currentLevelNumbers = {};
for (const paragraph of paragraphs) { for (const paragraph of paragraphs) {
let listPrefix = ""; let listPrefix = "";
@@ -571,7 +437,6 @@ export default {
listPrefix = this.getListNumber(numId, ilvl, numberingMap, currentLevelNumbers); listPrefix = this.getListNumber(numId, ilvl, numberingMap, currentLevelNumbers);
} }
} }
let paragraphText = listPrefix ? `${listPrefix} ` : ""; let paragraphText = listPrefix ? `${listPrefix} ` : "";
const runs = paragraph.getElementsByTagName("w:r"); const runs = paragraph.getElementsByTagName("w:r");
@@ -580,7 +445,6 @@ export default {
const drawings = run.getElementsByTagName("w:drawing"); const drawings = run.getElementsByTagName("w:drawing");
for (let d = 0; d < drawings.length; d++) { for (let d = 0; d < drawings.length; d++) {
const drawing = drawings[d]; const drawing = drawings[d];
// 使用命名空间提取 a:blip // 使用命名空间提取 a:blip
const blips = drawing.getElementsByTagNameNS("http://schemas.openxmlformats.org/drawingml/2006/main", "blip"); const blips = drawing.getElementsByTagNameNS("http://schemas.openxmlformats.org/drawingml/2006/main", "blip");
for (let b = 0; b < blips.length; b++) { for (let b = 0; b < blips.length; b++) {
@@ -592,24 +456,16 @@ export default {
} }
} }
} }
const texts = run.getElementsByTagName("w:t"); const texts = run.getElementsByTagName("w:t");
for (const text of texts) { for (const text of texts) {
textContent += text.textContent; textContent += text.textContent;
} }
const rPr = run.getElementsByTagName("w:rPr")[0]; const rPr = run.getElementsByTagName("w:rPr")[0];
let formattedText = textContent; let formattedText = textContent;
if (rPr) { if (rPr) {
const bold = rPr.getElementsByTagName("w:b").length > 0; const bold = rPr.getElementsByTagName("w:b").length > 0;
const italic = rPr.getElementsByTagName("w:i").length > 0; const italic = rPr.getElementsByTagName("w:i").length > 0;
const vertAlignElement = rPr.getElementsByTagName("w:vertAlign")[0]; const vertAlignElement = rPr.getElementsByTagName("w:vertAlign")[0];
if (bold) { if (bold) {
formattedText = `<b>${formattedText}</b>`; formattedText = `<b>${formattedText}</b>`;
} }
@@ -625,42 +481,32 @@ export default {
} }
} }
} }
formattedText = replaceNegativeSign(formattedText); formattedText = replaceNegativeSign(formattedText);
formattedText = capitalizeFirstLetter(formattedText); formattedText = capitalizeFirstLetter(formattedText);
const regex = /\[(\d+(?:\d+)?(?:, ?\d+(?:\d+)?)*)\]/g; const regex = /\[(\d+(?:\d+)?(?:, ?\d+(?:\d+)?)*)\]/g;
formattedText = formattedText.replace(/<blue>/g, '').replace(/<\/blue>/g, ''); formattedText = formattedText.replace(/<blue>/g, '').replace(/<\/blue>/g, '');
if (regex.test(formattedText)) { if (regex.test(formattedText)) {
formattedText = formattedText.replace(regex, function (match) { formattedText = formattedText.replace(regex, function (match) {
const content = match.slice(1, match.length - 1); const content = match.slice(1, match.length - 1);
if (/^\d+$/.test(content) || /, ?/.test(content) || //.test(content)) { if (/^\d+$/.test(content) || /, ?/.test(content) || //.test(content)) {
return `<blue>${match}</blue>`; return `<blue>${match}</blue>`;
} }
return match; return match;
}); });
} }
paragraphText += formattedText; paragraphText += formattedText;
} }
const breaks = paragraph.getElementsByTagName("w:br"); const breaks = paragraph.getElementsByTagName("w:br");
for (const br of breaks) { for (const br of breaks) {
paragraphText += "<br/>"; paragraphText += "<br/>";
} }
cellText += paragraphText; cellText += paragraphText;
} }
rowArray.push({ rowArray.push({
text: cellText, text: cellText,
colspan: colspan, colspan: colspan,
rowspan: rowspan rowspan: rowspan
}); });
if (rowspan > 1) { if (rowspan > 1) {
for (let j = 1; j < rowspan; j++) { for (let j = 1; j < rowspan; j++) {
if (!rowSpanMap[rowIndex + j]) { if (!rowSpanMap[rowIndex + j]) {
@@ -669,25 +515,102 @@ export default {
rowSpanMap[rowIndex + j][cellIndex] = true; rowSpanMap[rowIndex + j][cellIndex] = true;
} }
} }
cellIndex++; cellIndex++;
} }
tableArray.push(rowArray.filter(item => item !== null)); tableArray.push(rowArray.filter(item => item !== null));
} }
allTables.push(tableArray); allTables.push(tableArray);
} }
callback(allTables); callback(allTables);
} catch (error) { } catch (error) {
console.error("解析 Word 文件失败:", error); console.error("解析 Word 文件失败:", error);
callback([]); 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) { // async extractWordTablesToArrays(file, callback) {
// const namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; // const namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";

View File

@@ -2,26 +2,45 @@
<div style=""> <div style="">
<div style="display: flex; justify-content: space-between"> <div style="display: flex; justify-content: space-between">
<div style="width: 100%; min-width: 1020px; position: relative"> <div style="width: 100%; min-width: 1020px; position: relative">
<div class="step_list_new"> <div class="step_list_new" >
<el-steps :active="0" align-center> <el-steps align-center >
<el-step <!-- 状态 已完成 未完成 正在填写 -->
style="cursor: pointer" <el-step @click.native="StepCode(item)"
v-for="item in listStep" style="cursor: pointer; padding: 20px 0 10px"
:key="item.index" v-for="(item, i) in listStep"
:class="{ C_style: show_step === item.index }" :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>
<template #title> <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> </template>
</el-step> </el-step>
</el-steps> </el-steps>
</div> </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"> <div class="manu_add" style="width: calc(100% - 20px)" v-loading="loading">
<el-form ref="articleform" :model="form" :rules="rules" label-width="120px"> <el-form ref="articleform" :model="form" :rules="rules" label-width="120px">
<div class="bag_color" v-if="show_step == 1"> <div class="bag_color" v-if="show_step == 1">
@@ -47,7 +66,8 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="Manuscirpt :" prop="manuscirpt" label-width="120px"> <el-form-item label="Manuscirpt :" prop="manuscirpt" label-width="120px">
<el-upload :on-progress="handleProgress" <el-upload
:on-progress="handleProgress"
ref="uploadFileManuscirpt" ref="uploadFileManuscirpt"
class="upload-demo up_newstyle" class="upload-demo up_newstyle"
:action="upload_manuscirpt" :action="upload_manuscirpt"
@@ -121,7 +141,7 @@
label="Explain the reason clearly :" label="Explain the reason clearly :"
prop="approval_content" prop="approval_content"
v-if="form.approval === 0" 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-input type="textarea" rows="2" v-model="form.approval_content"></el-input>
</el-form-item> </el-form-item>
@@ -136,14 +156,22 @@
:props="topicsProps" :props="topicsProps"
clearable></el-cascader> clearable></el-cascader>
</el-form-item> --> </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-form-item label="Key words :">
<el-input <el-input
v-for="(item, index) in keywordsList" v-for="(item, index) in keywordsList"
:key="index" :key="index"
:name="index + 1" :name="index + 1"
v-model="item.ke" v-model="item.ke"
clearable
style="width: 150px; margin-right: 15px; margin-bottom: 3px" style="width: 150px; margin-right: 15px; margin-bottom: 3px"
></el-input> >
</el-input>
<el-button type="text" @click="addfund"> <el-button type="text" @click="addfund">
<i class="el-icon-circle-plus-outline">Add</i> <i class="el-icon-circle-plus-outline">Add</i>
</el-button> </el-button>
@@ -258,16 +286,16 @@
<p style="text-align: right; margin-bottom: 10px; border-bottom: 1px solid #dae8f7"> <p style="text-align: right; margin-bottom: 10px; border-bottom: 1px solid #dae8f7">
<span style="float: left; margin-top: 5px">Author {{ index + 1 }}</span> <span style="float: left; margin-top: 5px">Author {{ index + 1 }}</span>
<el-button @click="editAuthor(item, index)" type="text"> <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>
<el-button type="text" @click="gaPaiXu(item, 'top')" v-if="index != 0"> <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>
<el-button type="text" @click="gaPaiXu(item, 'bot')" v-if="index != form.authorList.length - 1"> <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>
<el-button @click="deleteAuthor(item, index)" type="text" class="Del_btn"> <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> </el-button>
</p> </p>
<p style="font-weight: bold; margin-bottom: 10px"> <p style="font-weight: bold; margin-bottom: 10px">
@@ -400,7 +428,7 @@
<div class="el-upload__tip" slot="tip">Only word files can be uploaded(.doc,.docx)</div> <div class="el-upload__tip" slot="tip">Only word files can be uploaded(.doc,.docx)</div>
</el-upload> </el-upload>
</el-form-item> --> </el-form-item> -->
<el-form-item label="Supplementary Material :" label-width="200px"> <el-form-item label="Supplementary Material :" label-width="200px">
<el-upload <el-upload
class="upload-demo up_newstyle" class="upload-demo up_newstyle"
@@ -441,9 +469,6 @@
<!-- 提交 submit --> <!-- 提交 submit -->
<div class="bag_color" v-if="show_step == 4"> <div class="bag_color" v-if="show_step == 4">
<div> <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> <h3>Co-submitting</h3>
<p style="line-height: 25px; margin: 0 10px 0 55px; font-size: 14px"> <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. TMR Publishing Group publishes multiple journals and offers you the opportunity to co-submit your paper.
@@ -477,7 +502,7 @@
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </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? Would you be interested in serving as a reviewer for this journal?
<br /><br /> <br /><br />
<el-switch <el-switch
@@ -648,7 +673,6 @@
investigation in line with our misconduct policy. investigation in line with our misconduct policy.
</p> </p>
<div style="margin: 30px 0 30px 0px"> <div style="margin: 30px 0 30px 0px">
<el-form-item label-width="180px"> <el-form-item label-width="180px">
<span slot="label"> <span slot="label">
@@ -692,7 +716,7 @@
</el-form-item> </el-form-item>
<el-form-item label="Figure Copyright Declaration :" label-width="220px" prop="qualification"> <el-form-item label="Figure Copyright Declaration :" label-width="220px" prop="qualification">
<div style="color: #333"> <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 >I confirm that all figures in this manuscript are original.</el-radio
> >
<el-radio v-model="form.qualification" label="2" <el-radio v-model="form.qualification" label="2"
@@ -704,19 +728,19 @@
<el-upload <el-upload
ref="uploadFile" ref="uploadFile"
class="upload-demo up_newstyle" class="upload-demo up_newstyle"
:action="upload_articleApproval" :action="upload_articleCopyright"
accept=".pdf" accept=".pdf,.rar,.zip"
name="articleApproval" name="articleCopyright"
:before-upload="beforeupload_articleApproval" :before-upload="beforeupload_articleCopyright"
:on-error="uperr_coverLetter" :on-error="uperr_coverLetter"
:on-success="upSuccess_articleApproval" :on-success="upSuccess_articleCopyright"
:limit="1" :limit="1"
:on-exceed="alertlimit" :on-exceed="alertlimit"
:on-remove="removefilearticleApproval" :on-remove="removefilearticleCopyright"
:file-list="fileL_articleApproval" :file-list="fileL_articleCopyright"
:on-preview="dowloadFile" :on-preview="dowloadFile"
> >
<div class="el-upload__text" @click="clearUploadedFile"> <div class="el-upload__text" @click="clearUploadedCopyrightFile">
<em>Upload</em> <em>Upload</em>
</div> </div>
<div class="el-upload__tip" slot="tip" style="color: #888; left: 80px; font-size: 13px"> <div class="el-upload__tip" slot="tip" style="color: #888; left: 80px; font-size: 13px">
@@ -763,7 +787,7 @@
style=" style="
padding: 0; padding: 0;
background: none; background: none;
margin-top: 30px; margin-top: 20px;
line-height: 20px; line-height: 20px;
color: #999; color: #999;
left: 990px; left: 990px;
@@ -798,10 +822,9 @@
attention. attention.
</p> </p>
<common-word-html <common-word-html
:articleId="stagingID" :articleId="stagingID"
imgHeight="120px" imgHeight="120px"
v-if="isShowCommonWord&&stagingID" v-if="isShowCommonWord && stagingID"
style="margin-top: 10px; box-sizing: border-box; background-color: #fff" style="margin-top: 10px; box-sizing: border-box; background-color: #fff"
></common-word-html> ></common-word-html>
</div> </div>
@@ -946,27 +969,33 @@
<script> <script>
import JournalSelector from '@/components/page/components/article/journal-selector.vue'; import JournalSelector from '@/components/page/components/article/journal-selector.vue';
import ProgressBar from '@/components/page/components/article/progress.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 { export default {
components: { components: {
JournalSelector, JournalSelector,
ProgressBar ProgressBar
}, },
data() { data() {
// 自定义校验禁止QQ邮箱 // 自定义校验禁止QQ邮箱
const validateNotQQEmail = (rule, value, callback) => { const validateNotQQEmail = (rule, value, callback) => {
// 正则匹配QQ邮箱以@qq.com结尾不区分大小写 // 正则匹配QQ邮箱以@qq.com结尾不区分大小写
const qqEmailReg = /@qq\.com$/i; const qqEmailReg = /@qq\.com$/i;
if (value) { // 若有值必填校验已通过则检查是否为QQ邮箱 if (value) {
if (qqEmailReg.test(value)) { // 若有值必填校验已通过则检查是否为QQ邮箱
callback(new Error('QQ email is not allowed')); // 不允许QQ邮箱 if (qqEmailReg.test(value)) {
} else { callback(new Error('QQ email is not allowed')); // 不允许QQ邮箱
callback(); // 校验通过 } else {
} callback(); // 校验通过
} else { }
callback(); // 空值由必填校验处理,这里不重复判断 } else {
} callback(); // 空值由必填校验处理,这里不重复判断
}; }
};
return { return {
formList: [],
stepStatus: [],
isShowProgress: false, isShowProgress: false,
hasAIContent: ['1'], hasAIContent: ['1'],
isHasAI: '0', isHasAI: '0',
@@ -1080,8 +1109,7 @@ export default {
department: '', department: '',
affiliation: '', affiliation: '',
major_all: '' major_all: ''
}, }
], ],
countrys: [], countrys: [],
fileL_articleApproval: [], fileL_articleApproval: [],
@@ -1104,9 +1132,9 @@ export default {
trigger: 'blur' trigger: 'blur'
}, },
{ {
validator: validateNotQQEmail, // 绑定自定义校验 validator: validateNotQQEmail, // 绑定自定义校验
trigger: 'blur' trigger: 'blur'
} }
], ],
department: [ department: [
{ {
@@ -1395,9 +1423,7 @@ export default {
immediate: true immediate: true
}, },
form: { form: {
handler(e) { handler(e) {},
// console.log(e)
},
deep: true deep: true
} }
}, },
@@ -1421,6 +1447,10 @@ export default {
upload_articleApproval: function () { upload_articleApproval: function () {
return this.baseUrl + 'api/Article/up_approval_file'; return this.baseUrl + 'api/Article/up_approval_file';
}, },
//图片版权
upload_articleCopyright: function () {
return this.baseUrl + 'api/Article/up_approval_file';
},
upload_coverLetter: function () { upload_coverLetter: function () {
return this.baseUrl + 'api/Article/up_file/type/coverLetter'; return this.baseUrl + 'api/Article/up_file/type/coverLetter';
}, },
@@ -1441,6 +1471,30 @@ export default {
} }
}, },
methods: { 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() { async validateAllReviewers() {
// 遍历所有表单实例 // 遍历所有表单实例
@@ -1705,6 +1759,18 @@ export default {
.catch((err) => { .catch((err) => {
console.log(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.$api.post('api/Admin/getCountrys').then((res) => {
this.countrys = 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.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); console.log('🚀 ~ checkAuthor ~ value:', value);
var isHaveAuthor = this.form.authorList.findIndex((e) => { var isHaveAuthor = this.form.authorList.findIndex((e) => {
if (e.email == value.email) { if (e.email == value.email) {
@@ -1857,8 +1912,9 @@ export default {
value.load = false; value.load = false;
value.sort = num; value.sort = num;
value.article_id = this.stagingID; value.article_id = this.stagingID;
this.$api.post('api/Article/addAuthor', value).then((res) => { await this.$api.post('api/Article/addAuthor', value).then(async (res) => {
this.TempoAuthor(); await this.TempoAuthor();
this.$message.success('Added successfully'); this.$message.success('Added successfully');
}); });
}, },
@@ -2213,6 +2269,10 @@ export default {
// } // }
// return ismau; // return ismau;
}, },
beforeupload_articleCopyright(file) {
},
beforeupload_coverLetter(file) { beforeupload_coverLetter(file) {
// const isWORd = // const isWORd =
// file.type === 'application/msword' || // file.type === 'application/msword' ||
@@ -2288,6 +2348,18 @@ export default {
} }
console.log(this.form); 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) { upSuccess_coverLetter(res, file) {
if (res.code == 0) { if (res.code == 0) {
this.form.coverLetter = 'coverLetter/' + res.upurl; this.form.coverLetter = 'coverLetter/' + res.upurl;
@@ -2317,15 +2389,13 @@ export default {
this.$message.error('service error' + res.msg); this.$message.error('service error' + res.msg);
} }
}, },
// 进度回调:实时获取进度 // 进度回调:实时获取进度
handleProgress(event, file, fileList) { handleProgress(event, file, fileList) {
this.isShowProgress=true this.isShowProgress = true;
// event.percentage 就是当前进度(百分比,浮点数) // event.percentage 就是当前进度(百分比,浮点数)
this.uploadPercentage = Math.round(event.percent); // 取整数 this.uploadPercentage = Math.round(event.percent); // 取整数
},
},
addWordTablesList(tables) { addWordTablesList(tables) {
var data = { var data = {
article_id: this.stagingID, article_id: this.stagingID,
@@ -2339,20 +2409,18 @@ export default {
this.$api.post('api/Article/addArticleTable', data).then((res) => { this.$api.post('api/Article/addArticleTable', data).then((res) => {
this.isShowCommonWord = true; this.isShowCommonWord = true;
setTimeout(() => { setTimeout(() => {
this.isShowProgress=false; this.isShowProgress = false;
},500) }, 500);
}); });
}, },
upLoadWordTables() {}, upLoadWordTables() {},
upSuccess_manuscirpt(res, File) { upSuccess_manuscirpt(res, File) {
// console.log('file at line 2174:', file.raw) // console.log('file at line 2174:', file.raw)
if (File) { if (File) {
var that = this; var that = this;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function (e) { reader.onload = function (e) {
that.$commonJS.extractWordTablesToArrays(File.raw, function (wordTables) { that.$commonJS.extractWordTablesToArrays(File.raw, function (wordTables) {
that.addWordTablesList(wordTables); that.addWordTablesList(wordTables);
}); });
}; };
@@ -2384,6 +2452,9 @@ export default {
clearUploadedFile() { clearUploadedFile() {
this.$refs['uploadFile'].clearFiles(); this.$refs['uploadFile'].clearFiles();
}, },
clearUploadedCopyrightFile() {
this.$refs['articleCopyright'].clearFiles();
},
//超出传送文件个数限制 //超出传送文件个数限制
alertlimit() { alertlimit() {
this.$message.error('The maximum number of uploaded files has been exceeded'); this.$message.error('The maximum number of uploaded files has been exceeded');
@@ -2393,6 +2464,10 @@ export default {
this.form.approval_file = ''; this.form.approval_file = '';
this.fileL_articleApproval = []; this.fileL_articleApproval = [];
}, },
removefilearticleCopyright(file, fileList) {
this.form.copyright_file = '';
this.fileL_articleCopyright = [];
},
removefilecoverLetter(file, fileList) { removefilecoverLetter(file, fileList) {
this.form.coverLetter = ''; this.form.coverLetter = '';
this.fileL_coverLetter = []; this.fileL_coverLetter = [];
@@ -2604,6 +2679,7 @@ export default {
} else if (this.move_step < e) { } else if (this.move_step < e) {
this.$refs.articleform.validate((valid) => { this.$refs.articleform.validate((valid) => {
if (valid) { if (valid) {
// deepEqual(this.form, this.oldForm);
if (this.move_step == 1) { if (this.move_step == 1) {
//暂时注销 start //暂时注销 start
// var flist = this.keywordsList; // var flist = this.keywordsList;
@@ -2618,15 +2694,16 @@ export default {
// if (res.code == 0) { // if (res.code == 0) {
// this.stagingID = res.data.article_id; // this.stagingID = res.data.article_id;
// this.form.article_id = res.data.article_id; // this.form.article_id = res.data.article_id;
// this.$message.success('Saving succeeded!');
// this.$message.success('Saving succeeded!');
//暂时注销 End //暂时注销 End
this.move_step = 2; //进行步骤 this.move_step = 2; //进行步骤
this.show_step = 2; //显示内容 this.show_step = 2; //显示内容
//暂时注销 start //暂时注销 start
// } else { // } else {
// this.$message.error(res.msg); // // this.$message.error(res.msg);
// } // }
// }); // });
//暂时注销 End //暂时注销 End
@@ -2692,15 +2769,10 @@ export default {
// 点击进行下一步 // 点击进行下一步
onStep(e) { 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) => { this.$refs.articleform.validate((valid) => {
if (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 == '') { 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'); this.$message.error('Please describe how artificial intelligence is utilized in this article');
return false; return false;
@@ -2710,13 +2782,7 @@ export default {
this.$message.error('Please select the Journal'); this.$message.error('Please select the Journal');
return false; return false;
} }
// this.onStaging(1)
// setTimeout(() => {
// console.log('456')
// this.move_step = 2 //进行步骤
// this.show_step = 2 //显示内容
// }, 1000);
var flist = this.keywordsList; var flist = this.keywordsList;
var fstr = ''; var fstr = '';
for (var fu in flist) { for (var fu in flist) {
@@ -2730,10 +2796,7 @@ export default {
this.form.major = this.majorValueList this.form.major = this.majorValueList
.map((item) => (item.selectedValue.length > 0 ? item.selectedValue[item.selectedValue.length - 1] : [])) .map((item) => (item.selectedValue.length > 0 ? item.selectedValue[item.selectedValue.length - 1] : []))
.toString(','); .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) => { this.$api.post('api/Article/addArticlePart1', this.form).then((res) => {
@@ -2743,6 +2806,7 @@ export default {
this.$message.success('Saving succeeded!'); this.$message.success('Saving succeeded!');
this.move_step = 2; //进行步骤 this.move_step = 2; //进行步骤
this.show_step = 2; //显示内容 this.show_step = 2; //显示内容
this.initStepStatus();
} else { } else {
this.$message.error(res.msg); this.$message.error(res.msg);
} }
@@ -2819,6 +2883,16 @@ export default {
this.$message.error('Please select the Journal'); this.$message.error('Please select the Journal');
return false; 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 flist = this.keywordsList;
var fstr = ''; var fstr = '';
for (var fu in flist) { for (var fu in flist) {
@@ -2826,7 +2900,6 @@ export default {
fstr += flist[fu].ke.trim() + ','; 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.keyWords = fstr == '' ? '' : fstr.substring(0, fstr.length - 1);
this.form.major = this.majorValueList.map((item) => item.selectedValue[item.selectedValue.length - 1]).toString(','); this.form.major = this.majorValueList.map((item) => item.selectedValue[item.selectedValue.length - 1]).toString(',');
@@ -2842,6 +2915,7 @@ export default {
if (res.code == 0) { if (res.code == 0) {
this.stagingID = res.data.article_id; this.stagingID = res.data.article_id;
this.form.article_id = res.data.article_id; this.form.article_id = res.data.article_id;
this.initStepStatus();
this.$message.success('Saving succeeded!'); this.$message.success('Saving succeeded!');
} else { } else {
this.$message.error(res.msg); this.$message.error(res.msg);
@@ -2927,7 +3001,7 @@ export default {
.then((res) => { .then((res) => {
if (res.code == 0) { if (res.code == 0) {
this.form.manuscirptId = res.data.file_id; this.form.manuscirptId = res.data.file_id;
setTimeout(() => { setTimeout(() => {
// that.getWordTablesList(); // that.getWordTablesList();
// that.getWordimgList(); // that.getWordimgList();
@@ -2985,12 +3059,12 @@ export default {
} }
}, },
// 读取 // 读取
Temporary() { async Temporary() {
this.$api this.$api
.post('api/Article/getSaveArticleDetail', { .post('api/Article/getSaveArticleDetail', {
article_id: this.stagingID article_id: this.stagingID
}) })
.then((res) => { .then(async (res) => {
console.log(res, '已经保存的值'); console.log(res, '已经保存的值');
if (res.code == 0) { if (res.code == 0) {
// 基本信息 // 基本信息
@@ -3041,21 +3115,22 @@ export default {
} }
this.showFiles(); this.showFiles();
} }
// await this.initStepStatus();
} else { } else {
this.$message.error(res.msg); this.$message.error(res.msg);
} }
}); });
this.TempoAuthor(); await this.TempoAuthor();
}, },
// 读取作者 // 读取作者
TempoAuthor() { async TempoAuthor() {
this.$api this.$api
.post('api/Article/getAuthors', { .post('api/Article/getAuthors', {
article_id: this.stagingID article_id: this.stagingID
}) })
.then((res) => { .then(async(res) => {
if (res.code == 0) { if (res.code == 0) {
// 作者 // 作者
if (res.data.authors.length > 0) { if (res.data.authors.length > 0) {
@@ -3078,9 +3153,11 @@ export default {
} else { } else {
this.form.authorList = []; this.form.authorList = [];
} }
} else { } else {
this.$message.error(res.msg); this.$message.error(res.msg);
} }
await this.initStepStatus();
}); });
}, },
@@ -3116,23 +3193,23 @@ export default {
.formTopics { .formTopics {
width: 100%; width: 100%;
} }
/* f8fbff */
.step_list_new { .step_list_new {
background-color: #f8fbff; background-color: #f8f8f8;
box-shadow: 2px 30px 15px -20px #ebf5ff inset; box-shadow: 2px 30px 15px -20px #eee inset;
padding-top: 26px;
padding-bottom: 10px;
border-top-left-radius: 10px; border-top-left-radius: 10px;
border-top-right-radius: 10px; border-top-right-radius: 10px;
width: calc(100% - 20px); width: calc(100% - 20px);
/* margin-bottom: 10px; */
margin-top: 20px; margin-top: 20px;
} }
::v-deep .step_list_new .el-step__icon.is-text { ::v-deep .step_list_new .el-step__icon.is-text {
border-radius: 0 !important; border: 3px solid !important;
border: none !important;
} }
::v-deep .step_list_new .el-step__icon { ::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 { ::v-deep .step_list_new .el-step__head.is-process {
color: #006699 !important; color: #006699 !important;
@@ -3143,14 +3220,58 @@ export default {
font-weight: bold !important; font-weight: bold !important;
} }
::v-deep .step_list_new .el-step.is-center .el-step__line { ::v-deep .step_list_new .el-step.is-center .el-step__line {
left: 58% !important; left: 57% !important;
right: -43% !important; right: -43% !important;
z-index: 2 !important;
} }
.manu_add h3 { .manu_add h3 {
font-size: 16px; font-size: 16px;
/* margin-bottom: 15px; */
margin-top: 0px; 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>
<style> <style>
@@ -3269,15 +3390,14 @@ export default {
.manu_add .bag_color { .manu_add .bag_color {
border-top: 1px solid #88888830; border-top: 1px solid #88888830;
padding-top: 15px; padding-top: 30px;
background-color: #fff; background-color: #fff;
/* box-shadow: 2px 30px 15px -20px #ebf5ff inset; */
} }
.manu_add .bag_color > div { .manu_add .bag_color > div {
padding: 0px 15px; padding: 0px 15px;
/* background-color: #fff; */
/* box-shadow: 2px -30px 15px -20px #ebf5ff inset; */
} }
.manu_add .pro_ceed { .manu_add .pro_ceed {
@@ -3340,8 +3460,8 @@ export default {
} }
.step_list > div.C_style { .step_list > div.C_style {
background-color: #f8fbff; background-color: #f8fbff !important;
box-shadow: 2px 30px 15px -20px #ebf5ff inset; box-shadow: 2px 30px 15px -20px #ebf5ff inset !important;
} }
.step_list > div.C_style > div { .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,150 +1,136 @@
<template> <template>
<div class="journal-selector relative w-full max-w-md" ref="journalSelector"> <div class="journal-selector relative w-full max-w-md" ref="journalSelector">
<!-- 使用el-input作为输入框 -->
<div v-if="!selectedJournal">
<!-- 使用el-input作为输入框 --> <el-input
<div v-if="!selectedJournal"> clearable
<el-input clearable v-model="searchTerm"
v-model="searchTerm" @focus="showDropdown = true"
size="medium"
@focus="showDropdown = true" class="journal-input"
size="medium" suffix-icon="el-icon-search"
class="journal-input" suffix-icon="el-icon-search" >
> <!-- 搜索图标插槽 -->
<!-- 搜索图标插槽 -->
<!-- 已选择时显示清除图标 -->
<i
<!-- 已选择时显示清除图标 --> slot="suffix"
<i v-if="selectedJournal"
slot="suffix" style="line-height: 38px"
v-if="selectedJournal" class="fas fa-times text-gray-400 hover:text-red-500 cursor-pointer el-input__icon"
class="fas fa-times text-gray-400 hover:text-red-500 cursor-pointer el-input__icon" @click.stop="clearSelection"
@click.stop="clearSelection" ></i>
></i> </el-input>
</el-input>
<!-- 下拉列表 -->
<!-- 下拉列表 --> <div class="journal-dropdown" v-if="showDropdown && filteredJournals.length">
<div <div class="journal-item" v-for="journal in filteredJournals" :key="journal.id" @click="selectJournal(journal)">
class="journal-dropdown " <div class="flexBox alignCenter justBetween">
v-if="showDropdown && filteredJournals.length" <img
> src="https://www.tmrjournals.com/public/journalicon/20251010/28267562ad187206ca77d64bcd7a138f.jpg"
:alt="journal.title"
<div class=""
class="journal-item " style="width: 40px; height: 50px"
v-for="journal in filteredJournals" />
:key="journal.id" <div class="journal-title">{{ journal.title }}</div>
@click="selectJournal(journal)" </div>
> </div>
<div class="flexBox alignCenter justBetween"> </div>
<img src="https://picsum.photos/40/40?random=1" :alt="journal.title" class="">
<div class="journal-title">{{ journal.title }}</div> <!-- 无结果提示 -->
<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>
</div>
</div>
</div>
<!-- 选中的期刊 -->
<div class="journal-selected" v-if="selectedJournal && !showDropdown">
<div class="flexBox alignCenter">
<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">
{{ selectedJournal.scope }}
</div>
<span class="journal-close" @click="closeSelection">
<i class="el-icon-close"></i>
</span>
</div> </div>
</div>
</div> </div>
<!-- 无结果提示 -->
<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>
</div>
</div>
</div>
<!-- 选中的期刊 -->
<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">
<span class="journal-selected-title">{{ selectedJournal.title }}</span>
</div>
<div class="journal-info">
{{ selectedJournal.scope }}
</div>
<span class="journal-close" @click="closeSelection">
<i class="el-icon-close"></i>
</span>
</div>
</div>
</template> </template>
<script> <script>
export default { export default {
name: 'JournalSelector', name: 'JournalSelector',
props: { props: {
list: { list: {
type: Array, type: Array,
default: () => [] default: () => []
},
check_item: {
type: Object,
default: () => ({})
}
}, },
check_item: { data() {
type: Object, return {
default: () => ({}) searchTerm: '',
showDropdown: false,
selectedJournal: null,
journals: []
};
}, },
}, watch: {
data() { list: {
return { handler(newVal) {
searchTerm: '', // this.journals = this.list;
showDropdown: false, // console.log('this.journals at line 96:', this.journals)
selectedJournal: null, },
journals: [] deep: true
}; },
}, check_item: {
watch: { handler(newVal) {
list: { // if (newVal.id) {
handler(newVal) { if (JSON.stringify(this.check_item) !== '{}') {
// this.journals = this.list; this.selectedJournal = this.check_item;
// console.log('this.journals at line 96:', this.journals) } else {
}, this.selectedJournal = null;
deep: true }
console.log('this.selectedJournal at line 113:', this.selectedJournal);
// }
},
deep: true
}
}, },
check_item: { computed: {
handler(newVal) { // 过滤期刊列表
// if (newVal.id) { filteredJournals() {
if(JSON.stringify(this.check_item)!=='{}'){ if (!this.searchTerm) {
this.selectedJournal = this.check_item; return this.journals;
}else{ }
this.selectedJournal = null; const term = this.searchTerm.toLowerCase();
}
console.log('this.selectedJournal at line 113:', this.selectedJournal)
return this.journals.filter(
// } (journal) =>
}, journal.abbr.toLowerCase().includes(term) ||
deep: true journal.title.toLowerCase().includes(term) ||
} journal.title.toLowerCase().includes(this.searchTerm) ||
}, journal.title.includes(this.searchTerm)
computed: { );
// 过滤期刊列表 }
filteredJournals() { },
if (!this.searchTerm) { methods: {
return this.journals; getJournalList() {
} this.$api
const term = this.searchTerm.toLowerCase();
console.log('term at line 109:', 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') .post('api/Article/getJournal')
.then((res) => { .then((res) => {
this.journals = res; this.journals = res;
@@ -152,161 +138,160 @@ export default {
.catch((err) => { .catch((err) => {
console.log(err); console.log(err);
}); });
}, },
closeSelection(){ closeSelection() {
this.selectedJournal=null
this.searchTerm = '';
this.showDropdown = false;
this.$emit('selected', '')
},
// 选择期刊
selectJournal(journal) {
this.selectedJournal = journal;
this.searchTerm = '';
this.showDropdown = false;
// 触发选中事件,方便父组件获取数据
this.$emit('selected', journal);
this.$emit('selected', journal)
},
// 清除选择
clearSelection() {
this.selectedJournal = null;
this.searchTerm = '';
this.$emit('cleared');
},
// 点击外部关闭下拉框
handleClickOutside(event) {
if (this.showDropdown && !this.$refs.journalSelector.contains(event.target)) {
this.showDropdown = false;
}
}
},
mounted() {
if(JSON.stringify(this.check_item)!=='{}'){
this.selectedJournal = this.check_item;
}else{
this.selectedJournal = null; this.selectedJournal = null;
} this.searchTerm = '';
this.getJournalList(); this.showDropdown = false;
document.addEventListener('click', this.handleClickOutside); this.$emit('selected', '');
}, },
beforeDestroy() { // 选择期刊
document.removeEventListener('click', this.handleClickOutside); selectJournal(journal) {
} this.selectedJournal = journal;
this.searchTerm = '';
this.showDropdown = false;
// 触发选中事件,方便父组件获取数据
this.$emit('selected', journal);
this.$emit('selected', journal);
},
// 清除选择
clearSelection() {
this.selectedJournal = null;
this.searchTerm = '';
this.$emit('cleared');
},
// 点击外部关闭下拉框
handleClickOutside(event) {
if (this.showDropdown && !this.$refs.journalSelector.contains(event.target)) {
this.showDropdown = false;
}
}
},
mounted() {
if (JSON.stringify(this.check_item) !== '{}') {
this.selectedJournal = this.check_item;
} else {
this.selectedJournal = null;
}
this.getJournalList();
document.addEventListener('click', this.handleClickOutside);
},
beforeDestroy() {
document.removeEventListener('click', this.handleClickOutside);
}
}; };
</script> </script>
<style scoped> <style scoped>
/* 适配el-input的样式 */ /* 适配el-input的样式 */
.journal-input { .journal-input {
--el-input-height: 40px; cursor: pointer; --el-input-height: 40px;
cursor: pointer;
} }
.journal-selector .title { .journal-selector .title {
font-size: 14px; font-size: 14px;
font-weight: 700; font-weight: 700;
color: #333; color: #333;
} }
.journal-input .el-input__inner { .journal-input .el-input__inner {
padding-left: 38px !important; padding-left: 38px !important;
border-radius: 8px; border-radius: 8px;
border-color: #e4e4e4; border-color: #e4e4e4;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.journal-input .el-input__inner:focus { .journal-input .el-input__inner:focus {
border-color: #3b82f6; border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
} }
.journal-input .el-input__prefix { .journal-input .el-input__prefix {
left: 12px; left: 12px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
} }
.journal-input .el-input__suffix { .journal-input .el-input__suffix {
right: 12px; right: 12px;
} }
/* 下拉列表样式 */ /* 下拉列表样式 */
.journal-dropdown { .journal-dropdown {
border: 1px solid #f0f0f0; border: 1px solid #f0f0f0;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
border-radius: 8px; border-radius: 8px;
margin-top: 6px; margin-top: 6px;
padding: 12px 0; padding: 12px 0 0;
box-sizing: border-box; cursor: pointer; box-sizing: border-box;
cursor: pointer;
} }
/* 列表项样式 */ /* 列表项样式 */
.journal-item { .journal-item {
padding: 0 15px; padding: 0 15px;
box-sizing: border-box; box-sizing: border-box;
border-bottom: 1px solid #f8f8f8; border-bottom: 1px solid #f8f8f8;
margin: 0; margin: 0;
color: #666; color: #666;
margin-bottom: 10px; margin-bottom: 10px;
cursor: pointer; cursor: pointer;
} }
.journal-item:last-child { .journal-item:last-child {
border-bottom: none; border-bottom: none;
} }
/* 选中项样式 */ /* 选中项样式 */
.journal-selected { .journal-selected {
border-radius: 6px; border-radius: 6px;
/* margin-top: 8px; */ /* margin-top: 8px; */
border: 1px solid #DCDFE6; border: 1px solid #dcdfe6;
font-size: 14px;color: #8c8d8f; font-size: 14px;
padding: 10px; color: #8c8d8f;
cursor: pointer; padding: 10px;
position: relative; cursor: pointer;
position: relative;
} }
.journal-selected-title{ .journal-selected-title {
font-size: 14px; font-size: 14px;
font-weight: 700; font-weight: 700;
color: #333; color: #333;
margin-left: 14px; margin-left: 14px;
line-height: 40px; line-height: 40px;
} }
/* 清除按钮样式 */ /* 清除按钮样式 */
.journal-clear { .journal-clear {
transition: all 0.2s; transition: all 0.2s;
} }
.flexBox{ .flexBox {
display: flex; display: flex;
} }
.alignCenter{ .alignCenter {
align-items: center; align-items: center;
} }
.justBetween{ .justBetween {
justify-content: space-between; justify-content: space-between;
} }
.journal-title{ .journal-title {
width: calc(100% - 60px); width: calc(100% - 60px);
} }
.journal-info { .journal-info {
margin-top: 8px; margin-top: 8px;
line-height: 20px; line-height: 16px;
text-align: justify; text-align: justify;
} }
.journal-close{ .journal-close {
position: absolute; position: absolute;
top: 0px; top: 0px;
right: 6px; right: 6px;
font-size: 16px; font-size: 16px;
color: #8c8d8f; color: #8c8d8f;
cursor: pointer; cursor: pointer;
} }
.nofound{ .nofound {
color: #8c8d8f; color: #8c8d8f;
padding-left: 10px; padding-left: 10px;
} }
</style> </style>

View File

@@ -136,7 +136,7 @@
<!-- 表格缩略图区域 --> <!-- 表格缩略图区域 -->
<li > <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 <div