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

@@ -2,26 +2,45 @@
<div style="">
<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 }"
<div class="step_list_new" >
<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">
@@ -400,7 +428,7 @@
<div class="el-upload__tip" slot="tip">Only word files can be uploaded(.doc,.docx)</div>
</el-upload>
</el-form-item> -->
<el-form-item label="Supplementary Material :" label-width="200px">
<el-upload
class="upload-demo up_newstyle"
@@ -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,10 +822,9 @@
attention.
</p>
<common-word-html
:articleId="stagingID"
imgHeight="120px"
v-if="isShowCommonWord&&stagingID"
v-if="isShowCommonWord && stagingID"
style="margin-top: 10px; box-sizing: border-box; background-color: #fff"
></common-word-html>
</div>
@@ -946,27 +969,33 @@
<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,
ProgressBar
},
data() {
// 自定义校验禁止QQ邮箱
const validateNotQQEmail = (rule, value, callback) => {
// 正则匹配QQ邮箱以@qq.com结尾不区分大小写
const qqEmailReg = /@qq\.com$/i;
if (value) { // 若有值必填校验已通过则检查是否为QQ邮箱
if (qqEmailReg.test(value)) {
callback(new Error('QQ email is not allowed')); // 不允许QQ邮箱
} else {
callback(); // 校验通过
}
} else {
callback(); // 空值由必填校验处理,这里不重复判断
}
};
// 自定义校验禁止QQ邮箱
const validateNotQQEmail = (rule, value, callback) => {
// 正则匹配QQ邮箱以@qq.com结尾不区分大小写
const qqEmailReg = /@qq\.com$/i;
if (value) {
// 若有值必填校验已通过则检查是否为QQ邮箱
if (qqEmailReg.test(value)) {
callback(new Error('QQ email is not allowed')); // 不允许QQ邮箱
} else {
callback(); // 校验通过
}
} else {
callback(); // 空值由必填校验处理,这里不重复判断
}
};
return {
formList: [],
stepStatus: [],
isShowProgress: false,
hasAIContent: ['1'],
isHasAI: '0',
@@ -1080,8 +1109,7 @@ export default {
department: '',
affiliation: '',
major_all: ''
},
}
],
countrys: [],
fileL_articleApproval: [],
@@ -1104,9 +1132,9 @@ export default {
trigger: 'blur'
},
{
validator: validateNotQQEmail, // 绑定自定义校验
trigger: 'blur'
}
validator: validateNotQQEmail, // 绑定自定义校验
trigger: 'blur'
}
],
department: [
{
@@ -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;
@@ -2317,15 +2389,13 @@ export default {
this.$message.error('service error' + res.msg);
}
},
// 进度回调:实时获取进度
handleProgress(event, file, fileList) {
this.isShowProgress=true
// event.percentage 就是当前进度(百分比,浮点数)
this.uploadPercentage = Math.round(event.percent); // 取整数
},
// 进度回调:实时获取进度
handleProgress(event, file, fileList) {
this.isShowProgress = true;
// event.percentage 就是当前进度(百分比,浮点数)
this.uploadPercentage = Math.round(event.percent); // 取整数
},
addWordTablesList(tables) {
var data = {
article_id: this.stagingID,
@@ -2339,20 +2409,18 @@ export default {
this.$api.post('api/Article/addArticleTable', data).then((res) => {
this.isShowCommonWord = true;
setTimeout(() => {
this.isShowProgress=false;
},500)
this.isShowProgress = false;
}, 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,15 +2694,16 @@ 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!');
// this.$message.success('Saving succeeded!');
//暂时注销 End
this.move_step = 2; //进行步骤
this.show_step = 2; //显示内容
//暂时注销 start
// } else {
// this.$message.error(res.msg);
// }
// } else {
// // this.$message.error(res.msg);
// }
// });
//暂时注销 End
@@ -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,13 +2782,7 @@ 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 = '';
for (var fu in flist) {
@@ -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);
@@ -2927,7 +3001,7 @@ export default {
.then((res) => {
if (res.code == 0) {
this.form.manuscirptId = res.data.file_id;
setTimeout(() => {
// that.getWordTablesList();
// that.getWordimgList();
@@ -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,150 +1,136 @@
<template>
<div class="journal-selector relative w-full max-w-md" ref="journalSelector">
<!-- 使用el-input作为输入框 -->
<div v-if="!selectedJournal">
<el-input clearable
v-model="searchTerm"
@focus="showDropdown = true"
size="medium"
class="journal-input" suffix-icon="el-icon-search"
>
<!-- 搜索图标插槽 -->
<!-- 已选择时显示清除图标 -->
<i
slot="suffix"
v-if="selectedJournal"
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="flexBox alignCenter justBetween">
<img src="https://picsum.photos/40/40?random=1" :alt="journal.title" class="">
<div class="journal-title">{{ journal.title }}</div>
<div class="journal-selector relative w-full max-w-md" ref="journalSelector">
<!-- 使用el-input作为输入框 -->
<div v-if="!selectedJournal">
<el-input
clearable
v-model="searchTerm"
@focus="showDropdown = true"
size="medium"
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="flexBox alignCenter justBetween">
<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="">
<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
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>
<script>
export default {
name: 'JournalSelector',
props: {
list: {
type: Array,
default: () => []
name: 'JournalSelector',
props: {
list: {
type: Array,
default: () => []
},
check_item: {
type: Object,
default: () => ({})
}
},
check_item: {
type: Object,
default: () => ({})
data() {
return {
searchTerm: '',
showDropdown: false,
selectedJournal: null,
journals: []
};
},
},
data() {
return {
searchTerm: '',
showDropdown: false,
selectedJournal: null,
journals: []
};
},
watch: {
list: {
handler(newVal) {
// this.journals = this.list;
// console.log('this.journals at line 96:', this.journals)
},
deep: true
watch: {
list: {
handler(newVal) {
// this.journals = this.list;
// console.log('this.journals at line 96:', this.journals)
},
deep: true
},
check_item: {
handler(newVal) {
// if (newVal.id) {
if (JSON.stringify(this.check_item) !== '{}') {
this.selectedJournal = this.check_item;
} else {
this.selectedJournal = null;
}
console.log('this.selectedJournal at line 113:', this.selectedJournal);
// }
},
deep: true
}
},
check_item: {
handler(newVal) {
// if (newVal.id) {
if(JSON.stringify(this.check_item)!=='{}'){
this.selectedJournal = this.check_item;
}else{
this.selectedJournal = null;
}
console.log('this.selectedJournal at line 113:', this.selectedJournal)
// }
},
deep: true
}
},
computed: {
// 过滤期刊列表
filteredJournals() {
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)||
journal.title.toLowerCase().includes(term)||
journal.title.toLowerCase().includes(this.searchTerm)||
journal.title.includes(this.searchTerm)
);
}
},
methods: {
getJournalList(){
this.$api
computed: {
// 过滤期刊列表
filteredJournals() {
if (!this.searchTerm) {
return this.journals;
}
const term = this.searchTerm.toLowerCase();
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')
.then((res) => {
this.journals = res;
@@ -152,161 +138,160 @@ export default {
.catch((err) => {
console.log(err);
});
},
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{
},
closeSelection() {
this.selectedJournal = null;
}
this.getJournalList();
document.addEventListener('click', this.handleClickOutside);
},
beforeDestroy() {
document.removeEventListener('click', this.handleClickOutside);
}
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.getJournalList();
document.addEventListener('click', this.handleClickOutside);
},
beforeDestroy() {
document.removeEventListener('click', this.handleClickOutside);
}
};
</script>
<style scoped>
/* 适配el-input的样式 */
.journal-input {
--el-input-height: 40px; cursor: pointer;
--el-input-height: 40px;
cursor: pointer;
}
.journal-selector .title {
font-size: 14px;
font-weight: 700;
color: #333;
font-size: 14px;
font-weight: 700;
color: #333;
}
.journal-input .el-input__inner {
padding-left: 38px !important;
border-radius: 8px;
border-color: #e4e4e4;
transition: all 0.2s ease;
padding-left: 38px !important;
border-radius: 8px;
border-color: #e4e4e4;
transition: all 0.2s ease;
}
.journal-input .el-input__inner:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.journal-input .el-input__prefix {
left: 12px;
top: 50%;
transform: translateY(-50%);
left: 12px;
top: 50%;
transform: translateY(-50%);
}
.journal-input .el-input__suffix {
right: 12px;
right: 12px;
}
/* 下拉列表样式 */
.journal-dropdown {
border: 1px solid #f0f0f0;
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;
border: 1px solid #f0f0f0;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
border-radius: 8px;
margin-top: 6px;
padding: 12px 0 0;
box-sizing: border-box;
cursor: pointer;
}
/* 列表项样式 */
.journal-item {
padding: 0 15px;
box-sizing: border-box;
border-bottom: 1px solid #f8f8f8;
margin: 0;
color: #666;
margin-bottom: 10px;
cursor: pointer;
padding: 0 15px;
box-sizing: border-box;
border-bottom: 1px solid #f8f8f8;
margin: 0;
color: #666;
margin-bottom: 10px;
cursor: pointer;
}
.journal-item:last-child {
border-bottom: none;
border-bottom: none;
}
/* 选中项样式 */
.journal-selected {
border-radius: 6px;
/* margin-top: 8px; */
border: 1px solid #DCDFE6;
font-size: 14px;color: #8c8d8f;
padding: 10px;
cursor: pointer;
position: relative;
border-radius: 6px;
/* margin-top: 8px; */
border: 1px solid #dcdfe6;
font-size: 14px;
color: #8c8d8f;
padding: 10px;
cursor: pointer;
position: relative;
}
.journal-selected-title{
font-size: 14px;
font-weight: 700;
color: #333;
margin-left: 14px;
line-height: 40px;
.journal-selected-title {
font-size: 14px;
font-weight: 700;
color: #333;
margin-left: 14px;
line-height: 40px;
}
/* 清除按钮样式 */
.journal-clear {
transition: all 0.2s;
transition: all 0.2s;
}
.flexBox{
display: flex;
.flexBox {
display: flex;
}
.alignCenter{
align-items: center;
.alignCenter {
align-items: center;
}
.justBetween{
justify-content: space-between;
.justBetween {
justify-content: space-between;
}
.journal-title{
width: calc(100% - 60px);
.journal-title {
width: calc(100% - 60px);
}
.journal-info {
margin-top: 8px;
line-height: 20px;
text-align: justify;
margin-top: 8px;
line-height: 16px;
text-align: justify;
}
.journal-close{
position: absolute;
top: 0px;
right: 6px;
font-size: 16px;
color: #8c8d8f;
cursor: pointer;
.journal-close {
position: absolute;
top: 0px;
right: 6px;
font-size: 16px;
color: #8c8d8f;
cursor: pointer;
}
.nofound{
color: #8c8d8f;
padding-left: 10px;
.nofound {
color: #8c8d8f;
padding-left: 10px;
}
</style>
</style>

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