Compare commits
13 Commits
052d0e4ca4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 77c0fac34d | |||
| 74a6c4b74b | |||
| 6050dd365d | |||
| f67d8d5600 | |||
| 67a4875b01 | |||
| 8e59702f0b | |||
| ea1564018e | |||
| 4d7c230abe | |||
| 8b9e35287c | |||
| 9234318139 | |||
| a4cbce2db7 | |||
| 56cda2c232 | |||
| 0c9ff0fe68 |
@@ -19,8 +19,8 @@ const service = axios.create({
|
||||
// baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换
|
||||
// baseURL: 'http://www.tougao.com/', //测试本地 记得切换
|
||||
// baseURL: 'http://192.168.110.110/tougao/public/index.php/',
|
||||
baseURL: '/api', //本地
|
||||
// baseURL: '/', //正式
|
||||
// baseURL: '/api', //本地
|
||||
baseURL: '/', //正式
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -176,6 +176,7 @@ export default {
|
||||
localStorage.removeItem('U_role');
|
||||
localStorage.removeItem('U_id');
|
||||
localStorage.removeItem('U_name');
|
||||
localStorage.removeItem('U_email');
|
||||
localStorage.removeItem('U_status');
|
||||
localStorage.removeItem('ms_journal_alias');
|
||||
localStorage.removeItem('journalTypeData');
|
||||
|
||||
@@ -174,6 +174,7 @@ export default {
|
||||
localStorage.removeItem('U_role');
|
||||
localStorage.removeItem('U_id');
|
||||
localStorage.removeItem('U_name');
|
||||
localStorage.removeItem('U_email');
|
||||
localStorage.removeItem('U_status');
|
||||
localStorage.removeItem('ms_journal_alias');
|
||||
localStorage.removeItem('journalTypeData');
|
||||
|
||||
@@ -20,8 +20,15 @@
|
||||
<template v-for="subItem in item.subs">
|
||||
<el-submenu v-if="subItem.subs" :index="subItem.index" :key="subItem.index + '-submenu'">
|
||||
<template slot="title">
|
||||
{{ subItem.title }}
|
||||
<!-- <el-badge is-dot :hidden="false">{{ subItem.title }}</el-badge> -->
|
||||
<el-badge
|
||||
v-if="subItem.index === '45'"
|
||||
is-dot
|
||||
:hidden="applyBadgeYouth <= 0"
|
||||
class="sidebar-submenu-apply-badge"
|
||||
>
|
||||
<span>{{ subItem.title }}</span>
|
||||
</el-badge>
|
||||
<template v-else>{{ subItem.title }}</template>
|
||||
</template>
|
||||
|
||||
<template v-for="(threeItem, i) in subItem.subs">
|
||||
@@ -35,8 +42,16 @@
|
||||
{{ fourItem.title }}
|
||||
</el-menu-item>
|
||||
</el-submenu>
|
||||
<el-menu-item v-else :index="threeItem.index" :key="threeItem.index + '-item'"
|
||||
>{{ threeItem.title }}
|
||||
<el-menu-item v-else :index="threeItem.index" :key="threeItem.index + '-item'">
|
||||
<el-badge
|
||||
v-if="threeItem.index === 'youthApplyList'"
|
||||
is-dot
|
||||
:hidden="applyBadgeYouth <= 0"
|
||||
class="sidebar-menu-youth-apply-badge"
|
||||
>
|
||||
<span class="sidebar-menu-youth-apply-badge__text">{{ threeItem.title }}</span>
|
||||
</el-badge>
|
||||
<template v-else>{{ threeItem.title }}</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-submenu>
|
||||
@@ -220,6 +235,8 @@ export default {
|
||||
user_cap: localStorage.getItem('U_role'),
|
||||
|
||||
menuList: [],
|
||||
/** 青年编委申请红点:Young Scientist 父级 + Apply 子项,数据来自 getYboardApplys */
|
||||
applyBadgeYouth: 0,
|
||||
items: [],
|
||||
// 作者
|
||||
author_items: [
|
||||
@@ -677,6 +694,9 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (String(this.userrole) === '1') {
|
||||
this.fetchApplyBadgeSummary();
|
||||
}
|
||||
// if(this.user_cap.includes(',board')||this.user_cap.includes('board_editor')||this.user_cap.includes('chief')||this.user_cap.includes('chief_editor')||this.user_cap.includes('deputy_editor')){
|
||||
// Promise.all([
|
||||
// this.$api
|
||||
@@ -845,10 +865,33 @@ export default {
|
||||
localStorage.setItem('collapse', this.collapse);
|
||||
bus.$emit('collapse-content', msg);
|
||||
});
|
||||
bus.$on('apply-badge-refresh', () => {
|
||||
this.fetchApplyBadgeSummary();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
bus.$off('apply-badge-refresh');
|
||||
},
|
||||
methods: {
|
||||
// 获取数据
|
||||
getDate() {}
|
||||
getDate() {},
|
||||
fetchApplyBadgeSummary() {
|
||||
if (String(this.userrole) !== '1') return;
|
||||
const editorId = localStorage.getItem('U_id');
|
||||
if (!editorId) return;
|
||||
this.$api
|
||||
.post('api/User/getYboardApplys', { editor_id: editorId })
|
||||
.then((res) => {
|
||||
if (res && res.code === 0 && res.data && Array.isArray(res.data.applys)) {
|
||||
this.applyBadgeYouth = res.data.applys.length > 0 ? 1 : 0;
|
||||
} else {
|
||||
this.applyBadgeYouth = 0;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.applyBadgeYouth = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -922,4 +965,38 @@ export default {
|
||||
.linkBar:hover {
|
||||
background: #00527a;
|
||||
}
|
||||
|
||||
/* Young Scientist 父级标题红点 */
|
||||
.sidebar-submenu-apply-badge {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sidebar-submenu-apply-badge ::v-deep .el-badge__content.is-dot {
|
||||
top: 28%;
|
||||
margin-top: 6px;
|
||||
right: -4px;
|
||||
border: 0;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Apply 子菜单行上的红点,与菜单行高对齐 */
|
||||
.sidebar-menu-youth-apply-badge {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.sidebar-menu-youth-apply-badge ::v-deep .el-badge__content.is-dot {
|
||||
top: 28%;
|
||||
margin-top: 6px;
|
||||
right: -4px;
|
||||
border: 0;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.sidebar-menu-youth-apply-badge__text {
|
||||
color: inherit;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,6 +20,13 @@ const en = {
|
||||
info: {
|
||||
realname: 'English names can only contain uppercase and lowercase letters, "-", and spaces'
|
||||
},
|
||||
articleAdd: {
|
||||
qqEmailAlertLine1: 'The publisher does not currently accept submissions from QQ Mail.',
|
||||
qqEmailAlertLine2Before: 'Please go to',
|
||||
qqEmailDashboardLink: 'Personal Center (Dashboard)',
|
||||
qqEmailAlertLine2After: ' to change your registered email.',
|
||||
qqEmailSubmitBlockedMsg: 'QQ Mail is not supported for submission. Please go to Personal Center (Dashboard) and change your email.'
|
||||
},
|
||||
total: {
|
||||
author: 'author',
|
||||
editor: 'editor',
|
||||
@@ -310,7 +317,8 @@ const en = {
|
||||
baseInfo: 'Base Information',
|
||||
country: 'Country',
|
||||
affiliation: 'Affiliation',
|
||||
researchAreas: 'Research areas'
|
||||
researchAreas: 'Research areas',
|
||||
unsubscribeStatus: 'Subscription status'
|
||||
},
|
||||
emptyMark: '-',
|
||||
fields: {
|
||||
@@ -327,7 +335,14 @@ const en = {
|
||||
detailCellEmpty: 'N/A',
|
||||
noFieldDetail: 'No publication details for these fields',
|
||||
exportWarn: 'Please select a research area, enter a keyword, or enter a field before exporting.',
|
||||
exportFailed: 'Export failed'
|
||||
exportFailed: 'Export failed',
|
||||
unsubscribeNormal: 'Subscribed',
|
||||
unsubscribeUnsubscribed: 'Unsubscribed',
|
||||
unsubscribeSwitchOn: 'Unsub',
|
||||
unsubscribeSwitchOff: 'Subscribed',
|
||||
unsubscribeMissingId: 'Missing expert ID, unable to switch unsubscribe status',
|
||||
unsubscribeUpdateSuccess: 'Unsubscribe status updated',
|
||||
unsubscribeUpdateFailed: 'Failed to update unsubscribe status'
|
||||
},
|
||||
countryManagement: {
|
||||
title: 'Country Management',
|
||||
@@ -980,8 +995,8 @@ colTitle: 'Template title',
|
||||
step3: 'References',
|
||||
step: 'step',
|
||||
Information: 'Fill in information',
|
||||
|
||||
|
||||
startPreAccept: 'Start the pre-acceptance process',
|
||||
startPreAcceptWithPayment: 'Start the pre-acceptance process and complete your payment',
|
||||
},
|
||||
Formula: {
|
||||
FormulaTemplate: 'Formula Template'
|
||||
@@ -1209,6 +1224,8 @@ colTitle: 'Template title',
|
||||
factoryExpertYoungBoard: 'Young editorial board',
|
||||
factoryExpertAuthor: 'Author',
|
||||
factoryExpertDb: 'Expert database',
|
||||
factoryExpertJump: 'View',
|
||||
factoryOfficialEmailTip: 'For this type, the system uses the official sender email by default. No account selection is required.',
|
||||
factoryScenario: 'Scenario',
|
||||
factoryScenarioPlaceholder: 'Select scenario',
|
||||
factoryScenarioSolicit: 'Invite Submission',
|
||||
@@ -1226,6 +1243,7 @@ colTitle: 'Template title',
|
||||
,
|
||||
autoPromotionLogs: {
|
||||
detail: 'Auto Promotion Details',
|
||||
pipelineHistory: 'PIPELINE HISTORY',
|
||||
factoryTaskSelectPlaceholder: 'Select promotion task',
|
||||
configured: 'Configured',
|
||||
editConfig: 'Edit auto promotion configuration',
|
||||
@@ -1293,6 +1311,7 @@ colTitle: 'Template title',
|
||||
taskLogState2: 'Failed',
|
||||
taskLogState3: 'Bounced',
|
||||
taskLogState4: 'Cancelled',
|
||||
logColIndex: 'No.',
|
||||
logColExpert: 'Expert',
|
||||
logColSendTime: 'Sent at',
|
||||
logColPreparedAt: 'Prepared at',
|
||||
|
||||
@@ -19,6 +19,13 @@ const zh = {
|
||||
}, info: {
|
||||
realname: '英文名字只能包含大小写英文字母、"-" 、和 空格'
|
||||
},
|
||||
articleAdd: {
|
||||
qqEmailAlertLine1: '出版社暂不支持qq邮箱投稿。',
|
||||
qqEmailAlertLine2Before: '请前往',
|
||||
qqEmailDashboardLink: '个人中心(Dashboard)',
|
||||
qqEmailAlertLine2After: '更换邮箱账号。',
|
||||
qqEmailSubmitBlockedMsg: '出版社暂不支持qq邮箱投稿,请去个人中心更换邮箱账号'
|
||||
},
|
||||
total: {
|
||||
author: '作者',
|
||||
editor: '编辑',
|
||||
@@ -299,7 +306,8 @@ const zh = {
|
||||
baseInfo: '基础信息',
|
||||
country: '国家',
|
||||
affiliation: '单位',
|
||||
researchAreas: '研究领域'
|
||||
researchAreas: '研究领域',
|
||||
unsubscribeStatus: '订阅状态'
|
||||
},
|
||||
emptyMark: '-',
|
||||
fields: {
|
||||
@@ -316,7 +324,14 @@ const zh = {
|
||||
detailCellEmpty: '暂无',
|
||||
noFieldDetail: '暂无领域对应的文献信息',
|
||||
exportWarn: '请选择研究领域或输入关键词或领域 field 后再导出。',
|
||||
exportFailed: '导出失败'
|
||||
exportFailed: '导出失败',
|
||||
unsubscribeNormal: '已订阅',
|
||||
unsubscribeUnsubscribed: '已退订',
|
||||
unsubscribeSwitchOn: '退订',
|
||||
unsubscribeSwitchOff: '已订阅',
|
||||
unsubscribeMissingId: '缺少专家 ID,无法切换退订状态',
|
||||
unsubscribeUpdateSuccess: '退订状态更新成功',
|
||||
unsubscribeUpdateFailed: '退订状态更新失败'
|
||||
},
|
||||
countryManagement: {
|
||||
title: '国家信息维护',
|
||||
@@ -969,8 +984,8 @@ const zh = {
|
||||
step3: '参考',
|
||||
step: 'step',
|
||||
Information: 'Fill in information',
|
||||
|
||||
|
||||
startPreAccept: '开始预接收流程',
|
||||
startPreAcceptWithPayment: '开始预接收流程并完成支付',
|
||||
},
|
||||
Formula:{
|
||||
FormulaTemplate:'公式模版'
|
||||
@@ -1194,6 +1209,8 @@ const zh = {
|
||||
factoryExpertYoungBoard: '青年编委',
|
||||
factoryExpertAuthor: '作者',
|
||||
factoryExpertDb: 'expert库',
|
||||
factoryExpertJump: '查看',
|
||||
factoryOfficialEmailTip: '此类型默认使用系统官方邮箱发送,无需选择邮箱账号。',
|
||||
factoryScenario: '场景',
|
||||
factoryScenarioPlaceholder: '请选择场景',
|
||||
factoryScenarioSolicit: '约稿',
|
||||
@@ -1211,6 +1228,7 @@ const zh = {
|
||||
,
|
||||
autoPromotionLogs: {
|
||||
detail: '自动推广详情',
|
||||
pipelineHistory: '流水线历史',
|
||||
factoryTaskSelectPlaceholder: '选择推广任务',
|
||||
configured: '已配置',
|
||||
editConfig: '修改期刊自动推广配置',
|
||||
@@ -1278,6 +1296,7 @@ const zh = {
|
||||
taskLogState2: '失败',
|
||||
taskLogState3: '退信',
|
||||
taskLogState4: '取消',
|
||||
logColIndex: '序号',
|
||||
logColExpert: '专家信息',
|
||||
logColSendTime: '发送时间',
|
||||
logColPreparedAt: '预处理完成时间',
|
||||
|
||||
@@ -859,8 +859,8 @@
|
||||
<el-form-item label="Account :" prop="account">
|
||||
<span>{{ coreForm.account }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Email :">
|
||||
<span>{{ coreForm.email }}</span>
|
||||
<el-form-item label="Email :" prop="email">
|
||||
<el-input type="text" placeholder="Please enter email..." v-model="coreForm.email" style="width: 320px" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Real name :" prop="realname">
|
||||
@@ -1610,6 +1610,14 @@ this.applyCvitaTable = data;
|
||||
.then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.coreTable = res.data.baseInfo;
|
||||
try {
|
||||
if (this.coreTable && this.coreTable.email != null && this.coreTable.email !== undefined) {
|
||||
localStorage.setItem('U_email', String(this.coreTable.email));
|
||||
}
|
||||
if (this.coreTable && this.coreTable.realname != null && this.coreTable.realname !== undefined) {
|
||||
localStorage.setItem('U_relname', String(this.coreTable.realname));
|
||||
}
|
||||
} catch (e) {}
|
||||
this.majorsList=res.data.baseInfo.majors;
|
||||
this.cvitaForm.user_id = res.data.baseInfo.user_id;
|
||||
this.reviewForm.user_id = res.data.baseInfo.user_id;
|
||||
@@ -1901,6 +1909,14 @@ this.applyCvitaTable = data;
|
||||
.then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('Personal information modified successfully!');
|
||||
try {
|
||||
if (this.coreForm.email != null && this.coreForm.email !== undefined) {
|
||||
localStorage.setItem('U_email', String(this.coreForm.email));
|
||||
}
|
||||
if (this.coreForm.realname != null && this.coreForm.realname !== undefined) {
|
||||
localStorage.setItem('U_relname', String(this.coreForm.realname));
|
||||
}
|
||||
} catch (e) {}
|
||||
this.coreVisible = false;
|
||||
this.tipVisible = false;
|
||||
this.getPersonData();
|
||||
|
||||
@@ -263,7 +263,39 @@
|
||||
<i v-else class="el-icon-plus avatar-uploader-icon" style="line-height: 120px"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item label="Contact Editor WeChat QR Code :" prop="wechat_yboard_qrcode">
|
||||
<div class="wechat-name-split__right">
|
||||
<div class="yboard-qrcode-side__label"></div>
|
||||
<div class="portrait WeChatCode">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
ref="upIconIMgYboard"
|
||||
:action="baseUrl + 'api/Journal/uploadYboardQrcode'"
|
||||
:show-file-list="false"
|
||||
name="qrcode_url"
|
||||
:on-success="handleAvatarSuccessYboard"
|
||||
:on-error="handleAvatarErrorYboard"
|
||||
:before-upload="beforeAvatarUpload2"
|
||||
>
|
||||
<img
|
||||
v-if="detailForm.wechat_yboard_qrcode"
|
||||
:src="
|
||||
/^https?:\/\//.test(detailForm.wechat_yboard_qrcode)
|
||||
? detailForm.wechat_yboard_qrcode
|
||||
: mediaUrl + 'journalyboardqrcode/' + detailForm.wechat_yboard_qrcode
|
||||
"
|
||||
class="avatar"
|
||||
accept=".png,.jpg"
|
||||
/>
|
||||
<i v-else class="el-icon-plus avatar-uploader-icon" style="line-height: 120px"></i>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Journal Topic :" prop="ResearchAreas">
|
||||
<el-button @click="addArea" size="mini" type="primary" plain style="position: absolute; right: 0; margin-left: 10px"
|
||||
>+ Add</el-button
|
||||
@@ -283,8 +315,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="Wechat Name :" prop="wechat_name">
|
||||
<el-input v-model="detailForm.wechat_name" placeholder="eg:公众号名称"></el-input>
|
||||
<el-form-item label="Wechat Name :" prop="wechat_name" class="wechat-name-split">
|
||||
<div class="wechat-name-split__inner">
|
||||
<div class="wechat-name-split__left">
|
||||
<el-input v-model="detailForm.wechat_name" placeholder="eg:公众号名称"></el-input>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="Wechat APP ID :" prop="wechat_app_id">
|
||||
<el-input v-model="detailForm.wechat_app_id" placeholder="eg:公众号app_id"></el-input>
|
||||
@@ -657,6 +694,17 @@ export default {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
},
|
||||
async handleAvatarSuccessYboard(res, file) {
|
||||
if (res.code == 0) {
|
||||
this.detailForm.wechat_yboard_qrcode = res.upurl;
|
||||
this.$forceUpdate();
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
},
|
||||
handleAvatarErrorYboard(res, file) {
|
||||
// no-op
|
||||
},
|
||||
handleAvatarError2(res, file) {
|
||||
// this.$message.error(res);
|
||||
},
|
||||
@@ -883,7 +931,9 @@ export default {
|
||||
epassword: data.epassword,
|
||||
kfen: data.kfen,
|
||||
fee: data.fee,
|
||||
databases: data.databases || data.database_inclusion || '',
|
||||
editor_qrcode: data.editor_qrcode,
|
||||
wechat_yboard_qrcode: data.wechat_yboard_qrcode || data.yboard_qrcode,
|
||||
scope: data.scope,
|
||||
abstract_chinese: data.abstract_chinese,
|
||||
publish_author: data.publish_author,
|
||||
@@ -1369,6 +1419,29 @@ export default {
|
||||
border-radius: 110px;
|
||||
}
|
||||
|
||||
/* Wechat Name + Apply Youth Board QR:两列对齐到右侧 */
|
||||
.wechat-name-split__inner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.wechat-name-split__left {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.wechat-name-split__right {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.yboard-qrcode-side__label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.portrait .ptmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
@@ -215,6 +215,8 @@
|
||||
localStorage.setItem('U_role', 'superadmin');
|
||||
localStorage.setItem('U_name', res.userinfo.account);
|
||||
localStorage.setItem('U_id', res.userinfo.user_id);
|
||||
localStorage.setItem('U_email', '');
|
||||
|
||||
|
||||
this.$router.push('/');
|
||||
} else if (res.data.roles.includes('editor')) {
|
||||
@@ -222,6 +224,7 @@
|
||||
localStorage.setItem('U_role', res.data.roles);
|
||||
localStorage.setItem('U_name', res.data.userinfo.account);
|
||||
localStorage.setItem('U_id', res.data.userinfo.user_id);
|
||||
localStorage.setItem('U_email', res.data.userinfo.email);
|
||||
this.$router.push('/');
|
||||
} else {
|
||||
localStorage.setItem('U_status', '2'); //其余的身份
|
||||
@@ -229,6 +232,7 @@
|
||||
localStorage.setItem('U_name', res.data.userinfo.account);
|
||||
localStorage.setItem('U_id', res.data.userinfo.user_id);
|
||||
localStorage.setItem('U_relname', res.data.userinfo.realname);
|
||||
localStorage.setItem('U_email', res.data.userinfo.email);
|
||||
this.$router.push('/');
|
||||
// this.roleVisible = true;
|
||||
// this.user_cap = res.data.roles;
|
||||
|
||||
238
src/components/page/YouthBoardSubmitSuccess.vue
Normal file
238
src/components/page/YouthBoardSubmitSuccess.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="submission-page">
|
||||
<div class="container">
|
||||
<div class="success-icon">✔</div>
|
||||
<h2 class="success-title">Application submitted successfully!</h2>
|
||||
<p class="success-desc" v-if="isFromChina">
|
||||
We have a WeChat group for academic discussions. Please feel free to scan the QR code below to join.
|
||||
</p>
|
||||
<p class="success-desc" v-else>
|
||||
Your application is currently under review. We appreciate your patience, and our team will notify you of the final decision via email as soon as possible.
|
||||
</p>
|
||||
|
||||
<div v-if="isFromChina" class="qr-section">
|
||||
<div v-if="qrLoading" class="qr-loading">Loading...</div>
|
||||
<div v-else class="qr-code-box" v-if="qrCodeUrl">
|
||||
<img :src="qrCodeUrl" alt="WeChat Group QR" />
|
||||
</div>
|
||||
<!-- <p class="remark-tip">Please use the following format for group remark:</p>
|
||||
<div class="remark-box">Name - Research Field - Affiliation</div> -->
|
||||
</div>
|
||||
|
||||
<router-link to="/login" replace class="back-btn"> Login to the Submission System Now </router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'YouthBoardSubmitSuccess',
|
||||
data() {
|
||||
return {
|
||||
qrCodeUrl: '',
|
||||
qrLoading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isFromChina() {
|
||||
const s = String(this.$route.query.country || '').trim();
|
||||
if (!s) return false;
|
||||
// 兼容:1 / China / 中国 / CN / CHN
|
||||
if (s === '1') return true;
|
||||
if (s === 'China' || s === 'CN' || s === 'CHN') return true;
|
||||
if (s === '中国') return true;
|
||||
if (s === 'US' || s === 'United States' || s === '美国') return false;
|
||||
return s === 'China';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.isFromChina) this.loadApplyBaseInfo();
|
||||
},
|
||||
methods: {
|
||||
normalizeCountry(v) {
|
||||
const s = String(v || '').trim();
|
||||
if (!s) return '';
|
||||
if (s === '中国' || s === 'CN' || s === 'CHN') return 'China';
|
||||
if (s === '美国' || s === 'USA' || s === 'US') return 'United States';
|
||||
if (s === '英国' || s === 'UK' || s === 'GBR') return 'United Kingdom';
|
||||
return s;
|
||||
},
|
||||
buildMediaUrl(raw) {
|
||||
if (!raw) return '';
|
||||
if (/^https?:\/\//i.test(raw)) return raw;
|
||||
const mediaBase = (this.Common && this.Common.mediaUrl ? this.Common.mediaUrl : '').replace(/\/+$/, '');
|
||||
if (!mediaBase) return raw;
|
||||
const cleanPath = String(raw).replace(/^\/+/, '');
|
||||
// 与后台上传目录保持一致(JournalManagement/common.vue)
|
||||
return `${mediaBase}/journalyboardqrcode/${cleanPath}`;
|
||||
},
|
||||
loadApplyBaseInfo() {
|
||||
const journal_id = this.$route.query.journal_id || this.$route.query.journalId;
|
||||
const expert_id = this.$route.query.expert_id || this.$route.query.expertId;
|
||||
if (!journal_id || !expert_id) {
|
||||
return;
|
||||
}
|
||||
this.qrLoading = true;
|
||||
|
||||
this.$api
|
||||
.post('api/Ucenter/getApplyYboardForExpertBaseInfo', {
|
||||
journal_id,
|
||||
expert_id
|
||||
})
|
||||
.then((res) => {
|
||||
const data = (res && res.data) || {};
|
||||
const journal = data.journal || data.journal_info || data.journalInfo || null;
|
||||
const raw =
|
||||
(journal && (journal.wechat_yboard_qrcode || journal.yboard_qrcode || journal.qrcode_url)) || '';
|
||||
this.qrCodeUrl = this.buildMediaUrl(raw);
|
||||
})
|
||||
.catch(() => {
|
||||
this.qrCodeUrl = '';
|
||||
})
|
||||
.finally(() => {
|
||||
this.qrLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.submission-page {
|
||||
--primary-blue: #3a91d9;
|
||||
--text-main: #2c3e50;
|
||||
--bg-light: #f7f9fc;
|
||||
--success-green: #48bb78;
|
||||
--danger: #e53e3e;
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background-color: var(--bg-light);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: #ffffff;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
height: fit-content;
|
||||
padding: 60px 40px;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.04);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 60px;
|
||||
color: var(--success-green);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.success-title {
|
||||
font-size: 24px;
|
||||
color: var(--text-main);
|
||||
font-weight: 700;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.success-desc {
|
||||
font-size: 16px;
|
||||
color: #718096;
|
||||
margin-bottom: 40px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.qr-section {
|
||||
background-color: #f0f7ff;
|
||||
border: 1px solid #c3dafe;
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.qr-header {
|
||||
color: #2b6cb0;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.qr-code-box {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: #fff;
|
||||
margin: 15px auto;
|
||||
border: 1px solid #eee;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-code-box img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.remark-tip {
|
||||
font-size: 14px;
|
||||
color: #4a5568;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.remark-box {
|
||||
background: #fff;
|
||||
border: 1px solid #feb2b2;
|
||||
color: var(--danger);
|
||||
font-weight: bold;
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: inline-block;
|
||||
margin-top: 40px;
|
||||
color: #006699;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
color: var(--primary-blue);
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 220px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #718096;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.qr-loading {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: #fff;
|
||||
margin: 15px auto;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #718096;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
612
src/components/page/YouthEditorialBoardRegistration.vue
Normal file
612
src/components/page/YouthEditorialBoardRegistration.vue
Normal file
@@ -0,0 +1,612 @@
|
||||
<template>
|
||||
<div class="registration-container">
|
||||
<div class="card">
|
||||
<h1>Youth Editorial Board Registration</h1>
|
||||
<div v-if="journalInfoLoaded" class="journal-hero">
|
||||
<div class="journal-cover-wrap" v-if="journalCoverUrl">
|
||||
<img :src="journalCoverUrl" :alt="journalTitle || 'Journal Cover'" class="journal-cover" />
|
||||
</div>
|
||||
<div class="journal-title">{{ journalTitle || 'Journal Information' }}</div>
|
||||
</div>
|
||||
<!-- <p v-if="journalId && expertId" class="link-meta">
|
||||
Journal ID: <strong>{{ journalId }}</strong> · Expert ID: <strong>{{ expertId }}</strong>
|
||||
</p> -->
|
||||
<!-- <p v-else class="link-meta link-meta--warn">
|
||||
Missing <code>journal_id</code> and/or <code>expert_id</code> in the URL. Please use the full invitation link.
|
||||
</p> -->
|
||||
|
||||
<div v-if="submitSuccess" class="success-panel">
|
||||
<h2>Thank you</h2>
|
||||
<p>Your registration has been submitted. We will contact you by email.</p>
|
||||
</div>
|
||||
|
||||
<form v-else @submit.prevent="handleSubmit" autocomplete="off">
|
||||
<div class="form-group">
|
||||
<label><span class="required-star">*</span> English Name</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="formData.engName"
|
||||
placeholder=""
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label><span class="required-star">*</span> Email (QQ mail is not allowed)</label>
|
||||
<input type="email" v-model="formData.email" placeholder="" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label><span class="required-star">*</span> Password</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="formData.password"
|
||||
placeholder=""
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label><span class="required-star">*</span> Upload CV</label>
|
||||
<div v-if="!selectedFile" class="upload-trigger" @click="$refs.fileInput.click()">
|
||||
<div class="icon">📤</div>
|
||||
<div class="hint">Click to upload PDF CV</div>
|
||||
<input type="file" ref="fileInput" hidden accept=".pdf" @change="onFileChange" />
|
||||
</div>
|
||||
<div v-else class="file-display-box">
|
||||
<span class="file-icon">📄</span>
|
||||
<span class="file-name">{{ selectedFile.name }}</span>
|
||||
<span class="remove-btn" @click.prevent="removeFile">×</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <transition name="fade">
|
||||
<div v-if="formData.country === 'China'" class="qr-area">
|
||||
<div class="qr-title">Mandatory for Youth Scientists in China</div>
|
||||
<div v-if="wechatQrUrl" class="qr-box">
|
||||
<img :src="wechatQrUrl" alt="Group QR Code" @error="onQrImgError" />
|
||||
</div>
|
||||
<p v-else class="qr-fallback">Please add image file <code>public/youth-board-wechat-qr.png</code> for the group QR code.</p>
|
||||
<p class="qr-hint">Please use the following format for the group remark:</p>
|
||||
<div class="format-tag">Name - Research Field - Affiliation</div>
|
||||
</div>
|
||||
</transition> -->
|
||||
|
||||
<button type="submit" class="submit-btn" :disabled="submitting">
|
||||
{{ submitting ? 'Submitting...' : 'Register Now' }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
/** 公开邀请注册:后端实现 multipart 接口后填写此路径(与 Dashboard 青年编委申请字段对齐可再改) */
|
||||
const SUBMIT_URL = '/api/User/youthBoardInviteRegister';
|
||||
|
||||
export default {
|
||||
name: 'YouthEditorialBoardRegistration',
|
||||
data() {
|
||||
return {
|
||||
baseUrl: this.Common.baseUrl,
|
||||
formData: {
|
||||
engName: '',
|
||||
email: '',
|
||||
country: '',
|
||||
password: ''
|
||||
},
|
||||
countryList: [],
|
||||
selectedFile: null,
|
||||
submitting: false,
|
||||
submitSuccess: false,
|
||||
wechatQrUrl: '',
|
||||
journalInfoLoaded: false,
|
||||
journalDetail: null,
|
||||
uploadedCvPath: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
journalId() {
|
||||
const q = this.$route.query || {};
|
||||
return String(q.journal_id || q.journalId || '').trim();
|
||||
},
|
||||
expertId() {
|
||||
const q = this.$route.query || {};
|
||||
return String(q.expert_id || q.expertId || '').trim();
|
||||
},
|
||||
journalTitle() {
|
||||
const j = this.journalDetail || {};
|
||||
return j.full_name || j.journal_name || j.title || j.abbr || '';
|
||||
},
|
||||
journalCoverUrl() {
|
||||
const j = this.journalDetail || {};
|
||||
const raw =
|
||||
j.journal_icon ||
|
||||
j.cover ||
|
||||
j.img ||
|
||||
j.image ||
|
||||
j.icon ||
|
||||
j.logo ||
|
||||
j.thumb ||
|
||||
j.picture ||
|
||||
j.photo ||
|
||||
j.journal_cover ||
|
||||
j.journal_img ||
|
||||
'';
|
||||
if (!raw) return '';
|
||||
if (/^https?:\/\//i.test(raw)) return raw;
|
||||
const mediaBase = (this.Common.mediaUrl || '').replace(/\/+$/, '');
|
||||
const cleanPath = String(raw).replace(/^\/+/, '');
|
||||
if (!mediaBase) return `/${cleanPath}`;
|
||||
if (/^journal\//i.test(cleanPath) || /^reviewer\//i.test(cleanPath)) {
|
||||
return `${mediaBase}/${cleanPath}`;
|
||||
}
|
||||
return `${mediaBase}/journal/${cleanPath}`;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const base = process.env.BASE_URL || '/';
|
||||
const prefix = base.endsWith('/') ? base : `${base}/`;
|
||||
this.wechatQrUrl = `${prefix}youth-board-wechat-qr.png`;
|
||||
this.fetchCountries();
|
||||
this.fetchApplyBaseInfo();
|
||||
},
|
||||
methods: {
|
||||
countryOptionKey(item) {
|
||||
return item.name || item.id || item.country || JSON.stringify(item);
|
||||
},
|
||||
countryOptionValue(item) {
|
||||
return item.name != null ? item.name : item.country || item.title || '';
|
||||
},
|
||||
countryOptionLabel(item) {
|
||||
return item.name != null ? item.name : item.country || item.title || '';
|
||||
},
|
||||
fetchCountries() {
|
||||
// this.$api
|
||||
// .post('api/Reviewer/getCountrys')
|
||||
// .then((res) => {
|
||||
// const list = res.countrys || res.data || [];
|
||||
// this.countryList = Array.isArray(list) ? list : [];
|
||||
// })
|
||||
// .catch(() => {
|
||||
// this.countryList = [{ name: 'China' }, { name: 'United States' }, { name: 'United Kingdom' }];
|
||||
// });
|
||||
},
|
||||
fetchApplyBaseInfo() {
|
||||
if (!this.journalId || !this.expertId) return;
|
||||
this.$api
|
||||
.post('api/Ucenter/getApplyYboardForExpertBaseInfo', {
|
||||
journal_id: this.journalId,
|
||||
expert_id: this.expertId
|
||||
})
|
||||
.then((res) => {
|
||||
const data = (res && res.data) || {};
|
||||
const expertCandidate =
|
||||
data.expert_info ||{}
|
||||
|
||||
const journal =
|
||||
data.journal ||
|
||||
data.journal_info ||
|
||||
data.journalInfo ||
|
||||
data.journal_detail ||
|
||||
data.journalDetail ||
|
||||
null;
|
||||
|
||||
// 为了兼容后端返回字段名,这里做多字段兜底取值
|
||||
const pickFirst = (obj, keys) => {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i];
|
||||
if (obj && obj[k] !== undefined && obj[k] !== null && String(obj[k]).trim() !== '') {
|
||||
return obj[k];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const pickFromCandidateOrData = (keys) => {
|
||||
const v1 = pickFirst(expertCandidate, keys);
|
||||
if (v1) return v1;
|
||||
return pickFirst(data, keys);
|
||||
};
|
||||
|
||||
const normalizeCountry = (v) => {
|
||||
const s = String(v || '').trim();
|
||||
if (!s) return '';
|
||||
// 常见中文/英文归一化(确保和下拉 options.value 匹配)
|
||||
if (s === '中国' || s === 'CN' || s === 'CHN') return 'China';
|
||||
if (s === '美国' || s === 'USA' || s === 'US') return 'United States';
|
||||
if (s === '英国' || s === 'UK' || s === 'GBR') return 'United Kingdom';
|
||||
return s;
|
||||
};
|
||||
|
||||
this.formData.engName = pickFromCandidateOrData([
|
||||
'eng_name',
|
||||
'english_name',
|
||||
'u_eng_name',
|
||||
'U_eng_name',
|
||||
'U_engName',
|
||||
'U_name_en',
|
||||
'realname',
|
||||
'u_relname',
|
||||
'U_relname',
|
||||
'name',
|
||||
'U_name'
|
||||
]);
|
||||
|
||||
this.formData.email = pickFromCandidateOrData([
|
||||
'email',
|
||||
'u_email',
|
||||
'U_email',
|
||||
'U_email_address',
|
||||
'user_email',
|
||||
'username',
|
||||
'U_username'
|
||||
]);
|
||||
|
||||
this.formData.country = normalizeCountry(
|
||||
pickFromCandidateOrData([
|
||||
'country',
|
||||
'country_name',
|
||||
'u_country',
|
||||
'U_country',
|
||||
'countryName',
|
||||
'U_country_name'
|
||||
])
|
||||
);
|
||||
|
||||
if (journal) {
|
||||
this.journalDetail = journal;
|
||||
this.journalInfoLoaded = true;
|
||||
}
|
||||
if (!journal) {
|
||||
this.journalDetail = null;
|
||||
this.journalInfoLoaded = true;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.journalDetail = null;
|
||||
this.journalInfoLoaded = true;
|
||||
});
|
||||
},
|
||||
async onFileChange(e) {
|
||||
const file = e.target.files && e.target.files[0];
|
||||
if (file && file.type === 'application/pdf') {
|
||||
this.selectedFile = file;
|
||||
const fd = new FormData();
|
||||
fd.append('reviewerCV', file);
|
||||
try {
|
||||
const resp = await axios.post(`${this.baseUrl}api/Ucenter/up_cv_file`, fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
const body = resp && resp.data ? resp.data : {};
|
||||
if (Number(body.code) === 0 && body.upurl) {
|
||||
this.uploadedCvPath = body.upurl;
|
||||
} else {
|
||||
this.selectedFile = null;
|
||||
this.uploadedCvPath = '';
|
||||
this.alertError((body && body.msg) || 'CV upload failed.');
|
||||
}
|
||||
} catch (err) {
|
||||
this.selectedFile = null;
|
||||
this.uploadedCvPath = '';
|
||||
this.alertError('CV upload failed. Please try again.');
|
||||
}
|
||||
} else if (file) {
|
||||
this.alertError('Please upload a valid PDF file.');
|
||||
}
|
||||
},
|
||||
removeFile() {
|
||||
this.selectedFile = null;
|
||||
this.uploadedCvPath = '';
|
||||
if (this.$refs.fileInput) this.$refs.fileInput.value = '';
|
||||
},
|
||||
onQrImgError() {
|
||||
this.wechatQrUrl = '';
|
||||
},
|
||||
alertError(msg) {
|
||||
this.$message.error(msg);
|
||||
},
|
||||
async handleSubmit() {
|
||||
if (!this.journalId || !this.expertId) {
|
||||
this.alertError('Invalid link: journal_id and expert_id are required in the URL.');
|
||||
return;
|
||||
}
|
||||
|
||||
const engName = (this.formData.engName || '').trim();
|
||||
if (!engName) {
|
||||
this.alertError('Please enter your English name.');
|
||||
return;
|
||||
}
|
||||
|
||||
const emailRaw = (this.formData.email || '').trim();
|
||||
if (!emailRaw) {
|
||||
this.alertError('Please enter your email address.');
|
||||
return;
|
||||
}
|
||||
const email = emailRaw.toLowerCase();
|
||||
if (!/^[-._A-Za-z0-9]+@[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)+$/.test(email)) {
|
||||
this.alertError('Please enter a valid email address.');
|
||||
return;
|
||||
}
|
||||
if (email.endsWith('@qq.com')) {
|
||||
this.alertError('Registration failed: QQ email addresses are not accepted.');
|
||||
return;
|
||||
}
|
||||
if (!this.formData.password) {
|
||||
this.alertError('Please enter your password.');
|
||||
return;
|
||||
}
|
||||
if (!this.selectedFile) {
|
||||
this.alertError('Please upload your CV.');
|
||||
return;
|
||||
}
|
||||
if (!this.uploadedCvPath) {
|
||||
this.alertError('CV upload is not complete. Please upload again.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
this.$api
|
||||
.post('api/Ucenter/submitApplyYboardForExpert', {
|
||||
journal_id: this.journalId,
|
||||
expert_id: this.expertId,
|
||||
name: engName,
|
||||
email: emailRaw,
|
||||
cv: this.uploadedCvPath,
|
||||
password: this.formData.password
|
||||
})
|
||||
.then((res) => {
|
||||
if (res && res.code == 0) {
|
||||
this.$router.replace({
|
||||
path: '/youthBoardSubmitSuccess',
|
||||
query: {
|
||||
country: this.formData.country == 'China' ? '1' : '0',
|
||||
journal_id: this.journalId,
|
||||
expert_id: this.expertId
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.alertError((res && res.msg) || 'Submission failed.');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.alertError((err && err.msg) || 'Submission failed.');
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.registration-container {
|
||||
--primary: #3a91d9;
|
||||
--text: #2c3e50;
|
||||
--border: #e2e8f0;
|
||||
--danger: #e53e3e;
|
||||
--bg: #f7f9fc;
|
||||
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #ffffff;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
padding: 40px;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.journal-hero {
|
||||
text-align: center;
|
||||
margin: 6px 0 18px;
|
||||
}
|
||||
|
||||
.journal-cover-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 112px;
|
||||
height: 150px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.journal-cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.journal-title {
|
||||
margin-top: 10px;
|
||||
color: #1d4f8c;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
font-weight: 700;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: var(--text);
|
||||
font-size: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.link-meta {
|
||||
font-size: 13px;
|
||||
color: #4a5568;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.link-meta--warn {
|
||||
color: #c05621;
|
||||
}
|
||||
|
||||
.success-panel {
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.success-panel h2 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #4a5568;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.required-star {
|
||||
color: #e53e3e;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.upload-trigger {
|
||||
border: 1px dashed #cbd5e0;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
}
|
||||
.upload-trigger:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
.upload-trigger .icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
.upload-trigger .hint {
|
||||
font-size: 14px;
|
||||
color: #718096;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.file-display-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
}
|
||||
.file-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.remove-btn {
|
||||
color: var(--danger);
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.qr-area {
|
||||
background-color: #f0f7ff;
|
||||
border: 1px solid #dbeafe;
|
||||
border-radius: 16px;
|
||||
padding: 25px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.qr-title {
|
||||
color: #2b6cb0;
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.qr-box {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background: #fff;
|
||||
margin: 0 auto 15px;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.qr-box img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.qr-fallback {
|
||||
font-size: 13px;
|
||||
color: #718096;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.qr-hint {
|
||||
font-size: 13px;
|
||||
color: #4a5568;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.format-tag {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #feb2b2;
|
||||
border-radius: 8px;
|
||||
color: var(--danger);
|
||||
font-weight: bold;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #4da1e6 0%, #3588d1 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
.submit-btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -810,9 +810,37 @@
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="articleAddQqEmailSubmitBlocked"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="max-width: 720px; margin: 24px auto 0"
|
||||
>
|
||||
<template slot="title">
|
||||
<div class="qq-email-alert-body">
|
||||
<div class="qq-email-alert-line1">{{ $t('articleAdd.qqEmailAlertLine1') }}</div>
|
||||
<div class="qq-email-alert-line2">
|
||||
<span>{{ $t('articleAdd.qqEmailAlertLine2Before') }}</span>
|
||||
<router-link to="/dashboard" class="qq-email-dashboard-link">
|
||||
<i class="el-icon-user-solid"></i>
|
||||
{{ $t('articleAdd.qqEmailDashboardLink') }}
|
||||
</router-link>
|
||||
<span>{{ $t('articleAdd.qqEmailAlertLine2After') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
<div style="text-align: center; margin: 40px 0 0 0">
|
||||
<el-button type="warning" @click="onStagingSave(4)" class="pro_stage">Save as draft </el-button>
|
||||
<el-button type="primary" @click="onSubmit(1)" class="pro_ceed">Confirm Submit </el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="onSubmit(1)"
|
||||
class="pro_ceed"
|
||||
:disabled="articleAddQqEmailSubmitBlocked"
|
||||
>
|
||||
Confirm Submit
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1612,6 +1640,17 @@ export default {
|
||||
},
|
||||
upload_qualifications: function () {
|
||||
return this.baseUrl + 'api/Admin/up_file';
|
||||
},
|
||||
/** 投稿页第四步:登录邮箱为 QQ 邮箱时不允许正式提交 */
|
||||
articleAddQqEmailSubmitBlocked() {
|
||||
try {
|
||||
const raw = localStorage.getItem('U_email') || '';
|
||||
const email = String(raw).trim().toLowerCase();
|
||||
if (!email || !email.includes('@')) return false;
|
||||
return /@qq\.com$/i.test(email) || /@vip\.qq\.com$/i.test(email);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -1907,6 +1946,10 @@ export default {
|
||||
}
|
||||
},
|
||||
async onSubmit() {
|
||||
if (this.articleAddQqEmailSubmitBlocked) {
|
||||
this.$message.warning(this.$t('articleAdd.qqEmailSubmitBlockedMsg'));
|
||||
return false;
|
||||
}
|
||||
// 1. 校验协议勾选
|
||||
if (!this.agreechecked) {
|
||||
this.$message.error(
|
||||
@@ -4063,6 +4106,28 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.qq-email-alert-body {
|
||||
line-height: 1.55;
|
||||
}
|
||||
.qq-email-alert-line1 {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.qq-email-alert-line2 {
|
||||
display: block;
|
||||
}
|
||||
.qq-email-dashboard-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin: 0 4px;
|
||||
font-weight: 600;
|
||||
color: #cf9236;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.qq-email-dashboard-link:hover {
|
||||
color: #b8822f;
|
||||
}
|
||||
/deep/.apc_content a{
|
||||
color: rgb(81, 127, 213) !important; cursor: pointer !important; text-decoration: underline !important;
|
||||
}
|
||||
|
||||
@@ -2462,6 +2462,7 @@ export default {
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
this.$api.post('api/Article/changeRepetition', this.repeform).then((res) => {
|
||||
this.repebox = false;
|
||||
load.close();
|
||||
this.$message.success('success');
|
||||
this.initarticle();
|
||||
|
||||
@@ -107,11 +107,11 @@
|
||||
</span>
|
||||
|
||||
|
||||
<span v-if="item.state == 6" style="text-decoration: none;margin-left: 20px;">
|
||||
<span v-if="item.state == 6 && preAcceptButtonReady(item)" style="text-decoration: none;margin-left: 20px;">
|
||||
|
||||
<span @click="goPre_ingested(item.article_id)" class="preButton">
|
||||
<!-- <el-badge is-dot class="item" > -->
|
||||
<i class="el-icon-bank-card"></i>Start the pre-acceptance process and complete your payment
|
||||
<i class="el-icon-bank-card"></i>{{ preAcceptEntryButtonText(item) }}
|
||||
<!-- </el-badge> -->
|
||||
</span>
|
||||
</span>
|
||||
@@ -539,6 +539,61 @@
|
||||
this.getdate();
|
||||
},
|
||||
methods: {
|
||||
/** 与 Complete_profile.vue getDetail 中订单/支付展示逻辑一致:preOrderDetail → is_buy、期刊费、折后费、isFree */
|
||||
_feeNum(v) {
|
||||
if (v == null || v === '') return 0;
|
||||
const n = Number(String(v).replace(/,/g, ''));
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
},
|
||||
computePreacceptShortButton(articleInfo, journalInfo) {
|
||||
if (!articleInfo || !journalInfo) return false;
|
||||
const journalFee = journalInfo.fee;
|
||||
const articleFee = articleInfo.fee;
|
||||
const isBuy = Number(articleInfo.is_buy) === 1;
|
||||
const tableFee = journalFee ? this._feeNum(articleFee) : 0;
|
||||
const isFree = isBuy && tableFee === 0;
|
||||
const noJournalFee = !journalFee || this._feeNum(journalFee) === 0 || String(journalFee) === '0.00';
|
||||
// Complete_profile: active=1 当已付,或 未付但期刊无 APC
|
||||
if (isFree) return true;
|
||||
if (isBuy) return true;
|
||||
if (!isBuy && noJournalFee) return true;
|
||||
return false;
|
||||
},
|
||||
preAcceptButtonReady(item) {
|
||||
const p = item._preacceptPay;
|
||||
return !!(p && p.loading === false);
|
||||
},
|
||||
async hydratePreacceptPaymentForList(rows) {
|
||||
if (!rows || !rows.length) return;
|
||||
const targets = rows.filter((r) => Number(r.state) === 6);
|
||||
await Promise.all(
|
||||
targets.map(async (row) => {
|
||||
try {
|
||||
const res = await this.$api.post('api/Order/preOrderDetail', {
|
||||
article_id: row.article_id
|
||||
});
|
||||
if (res && Number(res.code) === 0) {
|
||||
const article = res.data.article_detail || {};
|
||||
const journal = res.data.journal_detail || {};
|
||||
const shortButton = this.computePreacceptShortButton(article, journal);
|
||||
this.$set(row, '_preacceptPay', { loading: false, shortButton });
|
||||
} else {
|
||||
this.$set(row, '_preacceptPay', { loading: false, shortButton: false });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.$set(row, '_preacceptPay', { loading: false, shortButton: false });
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
preAcceptEntryButtonText(item) {
|
||||
const pay = item._preacceptPay;
|
||||
if (!pay || pay.loading) return '';
|
||||
return pay.shortButton
|
||||
? this.$t('PreAccept.startPreAccept')
|
||||
: this.$t('PreAccept.startPreAcceptWithPayment');
|
||||
},
|
||||
formatToHtml(val) {
|
||||
|
||||
if (!val) return '';
|
||||
@@ -612,8 +667,12 @@ return processedText;
|
||||
1 + '-';
|
||||
let D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
|
||||
res.data[i].ctime = Y + M + D;
|
||||
if (Number(res.data[i].state) === 6) {
|
||||
this.$set(res.data[i], '_preacceptPay', { loading: true, shortButton: false });
|
||||
}
|
||||
}
|
||||
this.tableData = res.data
|
||||
this.tableData = res.data;
|
||||
this.hydratePreacceptPaymentForList(res.data);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
|
||||
@@ -82,6 +82,12 @@
|
||||
>
|
||||
<span class="tpl-name tpl-name-single-line">{{ taskCard.templateName || 'No Template Configured' }}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<i class="el-icon-user"
|
||||
><span style="font-size: 11px; margin-left: 3px; margin-right: 3px">Type :</span></i
|
||||
>
|
||||
<span class="tpl-name">{{ taskCard.expertTypeLabel || '-' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- <div class="meta-row">
|
||||
<i class="el-icon-collection-tag"
|
||||
@@ -90,7 +96,7 @@
|
||||
<span class="tpl-name">{{ taskCard.fieldCountText }}</span>
|
||||
</div> -->
|
||||
|
||||
<div class="meta-row">
|
||||
<div class="meta-row" v-if="String(taskCard.expertType || '') === '5'">
|
||||
<i class="el-icon-location-outline"
|
||||
><span style="font-size: 11px; margin-left: 3px; margin-right: 3px">Country :</span></i
|
||||
>
|
||||
@@ -385,6 +391,8 @@ export default {
|
||||
const journalId = item.journal_id || item.id;
|
||||
let journalObj = {
|
||||
journal_id: journalId,
|
||||
abbr: item.abbr || '',
|
||||
journal_icon: item.journal_icon || '',
|
||||
title: item.title || item.journal_title || item.name || `Journal ${journalId}`,
|
||||
solicit: {
|
||||
enabled: false,
|
||||
@@ -450,6 +458,8 @@ export default {
|
||||
taskId: task.promotion_factory_id || '',
|
||||
type: type,
|
||||
typeLabel: this.mapFactoryTaskTypeLabel(type),
|
||||
expertType: task && task.expert_type != null ? String(task.expert_type) : '',
|
||||
expertTypeLabel: this.mapFactoryExpertTypeLabel(task && task.expert_type != null ? String(task.expert_type) : ''),
|
||||
enabled: enabled,
|
||||
switchLoading: false,
|
||||
initialized: true,
|
||||
@@ -567,6 +577,15 @@ export default {
|
||||
if (t === '4') return this.$t('autoPromotion.autoSolicit');
|
||||
return this.$t('autoPromotion.autoSolicit');
|
||||
},
|
||||
mapFactoryExpertTypeLabel(expertType) {
|
||||
const t = String(expertType || '').trim();
|
||||
if (t === '1') return this.$t('autoPromotion.factoryExpertChief');
|
||||
if (t === '2') return this.$t('autoPromotion.factoryExpertBoard');
|
||||
if (t === '3') return this.$t('autoPromotion.factoryExpertYoungBoard');
|
||||
if (t === '4') return this.$t('autoPromotion.factoryExpertAuthor');
|
||||
if (t === '5') return this.$t('autoPromotion.factoryExpertDb');
|
||||
return '-';
|
||||
},
|
||||
getJournalDisplayTasks(journal) {
|
||||
const list = journal && Array.isArray(journal.factoryTasks) ? journal.factoryTasks : [];
|
||||
return list;
|
||||
|
||||
@@ -19,19 +19,46 @@
|
||||
<el-select
|
||||
v-if="config.initialized && selectedJournalId"
|
||||
v-model="headerPromotionFactoryId"
|
||||
class="header-factory-task-select"
|
||||
class="header-factory-task-select custom-pipeline-select"
|
||||
size="small"
|
||||
filterable
|
||||
:loading="factoryTasksHeaderLoading"
|
||||
:placeholder="$t('autoPromotionLogs.factoryTaskSelectPlaceholder')"
|
||||
@change="onHeaderFactoryTaskChange"
|
||||
popper-class="pipeline-popper"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in factoryTaskOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
|
||||
|
||||
<el-option-group >
|
||||
|
||||
<el-option
|
||||
v-for="opt in factoryTaskOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
class="pipeline-option"
|
||||
>
|
||||
<div class="option-content">
|
||||
<div class="row-top">
|
||||
<span class="task-title">{{ mapFactoryTaskTypeLabel(opt.task.type) }}</span>
|
||||
<el-tag :type="getStatusType(opt.task.start_promotion)" size="mini" effect="plain" class="status-tag">
|
||||
{{ opt.task.start_promotion==1 ? $t('autoPromotion.running') : $t('autoPromotion.stopped') }}
|
||||
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="row-bottom">
|
||||
<span class="meta-item database">{{ mapFactoryExpertTypeLabel(opt.task.expert_type) }}</span>
|
||||
<template v-if="opt.task.expert_type==5">
|
||||
<span class="separator">•</span>
|
||||
<span class="meta-item region">{{ opt.task.country_scope_label }}</span>
|
||||
</template>
|
||||
|
||||
<span class="separator">•</span>
|
||||
<span class="meta-item time">{{ opt.task.ctime_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
<el-tag
|
||||
v-if="config.initialized && selectedJournalId && headerFactoryTaskRunning !== null"
|
||||
@@ -45,19 +72,10 @@
|
||||
{{ headerFactoryTaskRunning ? $t('autoPromotion.running') : $t('autoPromotion.stopped') }}
|
||||
</el-tag>
|
||||
|
||||
<!-- <template v-if="config.initialized"> -->
|
||||
<!-- <el-tag type="success" size="small" effect="plain" style="margin-left: 10px">
|
||||
<i class="el-icon-circle-check"></i> {{ $t('autoPromotionLogs.configured') }}
|
||||
</el-tag> -->
|
||||
<el-button type="text" size="small" style="margin-left: 10px" @click="openFactoryTaskDialogFromLogs">
|
||||
<i class="el-icon-edit"></i>
|
||||
{{ config.initialized ? $t('autoPromotionLogs.editConfig') : $t('autoPromotionLogs.startConfig') }}
|
||||
</el-button>
|
||||
<!-- </template> -->
|
||||
|
||||
<!-- <el-tag v-else type="info" size="small" effect="plain" style="margin-left: 10px">
|
||||
<i class="el-icon-info"></i> {{ $t('autoPromotionLogs.notConfigured') }}
|
||||
</el-tag> -->
|
||||
<el-button type="text" size="small" style="margin-left: 10px" @click="openFactoryTaskDialogFromLogs">
|
||||
<i class="el-icon-edit"></i>
|
||||
{{ config.initialized ? $t('autoPromotionLogs.editConfig') : $t('autoPromotionLogs.startConfig') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="config.initialized && selectedJournalId && headerPromotionFactoryId" class="right">
|
||||
<el-button
|
||||
@@ -100,8 +118,6 @@
|
||||
|
||||
<div v-else class="manage-mode">
|
||||
<el-card shadow="never" class="list-card">
|
||||
|
||||
|
||||
<div class="filter-header-row">
|
||||
<div class="tmr-capsule-group">
|
||||
<el-tabs v-model="query.state" type="card" @tab-click="handleTabClick">
|
||||
@@ -135,7 +151,9 @@
|
||||
<div class="task-column">
|
||||
<div class="task-name">{{ scope.row.task_name || '-' }}</div>
|
||||
<div class="task-id-tags">
|
||||
<span class="id-tag">{{ $t('autoPromotionLogs.templateIdLabel') }}: {{ scope.row.template_id }}</span>
|
||||
<span class="id-tag"
|
||||
>{{ $t('autoPromotionLogs.templateIdLabel') }}: {{ scope.row.template_id }}</span
|
||||
>
|
||||
<span class="id-tag">{{ $t('autoPromotionLogs.styleIdLabel') }}: {{ scope.row.style_id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,7 +242,11 @@
|
||||
:icon="String(scope.row.state) === '5' ? 'el-icon-edit-outline' : 'el-icon-view'"
|
||||
@click="previewRow(scope.row)"
|
||||
>
|
||||
{{ String(scope.row.state) === '5' ? $t('autoPromotionLogs.editAction') : $t('autoPromotionLogs.previewAction') }}
|
||||
{{
|
||||
String(scope.row.state) === '5'
|
||||
? $t('autoPromotionLogs.editAction')
|
||||
: $t('autoPromotionLogs.previewAction')
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -381,7 +403,7 @@ export default {
|
||||
selectedStyleName: '',
|
||||
|
||||
// 列表数据
|
||||
query: { keyword: '', state: 'all', pageIndex: 1, pageSize: 15 },
|
||||
query: { keyword: '', state: 'all', pageIndex: 1, pageSize: 10 },
|
||||
list: [],
|
||||
total: 0,
|
||||
|
||||
@@ -427,6 +449,36 @@ export default {
|
||||
const opt = this.factoryTaskOptions.find((o) => String(o.value) === id);
|
||||
if (opt && typeof opt.running === 'boolean') return opt.running;
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
* 顶部 select 前缀展示用:与 headerPromotionFactoryId 对应的 option(含 task)。
|
||||
* URL 回退插入的占位项可能没有 task,这里统一成可安全渲染的对象。
|
||||
*/
|
||||
currentSelectedTask() {
|
||||
const emptyTask = { type: '', ctime_text: '' };
|
||||
const id = String(this.headerPromotionFactoryId || '').trim();
|
||||
if (!id || !Array.isArray(this.factoryTaskOptions) || !this.factoryTaskOptions.length) {
|
||||
return { value: '', label: '', running: false, task: { ...emptyTask } };
|
||||
}
|
||||
const opt = this.factoryTaskOptions.find((o) => String(o.value) === id);
|
||||
if (!opt) {
|
||||
return { value: id, label: id, running: false, task: { ...emptyTask } };
|
||||
}
|
||||
const raw = opt.task && typeof opt.task === 'object' ? opt.task : {};
|
||||
const ctimeText =
|
||||
raw.ctime_text != null && String(raw.ctime_text).trim() !== ''
|
||||
? String(raw.ctime_text).trim()
|
||||
: this.formatFactoryHeaderTaskCreateTime(raw);
|
||||
return {
|
||||
value: opt.value,
|
||||
label: opt.label,
|
||||
running: typeof opt.running === 'boolean' ? opt.running : this.isFactoryHeaderTaskRunning(raw),
|
||||
task: {
|
||||
...raw,
|
||||
type: raw.type != null ? String(raw.type) : '',
|
||||
ctime_text: ctimeText || ''
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -445,12 +497,33 @@ export default {
|
||||
this.initPage();
|
||||
},
|
||||
methods: {
|
||||
mapFactoryTaskTypeLabel(type) {
|
||||
const t = String(type || '');
|
||||
if (t === '1') return this.$t('autoPromotion.factoryScenarioSolicit');
|
||||
if (t === '2') return this.$t('autoPromotion.factoryScenarioPromoteCitation');
|
||||
if (t === '3') return this.$t('autoPromotion.factoryScenarioGeneralThanks');
|
||||
if (t === '4') return this.$t('autoPromotion.autoSolicit');
|
||||
return this.$t('autoPromotion.autoSolicit');
|
||||
},
|
||||
mapFactoryExpertTypeLabel(expertType) {
|
||||
const t = String(expertType || '').trim();
|
||||
if (t === '1') return this.$t('autoPromotion.factoryExpertChief');
|
||||
if (t === '2') return this.$t('autoPromotion.factoryExpertBoard');
|
||||
if (t === '3') return this.$t('autoPromotion.factoryExpertYoungBoard');
|
||||
if (t === '4') return this.$t('autoPromotion.factoryExpertAuthor');
|
||||
if (t === '5') return this.$t('autoPromotion.factoryExpertDb');
|
||||
return '-';
|
||||
},
|
||||
getStatusType(status) {
|
||||
|
||||
return status==1?'success':'info';
|
||||
},
|
||||
handleTabClick(tab) {
|
||||
// tab.name 对应的就是原来的 value ("0", "1" 等)
|
||||
// 注意:el-tabs 的 v-model 绑定的是字符串
|
||||
this.query.state = tab.name;
|
||||
this.handleStateChange(); // 触发你原有的搜索逻辑
|
||||
},
|
||||
// tab.name 对应的就是原来的 value ("0", "1" 等)
|
||||
// 注意:el-tabs 的 v-model 绑定的是字符串
|
||||
this.query.state = tab.name;
|
||||
this.handleStateChange(); // 触发你原有的搜索逻辑
|
||||
},
|
||||
getPercent(row) {
|
||||
if (!row.total_count || row.total_count === 0) return 0;
|
||||
// 计算已发送占比
|
||||
@@ -554,9 +627,7 @@ export default {
|
||||
this.hidePage = false;
|
||||
var journal_id = (this.$route.query && this.$route.query.journal_id) || '';
|
||||
var pfid =
|
||||
(this.$route.query && this.$route.query.promotion_factory_id) ||
|
||||
(this.$route.query && this.$route.query.taskId) ||
|
||||
'';
|
||||
(this.$route.query && this.$route.query.promotion_factory_id) || (this.$route.query && this.$route.query.taskId) || '';
|
||||
this.routePromotionFactoryId = String(pfid || '');
|
||||
this.headerPromotionFactoryId = this.routePromotionFactoryId;
|
||||
this.selectedJournalId = String(journal_id);
|
||||
@@ -662,7 +733,7 @@ export default {
|
||||
}
|
||||
if (!dt || isNaN(dt.getTime())) return String(raw).trim();
|
||||
const pad = (v) => String(v).padStart(2, '0');
|
||||
return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}:${pad(dt.getSeconds())}`;
|
||||
return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())}`;
|
||||
},
|
||||
isFactoryHeaderTaskRunning(task) {
|
||||
if (!task || typeof task !== 'object') return false;
|
||||
@@ -676,9 +747,12 @@ export default {
|
||||
},
|
||||
/** 下拉仅展示「类型 - 创建日期」,运行状态单独用 el-tag */
|
||||
buildFactoryHeaderOptionMainLabel(task, pidFallback) {
|
||||
console.log("🚀 ~ buildFactoryHeaderOptionMainLabel ~ task:", task);
|
||||
|
||||
const typePart = this.getFactoryHeaderTaskTypeLabel(task) || String(pidFallback || '').trim() || '—';
|
||||
const expertTypePart = this.mapFactoryExpertTypeLabel(task.expert_type);
|
||||
const datePart = this.formatFactoryHeaderTaskCreateTime(task);
|
||||
return datePart ? `${typePart} - ${datePart}` : typePart;
|
||||
return datePart ? `${typePart} | ${expertTypePart}${task.expert_type==5 ? ` | ${task.country_scope_label} ` :''} | ${datePart}` : typePart;
|
||||
},
|
||||
replacePromotionFactoryIdInUrl(promotionFactoryId) {
|
||||
try {
|
||||
@@ -704,18 +778,20 @@ export default {
|
||||
});
|
||||
const payload = (res && res.data) || {};
|
||||
const list = this.findArray(payload) || this.findArray(res) || [];
|
||||
let opts = (Array.isArray(list) ? list : []).map((task, idx) => {
|
||||
const pid =
|
||||
task && task.promotion_factory_id != null
|
||||
? String(task.promotion_factory_id)
|
||||
: task && task.id != null
|
||||
? String(task.id)
|
||||
: '';
|
||||
if (!pid) return null;
|
||||
const label = this.buildFactoryHeaderOptionMainLabel(task, pid);
|
||||
const running = this.isFactoryHeaderTaskRunning(task);
|
||||
return { value: pid, label, running };
|
||||
}).filter(Boolean);
|
||||
let opts = (Array.isArray(list) ? list : [])
|
||||
.map((task, idx) => {
|
||||
const pid =
|
||||
task && task.promotion_factory_id != null
|
||||
? String(task.promotion_factory_id)
|
||||
: task && task.id != null
|
||||
? String(task.id)
|
||||
: '';
|
||||
if (!pid) return null;
|
||||
const label = this.buildFactoryHeaderOptionMainLabel(task, pid);
|
||||
const running = this.isFactoryHeaderTaskRunning(task);
|
||||
return { value: pid, label, running,task:task }
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
let cur = String(this.routePromotionFactoryId || this.headerPromotionFactoryId || '').trim();
|
||||
const ids = new Set(opts.map((o) => o.value));
|
||||
@@ -799,10 +875,14 @@ export default {
|
||||
selectedPayload.country_fetch_ids != null
|
||||
? selectedPayload.country_fetch_ids
|
||||
: selectedPayload.country_ids != null
|
||||
? selectedPayload.country_ids
|
||||
: '';
|
||||
? selectedPayload.country_ids
|
||||
: '';
|
||||
if (typeof raw === 'string' && raw.trim()) {
|
||||
return raw.split(',').map((s) => s.trim()).filter(Boolean).map(String);
|
||||
return raw
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map(String);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
@@ -825,7 +905,7 @@ export default {
|
||||
let availableArr = this.findArray(availablePayload);
|
||||
if (!availableArr) availableArr = Array.isArray(availablePayload) ? availablePayload : [];
|
||||
this.availableFields = availableArr.map((item, idx) => {
|
||||
const id = item.expert_fetch_id || item.fetch_id || item.id || item.field_id || (idx + 1);
|
||||
const id = item.expert_fetch_id || item.fetch_id || item.id || item.field_id || idx + 1;
|
||||
const label = item.field || item.title || item.name || item.label || String(id);
|
||||
return { id: String(id), label };
|
||||
});
|
||||
@@ -898,10 +978,10 @@ export default {
|
||||
matched.promotion_factory_id != null
|
||||
? String(matched.promotion_factory_id)
|
||||
: matched.id != null
|
||||
? String(matched.id)
|
||||
: matched.task_id != null
|
||||
? String(matched.task_id)
|
||||
: '';
|
||||
? String(matched.id)
|
||||
: matched.task_id != null
|
||||
? String(matched.task_id)
|
||||
: '';
|
||||
}
|
||||
}
|
||||
// promotion_factory/getDetail 必须使用地址栏 promotion_factory_id,避免列表首行 id 与路由不一致
|
||||
@@ -1035,7 +1115,7 @@ export default {
|
||||
journal_id: String(this.selectedJournalId || ''),
|
||||
factory_id: String(this.routePromotionFactoryId || ''),
|
||||
page: Number(this.query.pageIndex || 1),
|
||||
per_page: Number(this.query.pageSize || 15)
|
||||
per_page: Number(this.query.pageSize || 10)
|
||||
};
|
||||
if (this.query.state !== 'all' && this.query.state !== '' && this.query.state != null) {
|
||||
params.state = String(this.query.state);
|
||||
@@ -1048,11 +1128,7 @@ export default {
|
||||
const runAt = item.run_at || item.run_time || item.plan_time || item.execute_time || item.send_date || '';
|
||||
const state = String(item.state != null ? item.state : '');
|
||||
const promotionFactoryId =
|
||||
item.promotion_factory_id != null
|
||||
? item.promotion_factory_id
|
||||
: item.id != null
|
||||
? item.id
|
||||
: item.task_id;
|
||||
item.promotion_factory_id != null ? item.promotion_factory_id : item.id != null ? item.id : item.task_id;
|
||||
return {
|
||||
id: item.id || item.task_id || `task_${idx + 1}`,
|
||||
promotion_factory_id: promotionFactoryId != null ? String(promotionFactoryId) : '',
|
||||
@@ -1206,16 +1282,16 @@ export default {
|
||||
return Object.prototype.hasOwnProperty.call(stateTextMap, key) ? stateTextMap[key] : '-';
|
||||
},
|
||||
getTaskStatusClass(state) {
|
||||
const stateClassMap = {
|
||||
0: 'status-draft', // Draft - 浅灰色
|
||||
5: 'status-preparing', // Preparing - 橘色
|
||||
1: 'status-running', // Running - 深蓝色
|
||||
2: 'status-paused', // Paused - 深灰色
|
||||
3: 'status-completed', // Completed - 绿色
|
||||
4: 'status-cancelled' // Cancelled - 浅红色
|
||||
};
|
||||
return stateClassMap[Number(state)] || '';
|
||||
}
|
||||
const stateClassMap = {
|
||||
0: 'status-draft', // Draft - 浅灰色
|
||||
5: 'status-preparing', // Preparing - 橘色
|
||||
1: 'status-running', // Running - 深蓝色
|
||||
2: 'status-paused', // Paused - 深灰色
|
||||
3: 'status-completed', // Completed - 绿色
|
||||
4: 'status-cancelled' // Cancelled - 浅红色
|
||||
};
|
||||
return stateClassMap[Number(state)] || '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1229,11 +1305,13 @@ export default {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.config-bar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.config-bar .left {
|
||||
width: calc(100% - 100px);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
@@ -1758,96 +1836,96 @@ export default {
|
||||
}
|
||||
|
||||
.filter-header-row {
|
||||
display: flex;
|
||||
align-items: center; /* 垂直居中对齐 */
|
||||
gap: 16px; /* 胶囊和Search按钮之间的间距 */
|
||||
margin-bottom: 16px;
|
||||
background-color: transparent; /* 容器透明,不厚重 */
|
||||
display: flex;
|
||||
align-items: center; /* 垂直居中对齐 */
|
||||
gap: 16px; /* 胶囊和Search按钮之间的间距 */
|
||||
margin-bottom: 16px;
|
||||
background-color: transparent; /* 容器透明,不厚重 */
|
||||
}
|
||||
|
||||
/* 2. 彻底重置 el-tabs 的原生样式 (最丑的地方) */
|
||||
.tmr-capsule-group {
|
||||
flex: 1; /* 占据左侧空间 */
|
||||
flex: 1; /* 占据左侧空间 */
|
||||
}
|
||||
|
||||
/* 强制隐藏默认灰色横线和卡片灰边 */
|
||||
.tmr-capsule-group .el-tabs__header {
|
||||
margin: 0 !important;
|
||||
border-bottom: none !important;
|
||||
margin: 0 !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.tmr-capsule-group .el-tabs__nav {
|
||||
border: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* 3. 重新定义每个 Tab 的样式 (让其变成按钮) */
|
||||
.tmr-capsule-group .el-tabs--card > .el-tabs__header .el-tabs__item {
|
||||
height: 32px !important; /* 紧凑高度 */
|
||||
line-height: 32px !important;
|
||||
font-size: 13px; /* 紧凑字体 */
|
||||
border: none !important; /* 彻底隐藏原生卡片边框 */
|
||||
background-color: transparent; /* 默认状态下透明背景 */
|
||||
color: #515a6e; /* 默认状态深灰文字,更专业 */
|
||||
transition: all 0.2s ease-in-out;
|
||||
padding: 0 16px !important; /* 适当内边距 */
|
||||
margin-right: 8px; /* 每个 Tab 之间的间距 */
|
||||
border-radius: 6px !important; /* 先统一圆角 */
|
||||
overflow: visible; /* 确保选中的阴影显示完全 */
|
||||
height: 32px !important; /* 紧凑高度 */
|
||||
line-height: 32px !important;
|
||||
font-size: 13px; /* 紧凑字体 */
|
||||
border: none !important; /* 彻底隐藏原生卡片边框 */
|
||||
background-color: transparent; /* 默认状态下透明背景 */
|
||||
color: #515a6e; /* 默认状态深灰文字,更专业 */
|
||||
transition: all 0.2s ease-in-out;
|
||||
padding: 0 16px !important; /* 适当内边距 */
|
||||
margin-right: 8px; /* 每个 Tab 之间的间距 */
|
||||
border-radius: 6px !important; /* 先统一圆角 */
|
||||
overflow: visible; /* 确保选中的阴影显示完全 */
|
||||
}
|
||||
|
||||
/* 首尾 Tab 的圆角处理 (形成整体感) */
|
||||
.tmr-capsule-group .el-tabs--card > .el-tabs__header .el-tabs__item:first-child {
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
}
|
||||
.tmr-capsule-group .el-tabs--card > .el-tabs__header .el-tabs__item:last-child {
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
margin-right: 0;
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* 中间 Tab 的处理 (去掉左右圆角,紧凑对齐) */
|
||||
.tmr-capsule-group .el-tabs--card > .el-tabs__header .el-tabs__item:not(:first-child):not(:last-child) {
|
||||
border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* 4. **选中效果 (Active) - 胶囊浮动核心** */
|
||||
.tmr-capsule-group .el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
|
||||
background-color: #ffffff !important; /* 白色底色 */
|
||||
color: #409eff !important; /* 主题蓝色文字 */
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; /* **关键:增加轻微阴影,浮动感** */
|
||||
border-radius: 6px; /* 选中时恢复圆角 */
|
||||
position: relative;
|
||||
z-index: 2; /* 确保选中态在最前面,不被覆盖线破坏 */
|
||||
background-color: #ffffff !important; /* 白色底色 */
|
||||
color: #409eff !important; /* 主题蓝色文字 */
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; /* **关键:增加轻微阴影,浮动感** */
|
||||
border-radius: 6px; /* 选中时恢复圆角 */
|
||||
position: relative;
|
||||
z-index: 2; /* 确保选中态在最前面,不被覆盖线破坏 */
|
||||
}
|
||||
|
||||
/* 5. 处理 Tab 之间的断层 (像素级微调) */
|
||||
.tmr-capsule-group .el-tabs--card > .el-tabs__header .el-tabs__item {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 首尾 Tab 之间的像素级处理 */
|
||||
.tmr-capsule-group .el-tabs--card > .el-tabs__header .el-tabs__item:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -5px; /* 让 Tab 之间紧密无缝 */
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
background: transparent; /* 移除灰线,不丑 */
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -5px; /* 让 Tab 之间紧密无缝 */
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
background: transparent; /* 移除灰线,不丑 */
|
||||
}
|
||||
|
||||
/* 6. 搜索按钮对齐微调 */
|
||||
.filter-actions .el-button {
|
||||
height: 32px; /* 与 Tab 高度一致 */
|
||||
padding: 0 16px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
height: 32px; /* 与 Tab 高度一致 */
|
||||
padding: 0 16px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
margin-left: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
/* 基础 Badge 样式 */
|
||||
.status-badge {
|
||||
@@ -1894,21 +1972,25 @@ export default {
|
||||
background-color: #f1f5f9;
|
||||
color: #64748b;
|
||||
}
|
||||
.status-draft::before { background-color: #94a3b8; }
|
||||
.status-draft::before {
|
||||
background-color: #94a3b8;
|
||||
}
|
||||
|
||||
/* 5: Preparing - 橘色 */
|
||||
.status-preparing {
|
||||
background-color: #fff7ed;
|
||||
color: #c2410c;
|
||||
}
|
||||
.status-preparing::before { background-color: #f97316; }
|
||||
.status-preparing::before {
|
||||
background-color: #f97316;
|
||||
}
|
||||
|
||||
/* 1: Running - 深蓝色 */
|
||||
.status-running {
|
||||
background-color: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
.status-running::before {
|
||||
.status-running::before {
|
||||
background-color: #3b82f6;
|
||||
box-shadow: 0 0 4px rgba(59, 130, 246, 0.5); /* 模拟运行中的发光感 */
|
||||
}
|
||||
@@ -1919,19 +2001,104 @@ export default {
|
||||
color: #334155;
|
||||
border: 1px solid #e2e8f0; /* 停用状态加个微边框增加分量感 */
|
||||
}
|
||||
.status-paused::before { background-color: #475569; }
|
||||
.status-paused::before {
|
||||
background-color: #475569;
|
||||
}
|
||||
|
||||
/* 3: Completed - 绿色 */
|
||||
.status-completed {
|
||||
background-color: #f0fdf4;
|
||||
color: #15803d;
|
||||
}
|
||||
.status-completed::before { background-color: #22c55e; }
|
||||
.status-completed::before {
|
||||
background-color: #22c55e;
|
||||
}
|
||||
|
||||
/* 4: Cancelled - 浅红色 */
|
||||
.status-cancelled {
|
||||
background-color: #fef2f2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
.status-cancelled::before { background-color: #ef4444; }
|
||||
.status-cancelled::before {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
/* 基础 Select 宽度 */
|
||||
.custom-pipeline-select {
|
||||
width: 580px;
|
||||
}
|
||||
|
||||
/* 分组标题(el-option-group,避免在 ul 内放 div) */
|
||||
.pipeline-popper .el-select-group__title {
|
||||
padding: 10px 20px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #909399;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* 单个选项容器 */
|
||||
.pipeline-popper .el-select-dropdown__item {
|
||||
height: auto; /* 允许高度自适应 */
|
||||
padding: 12px 20px;
|
||||
line-height: normal;
|
||||
border-left: 3px solid transparent; /* 预留边框位置防止抖动 */
|
||||
}
|
||||
|
||||
/* 选中状态 */
|
||||
.pipeline-popper .el-select-dropdown__item.selected {
|
||||
background-color: #f5f7fa;
|
||||
border-left: 3px solid #409eff; /* 选中时的左侧蓝条 */
|
||||
color: #606266; /* 覆盖 Element 默认高亮色 */
|
||||
}
|
||||
|
||||
/* 选项内容布局 */
|
||||
.pipeline-popper .option-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* 第一行:标题 + 状态标签 */
|
||||
.pipeline-popper .option-content .row-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pipeline-popper .option-content .row-top .task-title {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pipeline-popper .option-content .row-top .status-tag {
|
||||
border-radius: 12px;
|
||||
padding: 0 8px;
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
/* 第二行:元数据信息 */
|
||||
.pipeline-popper .option-content .row-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.pipeline-popper .option-content .row-bottom .meta-item.database {
|
||||
color: #5856d6;
|
||||
font-weight: bold;
|
||||
/* text-transform: uppercase; */
|
||||
}
|
||||
|
||||
.pipeline-popper .option-content .row-bottom .separator {
|
||||
margin: 0 6px;
|
||||
color: #dcdfe6;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -198,8 +198,8 @@
|
||||
<el-form-item label="Account :" prop="account">
|
||||
<span>{{coreForm.account}}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="Email :">
|
||||
<span>{{coreForm.email}}</span>
|
||||
<el-form-item label="Email :" prop="email">
|
||||
<el-input type="text" placeholder="Please enter email..." v-model="coreForm.email" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="head portrait :">
|
||||
<el-upload class="avatar-uploader" :action="baseUrl+'master/Journal/up_topic_file'"
|
||||
|
||||
@@ -59,20 +59,22 @@
|
||||
|
||||
<div class="drawer-body" v-loading="loading">
|
||||
<div class="list-header">
|
||||
<div class="col-index">{{ $t('autoPromotionLogs.logColIndex') }}</div>
|
||||
<div class="col-info">{{ $t('autoPromotionLogs.logColExpert') }}</div>
|
||||
<div class="col-send" style="font-size: 12px;">{{ $t('autoPromotionLogs.logColSendTime') }}</div>
|
||||
<div class="col-prepared" style="font-size: 12px;">{{ $t('autoPromotionLogs.logColPreparedAt') }}</div>
|
||||
<div class="col-status">{{ $t('autoPromotionLogs.logColStatus') }}</div>
|
||||
<div class="col-action">{{ $t('autoPromotionLogs.logColAction') }}</div>
|
||||
<div class="col-action"></div>
|
||||
</div>
|
||||
|
||||
<div class="list-wrapper">
|
||||
<div
|
||||
v-for="item in fullData"
|
||||
v-for="(item, rowIndex) in fullData"
|
||||
:key="item.id"
|
||||
class="log-row"
|
||||
:class="{ 'row-error': item.isErrorRow }"
|
||||
>
|
||||
<div class="col-index">{{ (currentPage - 1) * pageSize + rowIndex + 1 }}</div>
|
||||
<div class="col-info">
|
||||
<div class="expert-main">
|
||||
<span class="name">{{ item.expertName }}</span>
|
||||
@@ -782,6 +784,14 @@ export default {
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.col-index {
|
||||
flex: 0 0 52px;
|
||||
width: 52px;
|
||||
text-align: center;
|
||||
color: #64748b;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.col-info {
|
||||
flex: 1.8;
|
||||
}
|
||||
|
||||
@@ -35,30 +35,62 @@
|
||||
<section class="config-section">
|
||||
<h4 class="section-header">
|
||||
<span class="section-header-left"
|
||||
>1. {{ $t('autoPromotion.factoryJournal') }}<span class="factory-required-star" aria-hidden="true">*</span></span
|
||||
>1. {{ $t('autoPromotion.factoryJournal') }} & {{ $t('autoPromotion.factoryExpertType') }}<span class="factory-required-star" aria-hidden="true">*</span></span
|
||||
>
|
||||
</h4>
|
||||
<div class="journal-scenario-row">
|
||||
<div class="journal-col">
|
||||
<el-select
|
||||
v-model="selectedJournalId"
|
||||
filterable
|
||||
clearable
|
||||
:disabled="isEditMode"
|
||||
style="width: 100%"
|
||||
:placeholder="$t('autoPromotion.factoryJournalPlaceholder')"
|
||||
@change="onJournalChange"
|
||||
<div class="combined-selector-container">
|
||||
<div class="journal-selection-side">
|
||||
<!-- <div class="mini-label">{{ $t('autoPromotion.factoryJournal') }}</div> -->
|
||||
<div class="journal-grid-compact">
|
||||
<div
|
||||
v-for="j in journalListForDisplay"
|
||||
:key="String(j.journal_id)"
|
||||
class="journal-card-mini"
|
||||
:class="{ 'is-active': selectedJournalId === j.journal_id, 'is-disabled': isEditMode }"
|
||||
@click="handleSelect(j.journal_id)"
|
||||
>
|
||||
<el-option
|
||||
v-for="j in journalList"
|
||||
:key="String(j.journal_id)"
|
||||
:label="j.title"
|
||||
:value="j.journal_id"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="mini-cover-wrapper">
|
||||
<el-image :src="j.journal_icon" fit="cover" class="mini-cover">
|
||||
<div slot="error" class="image-slot"><i class="el-icon-picture-outline"></i></div>
|
||||
</el-image>
|
||||
<div v-if="selectedJournalId === j.journal_id" class="mini-badge">
|
||||
<i class="el-icon-check"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mini-abbr" :class="{ 'is-active': selectedJournalId === j.journal_id }">{{ j.abbr || j.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="expert-type-side">
|
||||
<div class="mini-label">{{ $t('autoPromotion.factoryExpertType') }}</div>
|
||||
<div class="expert-tile-group custom-scrollbar">
|
||||
<div
|
||||
v-for="opt in visibleExpertOptions"
|
||||
:key="opt.value"
|
||||
class="expert-tile-mini"
|
||||
:class="{ 'is-active': expertType === opt.value, 'is-disabled': isEditMode }"
|
||||
@click="handleExpertTypeClick(opt.value)"
|
||||
>
|
||||
<span class="tile-text">
|
||||
{{ opt.label }}
|
||||
<i v-if="expertTypeCountsLoading" class="el-icon-loading tile-spinner" aria-hidden="true"></i>
|
||||
<span class="tile-count" v-else-if="opt.count != null">({{ opt.count }})</span>
|
||||
</span>
|
||||
<el-button
|
||||
v-if="opt.jump"
|
||||
class="tile-link-btn"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click.stop="openExpertTypeJump(opt)"
|
||||
>
|
||||
{{ $t('autoPromotion.factoryExpertJump') }}
|
||||
</el-button>
|
||||
<i class="el-icon-circle-check" v-if="expertType === opt.value"></i>
|
||||
<i class="el-icon-border-outer" v-else style="visibility: hidden;"></i> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="config-section">
|
||||
@@ -80,6 +112,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section class="config-section">
|
||||
<h4 class="section-header section-header-split">
|
||||
<span class="section-header-left">
|
||||
@@ -93,17 +127,18 @@
|
||||
</h4>
|
||||
<div class="account-scroll-area">
|
||||
<el-checkbox-group v-model="selectedEmailIds" class="account-grid-layout">
|
||||
<div v-for="acc in accountList" :key="String(acc.j_email_id)" class="acc-box">
|
||||
<el-checkbox :label="String(acc.j_email_id)">
|
||||
<div class="acc-info">
|
||||
<span class="u">{{ accountDisplayEmail(acc) }}</span>
|
||||
<span class="m"
|
||||
>{{ $t('autoPromotion.factoryQuotaLabel') }}: {{ acc.remaining_today }}/{{ acc.daily_limit }}</span
|
||||
>
|
||||
</div>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
<div v-for="acc in accountList" :key="String(acc.j_email_id)" class="acc-box">
|
||||
<el-checkbox :label="String(acc.j_email_id)">
|
||||
<div class="acc-info-inline">
|
||||
<span class="u">{{ accountDisplayEmail(acc) }}</span>
|
||||
<span class="divider">|</span>
|
||||
<span class="m">
|
||||
{{ $t('autoPromotion.factoryQuotaLabel') }}: {{ acc.remaining_today }}/{{ acc.daily_limit }}
|
||||
</span>
|
||||
</div>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -140,25 +175,34 @@
|
||||
<el-option :label="$t('autoPromotion.factoryScenarioGeneralThanks')" value="3" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<label class="param-label"
|
||||
>{{ $t('autoPromotion.factoryExpertType') }}<span class="factory-required-star" aria-hidden="true">*</span></label
|
||||
>
|
||||
<el-select v-model="expertType" size="small" class="param-select param-select-expert" :placeholder="$t('autoPromotion.factoryExpertTypePlaceholder')">
|
||||
<el-option :label="$t('autoPromotion.factoryExpertChief')" value="1" />
|
||||
<el-option :label="$t('autoPromotion.factoryExpertBoard')" value="2" />
|
||||
<el-option :label="$t('autoPromotion.factoryExpertYoungBoard')" value="3" />
|
||||
<el-option :label="$t('autoPromotion.factoryExpertAuthor')" value="4" />
|
||||
<el-option :label="$t('autoPromotion.factoryExpertDb')" value="5" />
|
||||
</el-select>
|
||||
</section>
|
||||
|
||||
<section v-if="isExpertDatabaseType" class="config-section">
|
||||
<h4 class="section-header">
|
||||
<span class="section-header-left">
|
||||
<i class="el-icon-location-outline section-header-icon"></i>
|
||||
4. {{ $t('autoPromotion.selectPromotionCountry') }}<span class="factory-required-star" aria-hidden="true">*</span>
|
||||
</span>
|
||||
</h4>
|
||||
<div class="status-confirm-box">
|
||||
|
||||
<div class="country-quick-checks">
|
||||
<el-checkbox-group v-model="factoryZoneCountryIds" size="small">
|
||||
<el-checkbox label="Partition1">{{ $t('autoPromotion.countryQuickZone1') }}</el-checkbox>
|
||||
<el-checkbox label="Partition2">{{ $t('autoPromotion.countryQuickZone2') }}</el-checkbox>
|
||||
<el-checkbox label="Partition3">{{ $t('autoPromotion.countryQuickZone3') }}</el-checkbox>
|
||||
<el-checkbox label="country_china">{{ $t('autoPromotion.countryQuickChina') }}</el-checkbox>
|
||||
<el-checkbox label="country_india">{{ $t('autoPromotion.countryQuickIndia') }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="config-section">
|
||||
<section v-if="isExpertDatabaseType" class="config-section">
|
||||
<h4 class="section-header section-header-split">
|
||||
<span class="section-header-left">
|
||||
<i class="el-icon-collection-tag section-header-icon"></i>
|
||||
4. {{ $t('autoPromotion.selectPromotionFields') }}<span class="factory-required-star" aria-hidden="true">*</span>
|
||||
5. {{ $t('autoPromotion.selectPromotionFields') }}<span class="factory-required-star" aria-hidden="true">*</span>
|
||||
</span>
|
||||
<span class="section-header-right">
|
||||
<span class="selected-count-inline">{{ $t('autoPromotion.selectedCount', { count: factoryFieldIds.length }) }}</span>
|
||||
@@ -184,10 +228,10 @@
|
||||
<div v-if="selectedFieldLabels.length" class="selected-tags">
|
||||
<el-tag v-for="label in selectedFieldLabels" :key="'pf-' + label" size="mini" type="info" effect="plain">{{ label }}</el-tag>
|
||||
</div>
|
||||
<div v-else class="empty-inline">
|
||||
<!-- <div v-else class="empty-inline">
|
||||
<i class="el-icon-collection-tag"></i>
|
||||
<span>{{ $t('autoPromotion.factoryClickConfigureFields') }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="field-tip">
|
||||
<span class="factory-required-star" aria-hidden="true">*</span
|
||||
><span>{{ $t('autoPromotion.factoryPromotionFieldsBlockTip') }}</span>
|
||||
@@ -195,27 +239,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="config-section">
|
||||
<h4 class="section-header">
|
||||
<span class="section-header-left">
|
||||
<i class="el-icon-location-outline section-header-icon"></i>
|
||||
5. {{ $t('autoPromotion.selectPromotionCountry') }}<span class="factory-required-star" aria-hidden="true">*</span>
|
||||
</span>
|
||||
</h4>
|
||||
<div class="status-confirm-box">
|
||||
|
||||
<div class="country-quick-checks">
|
||||
<el-checkbox-group v-model="factoryZoneCountryIds" size="small">
|
||||
<el-checkbox label="Partition1">{{ $t('autoPromotion.countryQuickZone1') }}</el-checkbox>
|
||||
<el-checkbox label="Partition2">{{ $t('autoPromotion.countryQuickZone2') }}</el-checkbox>
|
||||
<el-checkbox label="Partition3">{{ $t('autoPromotion.countryQuickZone3') }}</el-checkbox>
|
||||
<el-checkbox label="country_china">{{ $t('autoPromotion.countryQuickChina') }}</el-checkbox>
|
||||
<el-checkbox label="country_india">{{ $t('autoPromotion.countryQuickIndia') }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -327,6 +350,16 @@
|
||||
sendLimitFromApi: false,
|
||||
factoryType: '1',
|
||||
expertType: '5',
|
||||
expertTypeCounts: {
|
||||
chief: null,
|
||||
board: null,
|
||||
young: null,
|
||||
author: null,
|
||||
reviewer: null,
|
||||
expertDb: null
|
||||
},
|
||||
expertTypeCountsLoading: false,
|
||||
expertTypeCountsReqSeq: 0,
|
||||
factoryStartPromotion: true,
|
||||
factoryConfig: { defaultTemplateId: '', defaultStyleId: '' },
|
||||
factoryTemplateName: '',
|
||||
@@ -355,13 +388,83 @@
|
||||
}
|
||||
},
|
||||
factorySteps() {
|
||||
return [
|
||||
const steps = [
|
||||
{ title: this.$t('autoPromotion.factoryStepNav1Title'), desc: this.$t('autoPromotion.factoryStepNav1Desc') },
|
||||
{ title: this.$t('autoPromotion.factoryStepNav2Title'), desc: this.$t('autoPromotion.factoryStepNav2Desc') },
|
||||
{ title: this.$t('autoPromotion.factoryStepNav3Title'), desc: this.$t('autoPromotion.factoryStepNav3Desc') },
|
||||
{ title: this.$t('autoPromotion.factoryStepNav4Title'), desc: this.$t('autoPromotion.factoryStepNav4Desc') },
|
||||
{ title: this.$t('autoPromotion.factoryStepNav5Title'), desc: this.$t('autoPromotion.factoryStepNav5Desc') }
|
||||
// { title: this.$t('autoPromotion.factoryExpertType'), desc: this.$t('autoPromotion.factoryExpertTypePlaceholder') },
|
||||
{ title: this.$t('autoPromotion.factoryStepNav3Title'), desc: this.$t('autoPromotion.factoryStepNav3Desc') }
|
||||
];
|
||||
if (this.isExpertDatabaseType) {
|
||||
steps.push(
|
||||
{ title: this.$t('autoPromotion.factoryStepNav4Title'), desc: this.$t('autoPromotion.factoryStepNav4Desc') },
|
||||
{ title: this.$t('autoPromotion.factoryStepNav5Title'), desc: this.$t('autoPromotion.factoryStepNav5Desc') }
|
||||
);
|
||||
}
|
||||
return steps;
|
||||
},
|
||||
isExpertDatabaseType() {
|
||||
return String(this.expertType || '') === '5';
|
||||
},
|
||||
expertOptions() {
|
||||
const counts = this.expertTypeCounts || {};
|
||||
return [
|
||||
{
|
||||
value: '1',
|
||||
label: this.$t('autoPromotion.factoryExpertChief'),
|
||||
desc: this.$t('autoPromotion.factoryExpertChief'),
|
||||
icon: 'el-icon-s-custom',
|
||||
count: counts.chief,
|
||||
jump: { path: '/editorBorder', queryKey: 'journal_id' }
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: this.$t('autoPromotion.factoryExpertBoard'),
|
||||
desc: this.$t('autoPromotion.factoryExpertBoard'),
|
||||
icon: 'el-icon-user-solid',
|
||||
count: counts.board,
|
||||
jump: { path: '/editorBorder', queryKey: 'journal_id' }
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: this.$t('autoPromotion.factoryExpertYoungBoard'),
|
||||
desc: this.$t('autoPromotion.factoryExpertYoungBoard'),
|
||||
icon: 'el-icon-user',
|
||||
count: counts.young,
|
||||
jump: { path: '/youthList', queryKey: 'journal_id' }
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: this.$t('autoPromotion.factoryExpertAuthor'),
|
||||
desc: this.$t('autoPromotion.factoryExpertAuthor'),
|
||||
icon: 'el-icon-edit',
|
||||
count: counts.author,
|
||||
jump: { path: '/partyListCorr', queryKey: 'journal_id' }
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: this.$t('autoPromotion.factoryExpertDb'),
|
||||
desc: this.$t('autoPromotion.factoryExpertDb'),
|
||||
icon: 'el-icon-collection',
|
||||
count: counts.expertDb,
|
||||
jump: { path: '/expertDatabase', queryKey: 'journal_id' }
|
||||
}
|
||||
];
|
||||
},
|
||||
visibleExpertOptions() {
|
||||
if (!this.isEditMode) return this.expertOptions;
|
||||
const current = String(this.expertType || '').trim();
|
||||
return this.expertOptions.filter((opt) => String(opt.value) === current);
|
||||
},
|
||||
/** 编辑任务时期刊不可切换:列表只展示当前任务所属期刊 */
|
||||
journalListForDisplay() {
|
||||
const list = Array.isArray(this.journalList) ? this.journalList : [];
|
||||
if (!this.isEditMode) return list;
|
||||
const id = this.selectedJournalId != null && this.selectedJournalId !== '' ? String(this.selectedJournalId) : '';
|
||||
if (!id) return list;
|
||||
const rows = list.filter(function (j) {
|
||||
return String(j.journal_id) === id;
|
||||
});
|
||||
return rows.length ? rows : list;
|
||||
},
|
||||
isEditMode() {
|
||||
const task = this.initialTask;
|
||||
@@ -440,6 +543,10 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleExpertTypeClick(v) {
|
||||
if (this.isEditMode) return;
|
||||
this.expertType = v;
|
||||
},
|
||||
handleDialogOpen() {
|
||||
this.initDialogState();
|
||||
},
|
||||
@@ -472,7 +579,11 @@
|
||||
await this.loadAccounts(this.selectedJournalId);
|
||||
const initialSeed = await this.loadInitialTaskSeed();
|
||||
await this.applyInitialTaskSeed(initialSeed);
|
||||
await Promise.all([this.loadAvailableFields(this.selectedJournalId), this.refreshSendCountMax()]);
|
||||
await Promise.all([
|
||||
this.loadAvailableFields(this.selectedJournalId),
|
||||
this.isExpertDatabaseType ? this.refreshSendCountMax() : Promise.resolve()
|
||||
]);
|
||||
this.fetchExpertTypeCounts();
|
||||
this.syncActiveStep();
|
||||
} else {
|
||||
await this.onJournalChange(this.selectedJournalId);
|
||||
@@ -697,22 +808,23 @@
|
||||
s = 1;
|
||||
}
|
||||
if (
|
||||
this.selectedEmailIds &&
|
||||
this.selectedEmailIds.length > 0 &&
|
||||
String(this.factoryType || '').trim() !== '' &&
|
||||
String(this.expertType || '').trim() !== '' &&
|
||||
this.sendCount != null &&
|
||||
this.sendCount >= 1
|
||||
this.sendCount >= 1 &&
|
||||
this.selectedEmailIds &&
|
||||
this.selectedEmailIds.length > 0
|
||||
) {
|
||||
s = 2;
|
||||
}
|
||||
if (this.factoryFieldIds && this.factoryFieldIds.length > 0) {
|
||||
if (this.isExpertDatabaseType && this.factoryZoneCountryIds && this.factoryZoneCountryIds.length > 0) {
|
||||
s = 3;
|
||||
}
|
||||
if (this.factoryZoneCountryIds && this.factoryZoneCountryIds.length > 0) {
|
||||
if (this.isExpertDatabaseType && this.factoryFieldIds && this.factoryFieldIds.length > 0) {
|
||||
s = 4;
|
||||
}
|
||||
if (s >= 4) s = 4;
|
||||
var maxIdx = (this.factorySteps && this.factorySteps.length ? this.factorySteps.length : 1) - 1;
|
||||
if (s > maxIdx) s = maxIdx;
|
||||
this.activeStep = s;
|
||||
},
|
||||
async loadJournalList() {
|
||||
@@ -721,7 +833,9 @@
|
||||
this.journalList = externalList.map(function (item) {
|
||||
return {
|
||||
journal_id: item.journal_id != null ? item.journal_id : item.id,
|
||||
title: item.title || item.journal_title || item.name || ''
|
||||
title: item.title || item.journal_title || item.name || '',
|
||||
journal_icon: item.journal_icon || '',
|
||||
abbr: item.abbr || ''
|
||||
};
|
||||
});
|
||||
return;
|
||||
@@ -737,9 +851,12 @@
|
||||
list = res;
|
||||
}
|
||||
this.journalList = list.map(function (item) {
|
||||
|
||||
return {
|
||||
journal_id: item.journal_id != null ? item.journal_id : item.id,
|
||||
title: item.title || item.name || ''
|
||||
title: item.title || item.name || '',
|
||||
journal_icon: item.journal_icon || '',
|
||||
abbr: item.abbr || ''
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -749,19 +866,151 @@
|
||||
this.journalLoading = false;
|
||||
}
|
||||
},
|
||||
async handleSelect(id) {
|
||||
if (this.isEditMode) return;
|
||||
|
||||
// 如果点击的是当前已选中的,则置空(实现取消选中功能)
|
||||
const newValue = this.selectedJournalId === id ? null : id;
|
||||
|
||||
// 更新 v-model 绑定的变量
|
||||
this.selectedJournalId = newValue;
|
||||
|
||||
// 调用你原有的业务逻辑方法
|
||||
await this.onJournalChange(newValue);
|
||||
},
|
||||
async onJournalChange(val) {
|
||||
if (!val) {
|
||||
this.accountList = [];
|
||||
this.availableFields = [];
|
||||
this.factoryFieldIds = [];
|
||||
this.selectedEmailIds = [];
|
||||
this.expertTypeCounts = { chief: null, board: null, young: null, author: null, reviewer: null, expertDb: null };
|
||||
this.syncActiveStep();
|
||||
return;
|
||||
}
|
||||
// 切换期刊时:先清空旧人数并立即触发 loading
|
||||
this.expertTypeCounts = { chief: null, board: null, young: null, author: null, reviewer: null, expertDb: null };
|
||||
this.fetchExpertTypeCounts();
|
||||
await this.loadAccounts(val);
|
||||
await Promise.all([this.loadAvailableFields(val), this.refreshSendCountMax()]);
|
||||
await Promise.all([
|
||||
this.loadAvailableFields(val),
|
||||
this.refreshSendCountMax()
|
||||
]);
|
||||
this.syncActiveStep();
|
||||
},
|
||||
openExpertTypeJump(opt) {
|
||||
const jump = opt && opt.jump;
|
||||
if (!jump || !jump.path) return;
|
||||
const journalId = this.selectedJournalId;
|
||||
// 不使用 query(会污染地址栏 + Tags fullPath);改用 sessionStorage 传参,目标页读取一次后即销毁
|
||||
try {
|
||||
const payload = {
|
||||
from: 'promotionFactory',
|
||||
journal_id: journalId != null && journalId !== '' ? String(journalId) : '',
|
||||
ts: Date.now(),
|
||||
targetPath: String(jump.path || '')
|
||||
};
|
||||
sessionStorage.setItem('promotionFactoryJump', JSON.stringify(payload));
|
||||
} catch (e) {}
|
||||
// 跳转前先关闭并销毁弹窗(destroy-on-close),避免“中间态”残留
|
||||
this.innerVisible = false;
|
||||
this.$nextTick(() => {
|
||||
this.$router.push({ path: jump.path });
|
||||
});
|
||||
},
|
||||
async fetchExpertTypeCounts() {
|
||||
const journalId = this.selectedJournalId;
|
||||
if (!journalId) return;
|
||||
const reqId = ++this.expertTypeCountsReqSeq;
|
||||
this.expertTypeCountsLoading = true;
|
||||
try {
|
||||
const safeCount = (v) => (v == null || isNaN(Number(v)) ? 0 : Number(v));
|
||||
const countBoardMembers = (member) => {
|
||||
if (!member) return 0;
|
||||
if (Array.isArray(member)) return member.length;
|
||||
let sum = 0;
|
||||
Object.keys(member).forEach((k) => {
|
||||
const v = member[k];
|
||||
if (Array.isArray(v)) sum += v.length;
|
||||
else if (v && typeof v === 'object' && Array.isArray(v.list)) sum += v.list.length;
|
||||
else if (v && typeof v === 'object' && Array.isArray(v.data)) sum += v.data.length;
|
||||
else if (v && typeof v === 'object') sum += 1;
|
||||
});
|
||||
return sum;
|
||||
};
|
||||
|
||||
const [boardRes, youngRes, authorRes, reviewerRes, expertDbRes] = await Promise.all([
|
||||
this.$api.post('api/Board/getBoards', { journal_id: journalId }).catch(() => null),
|
||||
this.$api
|
||||
.post('api/User/getYboardlist', {
|
||||
journal_id: journalId,
|
||||
type: 1,
|
||||
year: 0,
|
||||
pageIndex: 1,
|
||||
pageSize: 1,
|
||||
keywords: '',
|
||||
fieldkey: '',
|
||||
order_remark: 0
|
||||
})
|
||||
.catch(() => null),
|
||||
this.$api
|
||||
.post('api/User/authorDatabase', {
|
||||
journal_id: journalId,
|
||||
keywords: '',
|
||||
page: 1,
|
||||
limit: 1
|
||||
})
|
||||
.catch(() => null),
|
||||
this.$api
|
||||
.post('api/Reviewer/getReviewerListByJournal', {
|
||||
username: localStorage.getItem('U_name'),
|
||||
journalId: journalId,
|
||||
class: 0,
|
||||
pageIndex: 1,
|
||||
pageSize: 1
|
||||
})
|
||||
.catch(() => null),
|
||||
this.$api
|
||||
.post('api/expert_manage/getList', {
|
||||
pageIndex: 1,
|
||||
pageSize: 1
|
||||
})
|
||||
.catch(() => null)
|
||||
]);
|
||||
|
||||
const next = { chief: null, board: null, young: null, author: null, reviewer: null, expertDb: null };
|
||||
|
||||
if (boardRes && boardRes.code === 0 && boardRes.data && boardRes.data.boards) {
|
||||
const b = boardRes.data.boards || {};
|
||||
const chief = Array.isArray(b.main) ? b.main.length : 0;
|
||||
const remain = Array.isArray(b.remain) ? b.remain.length : 0;
|
||||
const member = countBoardMembers(b.member);
|
||||
next.chief = safeCount(chief);
|
||||
next.board = safeCount(remain + member);
|
||||
}
|
||||
if (youngRes && youngRes.code === 0) {
|
||||
next.young = safeCount(youngRes.data && youngRes.data.count);
|
||||
}
|
||||
if (authorRes && authorRes.code === 0) {
|
||||
next.author = safeCount(authorRes.data && authorRes.data.count);
|
||||
}
|
||||
if (reviewerRes && reviewerRes.code === 0) {
|
||||
next.reviewer = safeCount(reviewerRes.total);
|
||||
}
|
||||
if (expertDbRes && expertDbRes.code === 0) {
|
||||
const total = (expertDbRes.data && (expertDbRes.data.total || expertDbRes.data.count)) || expertDbRes.total;
|
||||
next.expertDb = safeCount(total);
|
||||
}
|
||||
|
||||
// 防止快速切换期刊导致旧请求覆盖新请求
|
||||
if (reqId !== this.expertTypeCountsReqSeq) return;
|
||||
this.expertTypeCounts = next;
|
||||
} finally {
|
||||
if (reqId === this.expertTypeCountsReqSeq) {
|
||||
this.expertTypeCountsLoading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadAccounts(id) {
|
||||
this.accountsLoading = true;
|
||||
try {
|
||||
@@ -879,18 +1128,18 @@
|
||||
let fromApi = false;
|
||||
try {
|
||||
if (this.selectedEmailIds && this.selectedEmailIds.length > 0) {
|
||||
// 优先走后端限制接口:api/promotion_factory/getCountForPromotionEmailIds
|
||||
const emailCap = await this.loadSendCountMaxFromEmailIds();
|
||||
if (emailCap.max > 0) {
|
||||
max = emailCap.max;
|
||||
fromApi = emailCap.fromApi;
|
||||
}
|
||||
}
|
||||
// 没有选账号或接口不可用时:回退为邮箱额度合计(或默认上限)
|
||||
if (!max || max < 1) {
|
||||
const fallbackCap = this.loadSendCountMaxFallback();
|
||||
max = fallbackCap.max;
|
||||
if (!fromApi && fallbackCap.fromApi) {
|
||||
fromApi = true;
|
||||
}
|
||||
fromApi = false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -994,11 +1243,19 @@
|
||||
this.$message.warning(this.$t('autoPromotion.factoryNeedExpertType'));
|
||||
return;
|
||||
}
|
||||
if (!this.factoryFieldIds || !this.factoryFieldIds.length) {
|
||||
if (!String(this.factoryType || '').trim()) {
|
||||
this.$message.warning(this.$t('autoPromotion.factoryNeedExpertType'));
|
||||
return;
|
||||
}
|
||||
if (this.sendCount == null || Number(this.sendCount) < 1) {
|
||||
this.$message.warning(this.$t('autoPromotion.factoryFillRequired'));
|
||||
return;
|
||||
}
|
||||
if (this.isExpertDatabaseType && (!this.factoryFieldIds || !this.factoryFieldIds.length)) {
|
||||
this.$message.warning(this.$t('autoPromotion.factoryNeedPromotionFields'));
|
||||
return;
|
||||
}
|
||||
if (!this.factoryZoneCountryIds || !this.factoryZoneCountryIds.length) {
|
||||
if (this.isExpertDatabaseType && (!this.factoryZoneCountryIds || !this.factoryZoneCountryIds.length)) {
|
||||
this.$message.warning(this.$t('autoPromotion.factoryNeedPromotionCountry'));
|
||||
return;
|
||||
}
|
||||
@@ -1011,9 +1268,9 @@
|
||||
send_count: String(this.sendCount),
|
||||
template_id: String(this.factoryConfig.defaultTemplateId),
|
||||
style_id: String(this.factoryConfig.defaultStyleId),
|
||||
fetch_ids: (this.factoryFieldIds || []).join(','),
|
||||
target_partitions: pc.target_partitions,
|
||||
target_country_ids: pc.target_country_ids,
|
||||
fetch_ids: this.isExpertDatabaseType ? (this.factoryFieldIds || []).join(',') : '',
|
||||
target_partitions: this.isExpertDatabaseType ? pc.target_partitions : '',
|
||||
target_country_ids: this.isExpertDatabaseType ? pc.target_country_ids : '',
|
||||
start_promotion: '0'
|
||||
};
|
||||
const endpoint = this.isEditMode ? API_FACTORY_EDIT : API_FACTORY_ADD;
|
||||
@@ -1098,10 +1355,10 @@
|
||||
|
||||
/* 左侧步骤条样式 (模仿图一) */
|
||||
.step-navigation {
|
||||
width: 240px;
|
||||
width: 230px;
|
||||
background: #fafbfc;
|
||||
border-right: 1px solid #edf0f5;
|
||||
padding: 10px 20px 30px 10px;
|
||||
padding: 10px 20px 30px 0px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
@@ -1300,10 +1557,10 @@
|
||||
.click-card {
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
padding: 4px 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-height: 50px;
|
||||
min-height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1325,21 +1582,69 @@
|
||||
}
|
||||
|
||||
.account-grid-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.acc-box {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.acc-info { display: flex; flex-direction: column; margin-left: 5px; }
|
||||
.acc-info .u { font-size: 12px; font-weight: 500; color: #333; }
|
||||
.acc-info .m { font-size: 11px; color: #999; }
|
||||
display: grid;
|
||||
/* 如果希望固定 3 个一行:repeat(3, 1fr);如果自适应:repeat(auto-fill, minmax(280px, 1fr)) */
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 账号外框:大幅缩小内边距和高度 */
|
||||
.acc-box {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
padding: 4px 10px; /* 极窄内边距 */
|
||||
background: #fff;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px; /* 强制固定一行的高度 */
|
||||
}
|
||||
|
||||
.acc-box:hover {
|
||||
border-color: #409eff;
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
/* 内部信息:改为横向 Flex */
|
||||
.acc-info-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.acc-info-inline .u {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
max-width: 180px; /* 防止邮箱过长撑破布局 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.acc-info-inline .m {
|
||||
color: #909399;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
color: #dcdfe6;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* 深度选择器:去除 Element 默认的间距 */
|
||||
.acc-box /deep/ .el-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.acc-box /deep/ .el-checkbox__label {
|
||||
padding-left: 8px;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 底部参数栏:白底 + 左侧蓝边;标签与控件同一行横向排布 */
|
||||
.parameter-bar {
|
||||
@@ -1483,4 +1788,301 @@
|
||||
padding-top:15px !important;
|
||||
padding-bottom:15px !important;
|
||||
}
|
||||
</style>
|
||||
.journal-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.journal-card {
|
||||
width: 80px; /* 根据实际需求调整宽度 */
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
background: #f8f9fa;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border-color: #409eff; /* ElementUI 主题色 */
|
||||
background: #ecf5ff;
|
||||
.journal-abbreviation {
|
||||
color: #409eff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.journal-cover {
|
||||
width: 100%;
|
||||
height: 110px; /* 保持期刊封面比例 */
|
||||
margin-bottom: 8px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
|
||||
.el-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.journal-abbreviation {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.active-badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* 3. 核心美化区块:期刊+专家合并 */
|
||||
.combined-selector-container {
|
||||
display: flex;
|
||||
gap: 30px; /* 期刊和专家类型之间的间距 */
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid #ebf0f5;
|
||||
align-items: flex-start; /* 顶部对齐 */
|
||||
}
|
||||
|
||||
/* 左侧期刊区域:由内容撑开,不收缩 */
|
||||
.journal-selection-side {
|
||||
flex-shrink: 0;
|
||||
max-width:450px;
|
||||
|
||||
/* 如果你想让左侧也稍微宽点,可以设置 min-width: 300px; */
|
||||
}
|
||||
|
||||
|
||||
.mini-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
|
||||
}
|
||||
|
||||
/* 左侧期刊:更紧凑的列表 */
|
||||
.journal-selection-side {
|
||||
flex: 1;
|
||||
border-right: 1px solid #e4e7ed;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.journal-grid-compact {
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.journal-card-mini {
|
||||
width: 70px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mini-cover {
|
||||
width: 70px;
|
||||
height: 90px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||
border: 2px solid transparent;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.journal-card-mini.is-active .mini-cover {
|
||||
border-color: #409eff;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.mini-abbr {
|
||||
font-size: 11px;
|
||||
margin-top: 5px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
/* 期刊网格微调 */
|
||||
.journal-grid-compact { display: flex; flex-wrap: wrap; gap: 12px; }
|
||||
.journal-card-mini { width: 70px; text-align: center; cursor: pointer; }
|
||||
.mini-cover-wrapper { position: relative; width: 70px; height: 95px; }
|
||||
.mini-cover {
|
||||
width: 100%; height: 100%; border-radius: 4px; border: 2px solid transparent;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.1); transition: 0.3s;
|
||||
}
|
||||
.journal-card-mini.is-active .mini-cover { border-color: #409eff; transform: translateY(-3px); }
|
||||
.mini-badge {
|
||||
position: absolute; top: -6px; right: -6px; background: #67c23a; color: #fff;
|
||||
width: 18px; height: 18px; border-radius: 50%; font-size: 10px;
|
||||
display: flex; align-items: center; justify-content: center; z-index: 2;
|
||||
}
|
||||
.mini-abbr { font-size: 11px; color: #606266; margin-top: 6px; overflow: hidden; text-overflow: ellipsis; }
|
||||
.mini-abbr.is-active {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.expert-type-side {
|
||||
flex: 1; /* 核心代码:占据剩余空间 */
|
||||
min-width: 0; /* 防止 Flex 子项溢出 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
.mini-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px; /* 紧凑间距 */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
.expert-tile-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.expert-tile-mini {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 12px;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
/* --- 关键修改:不强制占满 --- */
|
||||
flex: 0 0 auto; /* 不放大,不缩小,根据内容定宽度 */
|
||||
min-width: 140px; /* 给一个最小宽度,保证整齐度 */
|
||||
max-width: 300px; /* 限制最大宽度,防止太长 */
|
||||
/* ------------------------ */
|
||||
|
||||
height: 34px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.expert-tile-mini.is-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.expert-tile-mini.is-disabled:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.tile-text {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.tile-count {
|
||||
margin-left: 4px;
|
||||
color: #909399;
|
||||
font-weight: 400;
|
||||
}
|
||||
.tile-count--loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.tile-link-btn {
|
||||
padding: 0;
|
||||
margin-left: auto;
|
||||
line-height: 1;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tile-spinner {
|
||||
margin-left: 6px;
|
||||
color: #409eff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 选中状态 */
|
||||
.expert-tile-mini.is-active {
|
||||
border-color: #409eff;
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
|
||||
.expert-tile-mini:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.expert-tile-mini.is-active .tile-text {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 选中图标微型化 */
|
||||
.expert-tile-mini i {
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
/* 隐藏横向滚动条,美化纵向滚动条 */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 激活状态左侧的小指示条(可选,增加精致感) */
|
||||
.expert-tile-mini.is-active {
|
||||
position: relative;
|
||||
}
|
||||
.expert-tile-mini.is-active::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 2px;
|
||||
background: #409eff;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -74,7 +74,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mail-body-content" v-html="mailData.content_html"></div>
|
||||
<div class="mail-body-content" v-html="mailBodyHtml"></div>
|
||||
|
||||
<div v-if="mailData.attachments && mailData.attachments.length" class="attachment-section">
|
||||
<div class="attachment-header">
|
||||
@@ -123,6 +123,7 @@
|
||||
|
||||
<script>
|
||||
import Common from '@/components/common/common';
|
||||
import { normalizeEmailHtmlForInlineDisplay } from '@/utils/emailHtmlView';
|
||||
import JSZip from 'jszip';
|
||||
import { saveAs } from 'file-saver';
|
||||
import FilePreviewDialog from './FilePreviewDialog.vue';
|
||||
@@ -159,9 +160,29 @@ export default {
|
||||
if (!this.mailData.attachments || !this.mailData.attachments.length) return '0B';
|
||||
const total = this.mailData.attachments.reduce((sum, f) => sum + (Number(f.size) || 0), 0);
|
||||
return this.formatFileSize(total);
|
||||
},
|
||||
/** 正文:兼容 content_html / body / html,纯文本时包一层 pre */
|
||||
mailBodyHtml() {
|
||||
const m = this.mailData || {};
|
||||
let raw = m.content_html || m.body_html || m.html || m.body || m.content || '';
|
||||
raw = normalizeEmailHtmlForInlineDisplay(raw);
|
||||
if (raw) return raw;
|
||||
const text = m.content_text;
|
||||
if (text != null && String(text).trim() !== '') {
|
||||
return `<pre class="mail-plain-pre">${this.escapeHtml(String(text))}</pre>`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
escapeHtml(text) {
|
||||
if (text == null) return '';
|
||||
return String(text)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
},
|
||||
scrollToAttachments() {
|
||||
// 1. 获取目标元素的 DOM
|
||||
const target = this.$el.querySelector('.attachment-section');
|
||||
@@ -441,6 +462,15 @@ const res = await this.$api.post('api/email_client/getAttachment', {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
/* v-html 注入的正文无 scoped 属性,用 deep 命中内部的 pre */
|
||||
.mail-body-content >>> .mail-plain-pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 附件卡片 */
|
||||
.attachment-section {
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
@@ -771,6 +771,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bus from '../common/bus'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -1188,6 +1189,43 @@ export default {
|
||||
this.getjour();
|
||||
},
|
||||
methods: {
|
||||
readPromotionFactoryJumpJournalId() {
|
||||
try {
|
||||
const raw = sessionStorage.getItem('promotionFactoryJump');
|
||||
if (!raw) return '';
|
||||
const obj = JSON.parse(raw || '{}');
|
||||
if (!obj || obj.from !== 'promotionFactory') return '';
|
||||
if (obj.targetPath && String(obj.targetPath) !== String(this.$route.path)) return '';
|
||||
const id = obj.journal_id != null ? String(obj.journal_id) : '';
|
||||
sessionStorage.removeItem('promotionFactoryJump');
|
||||
return id;
|
||||
} catch (e) {
|
||||
try {
|
||||
sessionStorage.removeItem('promotionFactoryJump');
|
||||
} catch (e2) {}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
forceClearJumpQuery() {
|
||||
const q = (this.$route && this.$route.query) || {};
|
||||
const has = q && (q._ap_from || q.journal_id || q.journalId || q.journalIdd);
|
||||
if (!has) return;
|
||||
const oldFullPath = this.$route && this.$route.fullPath ? this.$route.fullPath : '';
|
||||
const nextQuery = Object.assign({}, q);
|
||||
delete nextQuery._ap_from;
|
||||
delete nextQuery.journal_id;
|
||||
delete nextQuery.journalId;
|
||||
delete nextQuery.journalIdd;
|
||||
// 关闭“中间态 fullPath”对应的标签页,避免残留在顶部 Tags
|
||||
if (oldFullPath) {
|
||||
bus.$emit('close_tag_by_path', { path: oldFullPath, silent: true });
|
||||
}
|
||||
this.$router.replace({ path: this.$route.path, query: nextQuery }).catch(() => {});
|
||||
try {
|
||||
const cleanUrl = window.location.origin + this.$route.path;
|
||||
window.history.replaceState({}, '', cleanUrl);
|
||||
} catch (e) {}
|
||||
},
|
||||
unplIcon() {
|
||||
this.$refs['upIconIMg'].$refs['upload-inner'].handleClick();
|
||||
},
|
||||
@@ -1314,9 +1352,28 @@ export default {
|
||||
if (res.code == 0) {
|
||||
this.df_jour = res.data.journals;
|
||||
this.add_jour = res.data.journals;
|
||||
this.query.journal_id = this.df_jour[0].journal_id;
|
||||
this.query.journal_title = this.df_jour[0].title;
|
||||
const fromQueryId = this.$route && this.$route.query ? this.$route.query.journal_id : '';
|
||||
const fromSessionId = this.readPromotionFactoryJumpJournalId();
|
||||
const pickedId = fromQueryId || fromSessionId;
|
||||
const matched = pickedId ? this.df_jour.find((j) => String(j.journal_id) === String(pickedId)) : null;
|
||||
const first = this.df_jour[0];
|
||||
const picked = matched || first;
|
||||
this.query.journal_id = picked ? picked.journal_id : 0;
|
||||
this.query.journal_title = picked ? picked.title : '';
|
||||
this.getgroup();
|
||||
|
||||
// 仅首次接收参数:应用后立刻清掉 URL,避免刷新仍然固定某一本期刊
|
||||
if (fromQueryId) {
|
||||
const nextQuery = Object.assign({}, (this.$route && this.$route.query) || {});
|
||||
delete nextQuery.journal_id;
|
||||
delete nextQuery.journalId;
|
||||
delete nextQuery._ap_from;
|
||||
this.$router.replace({ path: this.$route.path, query: nextQuery }).catch(() => {});
|
||||
// 双保险:路由稳定后再强制清参一次,避免地址栏残留“中间态”
|
||||
setTimeout(() => {
|
||||
this.forceClearJumpQuery();
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,32 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="country" :label="$t('expertDatabase.columns.country')" min-width="100" />
|
||||
<el-table-column prop="affiliation" :label="$t('expertDatabase.columns.affiliation')" min-width="460" />
|
||||
<el-table-column :label="$t('expertDatabase.columns.unsubscribeStatus')" width="180" align="center">
|
||||
<template slot-scope="scope">
|
||||
<div class="unsubscribe-cell">
|
||||
<el-tag
|
||||
size="mini"
|
||||
:type="getUnsubscribeValue(scope.row) === 1 ? 'warning' : 'success'"
|
||||
effect="plain"
|
||||
class="unsubscribe-tag"
|
||||
>
|
||||
{{
|
||||
getUnsubscribeValue(scope.row) === 1
|
||||
? $t('expertDatabase.unsubscribeUnsubscribed')
|
||||
: $t('expertDatabase.unsubscribeNormal')
|
||||
}}
|
||||
</el-tag>
|
||||
<el-switch
|
||||
class="unsubscribe-switch"
|
||||
:value="getUnsubscribeValue(scope.row) === 0"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#dcdfe6"
|
||||
:disabled="switchingExpertId === getExpertId(scope.row)"
|
||||
@change="(val) => handleUnsubscribeSwitch(scope.row, val)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fieldDisplay" :label="$t('expertDatabase.columns.researchAreas')" min-width="260">
|
||||
<template slot-scope="scope">
|
||||
<div v-for="(field, index) in scope.row.fields" :key="index">
|
||||
@@ -158,6 +184,7 @@ export default {
|
||||
total: 0,
|
||||
loading: false,
|
||||
exportLoading: false,
|
||||
switchingExpertId: '',
|
||||
fieldDetailVisible: false,
|
||||
fieldDetailExpert: null,
|
||||
fieldDetailRows: []
|
||||
@@ -223,8 +250,12 @@ export default {
|
||||
this.list = rawList.map((item) => {
|
||||
const fieldArray = item.fields || [];
|
||||
const fieldNames = fieldArray.map((f) => f.field).join(', ');
|
||||
const unsubscribed = this.normalizeUnsubscribeValue(item);
|
||||
return {
|
||||
...item,
|
||||
// 以 unsubscribed 作为页面主判断字段
|
||||
unsubscribed,
|
||||
unsubscribe: unsubscribed,
|
||||
fieldDisplay: fieldNames
|
||||
};
|
||||
});
|
||||
@@ -240,6 +271,57 @@ export default {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
getExpertId(row) {
|
||||
if (!row) return '';
|
||||
return String(row.expert_id || row.id || row.user_id || row.uid || '').trim();
|
||||
},
|
||||
normalizeUnsubscribeValue(row) {
|
||||
const raw =
|
||||
row && row.unsubscribed != null
|
||||
? row.unsubscribed
|
||||
: row && row.unsubscribe != null
|
||||
? row.unsubscribe
|
||||
: row && row.is_unsubscribe != null
|
||||
? row.is_unsubscribe
|
||||
: 0;
|
||||
return Number(raw) === 1 ? 1 : 0;
|
||||
},
|
||||
getUnsubscribeValue(row) {
|
||||
return this.normalizeUnsubscribeValue(row);
|
||||
},
|
||||
async handleUnsubscribeSwitch(row, checked) {
|
||||
const expertId = this.getExpertId(row);
|
||||
if (!expertId) {
|
||||
this.$message.warning(this.$t('expertDatabase.unsubscribeMissingId'));
|
||||
return;
|
||||
}
|
||||
const oldValue = this.getUnsubscribeValue(row);
|
||||
// switch: ON(checked)=正常(0), OFF=退订(1)
|
||||
const nextValue = checked ? 0 : 1;
|
||||
if (oldValue === nextValue) return;
|
||||
row.unsubscribed = nextValue;
|
||||
row.unsubscribe = nextValue;
|
||||
this.switchingExpertId = expertId;
|
||||
try {
|
||||
const apiUrl = nextValue === 1 ? 'api/expert_manage/unsubscribe' : 'api/expert_manage/resubscribe';
|
||||
const res = await this.$api.post(apiUrl, {
|
||||
expert_ids: String(expertId)
|
||||
});
|
||||
if (res && Number(res.code) === 0) {
|
||||
this.$message.success(this.$t('expertDatabase.unsubscribeUpdateSuccess'));
|
||||
} else {
|
||||
row.unsubscribed = oldValue;
|
||||
row.unsubscribe = oldValue;
|
||||
this.$message.error((res && res.msg) || this.$t('expertDatabase.unsubscribeUpdateFailed'));
|
||||
}
|
||||
} catch (e) {
|
||||
row.unsubscribed = oldValue;
|
||||
row.unsubscribe = oldValue;
|
||||
this.$message.error(this.$t('expertDatabase.unsubscribeUpdateFailed'));
|
||||
} finally {
|
||||
this.switchingExpertId = '';
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.query.pageIndex = 1;
|
||||
this.fetchList();
|
||||
@@ -390,5 +472,24 @@ export default {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
.unsubscribe-cell {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.unsubscribe-tag {
|
||||
min-width: 56px;
|
||||
height: 22px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.unsubscribe-switch {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -109,7 +109,11 @@
|
||||
<div class="mail-content-panel" v-if="selectedAccount" v-loading="detailLoading">
|
||||
<template v-if="activeMailId && !detailLoading">
|
||||
<div class="mail-page">
|
||||
<mail-detail v-if="detailMail" :mailData="detailMail" @close="closeDetail" />
|
||||
<mail-detail
|
||||
v-if="detailMail && String(detailMail.inbox_id || '') !== ''"
|
||||
:mailData="detailMail"
|
||||
@close="closeDetail"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -168,9 +172,11 @@ const API = {
|
||||
getAllJournal: 'api/Article/getJournal'
|
||||
};
|
||||
import MailDetail from '../../components/page/components/email/MailDetail.vue';
|
||||
import { normalizeEmailHtmlForInlineDisplay } from '@/utils/emailHtmlView';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
baseUrl: this.Common.baseUrl,
|
||||
currentFolder: 'inbox',
|
||||
searchKeyword: '',
|
||||
syncLoading: false,
|
||||
@@ -292,8 +298,14 @@ import MailDetail from '../../components/page/components/email/MailDetail.vue';
|
||||
this.$api
|
||||
.post(API.getOneEmail, { j_email_id: jEmailId })
|
||||
.then((res) => {
|
||||
const email = res && res.data ? res.data.email : null;
|
||||
if (res && res.code === 0 && email) {
|
||||
const raw = res && res.data != null ? res.data : null;
|
||||
const email =
|
||||
raw && typeof raw === 'object' && raw.email && typeof raw.email === 'object'
|
||||
? raw.email
|
||||
: raw && typeof raw === 'object' && (raw.j_email_id != null || raw.smtp_user)
|
||||
? raw
|
||||
: null;
|
||||
if (res && Number(res.code) === 0 && email) {
|
||||
this.selectedAccount = email;
|
||||
this.fetchData();
|
||||
this.startInboxSse();
|
||||
@@ -488,28 +500,103 @@ fetchLatestSingleMail(jEmailId, journalId) {
|
||||
this.currentFolder = f;
|
||||
this.activeMailId = null;
|
||||
},
|
||||
selectMail(item,index) {
|
||||
/** 接口 code 可能是数字 0 或字符串 "0" */
|
||||
isEmailApiSuccess(res) {
|
||||
if (!res || res.code === undefined || res.code === null) return false;
|
||||
return Number(res.code) === 0;
|
||||
},
|
||||
escapeHtml(text) {
|
||||
if (text == null) return '';
|
||||
return String(text)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
},
|
||||
/** 解析 getEmailDetail 返回体,统一 content 字段;失败时用列表行兜底 */
|
||||
buildDetailMailFromResponse(res, item, inboxId) {
|
||||
const ok = this.isEmailApiSuccess(res);
|
||||
let d = res && res.data != null ? res.data : null;
|
||||
if (d && typeof d === 'string') {
|
||||
try {
|
||||
d = JSON.parse(d);
|
||||
} catch (e) {
|
||||
d = null;
|
||||
}
|
||||
}
|
||||
if (Array.isArray(d) && d.length) {
|
||||
d = d[0];
|
||||
}
|
||||
if (ok && d && typeof d === 'object' && d.data && typeof d.data === 'object' && !d.content_html && !d.content_text) {
|
||||
d = { ...d, ...d.data };
|
||||
}
|
||||
if (ok && (!d || typeof d !== 'object') && res && (res.content_html != null || res.content_text != null || res.subject != null)) {
|
||||
d = { ...res };
|
||||
delete d.code;
|
||||
delete d.msg;
|
||||
if (Object.prototype.hasOwnProperty.call(d, 'data')) delete d.data;
|
||||
}
|
||||
const inboxKey = String(inboxId);
|
||||
const listSnippet = (item && item.content) || '';
|
||||
if (!ok || !d || typeof d !== 'object') {
|
||||
return {
|
||||
...item,
|
||||
subject: (item && item.subject) || '',
|
||||
from_name: (item && item.from_name) || '',
|
||||
from_email: (item && item.email) || '',
|
||||
email_date: item && item.email_date,
|
||||
content_html: listSnippet,
|
||||
inbox_id: inboxKey,
|
||||
attachments: [],
|
||||
has_attachment: (item && item.has_attachment) || 0
|
||||
};
|
||||
}
|
||||
const html = d.content_html || d.body_html || d.html || d.body || '';
|
||||
const text = d.content_text || '';
|
||||
let content_html =
|
||||
html ||
|
||||
(text ? `<pre class="mail-plain-pre">${this.escapeHtml(text)}</pre>` : '') ||
|
||||
listSnippet;
|
||||
if (content_html && typeof content_html === 'string' && /^<!DOCTYPE|^<\s*html[\s>]/i.test(content_html.trim())) {
|
||||
const normalized = normalizeEmailHtmlForInlineDisplay(content_html);
|
||||
if (normalized && normalized.trim()) content_html = normalized;
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
...d,
|
||||
content_html,
|
||||
inbox_id: inboxKey,
|
||||
attachments: []
|
||||
};
|
||||
},
|
||||
selectMail(item, index) {
|
||||
this.activeMailId = item.id;
|
||||
this.detailLoading = true;
|
||||
this.detailMail = {};
|
||||
const inboxId = item.inbox_id || item.id;
|
||||
this.$api
|
||||
.post(API.getEmailDetail, { inbox_id: inboxId })
|
||||
.then((res) => {
|
||||
const d = res && res.data;
|
||||
if (res && res.code === 0 && d) {
|
||||
this.detailMail = { ...d, inbox_id: String(inboxId), attachments: [] };
|
||||
item.state = 0;
|
||||
this.displayList[index].is_read = d.is_read;
|
||||
if (Number(d.has_attachment) === 1) {
|
||||
this.fetchAttachments(String(inboxId));
|
||||
} else {
|
||||
this.detailLoading = false;
|
||||
try {
|
||||
const merged = this.buildDetailMailFromResponse(res, item, inboxId);
|
||||
this.detailMail = merged;
|
||||
if (item) item.state = 0;
|
||||
const row = this.displayList[index];
|
||||
if (row && merged && merged.is_read !== undefined && merged.is_read !== null) {
|
||||
this.$set(row, 'is_read', merged.is_read);
|
||||
}
|
||||
} else {
|
||||
this.detailLoading = false;
|
||||
if (Number(merged.has_attachment) === 1) {
|
||||
this.fetchAttachments(String(inboxId));
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.detailMail = this.buildDetailMailFromResponse(null, item, inboxId);
|
||||
}
|
||||
this.detailLoading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.detailMail = this.buildDetailMailFromResponse(null, item, inboxId);
|
||||
this.detailLoading = false;
|
||||
});
|
||||
},
|
||||
@@ -645,7 +732,7 @@ fetchLatestSingleMail(jEmailId, journalId) {
|
||||
},
|
||||
buildSseUrl(jEmailId) {
|
||||
// 与现有 axios baseURL=/api + 相对路径 规则一致;GET + query 传参
|
||||
const base = `/api/${API.inboxSse}`;
|
||||
const base = `${this.baseUrl}${API.inboxSse}`;
|
||||
const q = new URLSearchParams({ j_email_id: String(jEmailId) }).toString();
|
||||
return `${base}?${q}`;
|
||||
},
|
||||
|
||||
@@ -147,6 +147,12 @@ const API = {
|
||||
deleteTemplate: 'api/mail_template/deleteTemplate',
|
||||
deleteStyle: 'api/mail_template/deleteStyle'
|
||||
};
|
||||
// 仅在当前 SPA 会话内记忆筛选(刷新页面即重置)
|
||||
const mailboxMouldSessionMemory = {
|
||||
journalId: '',
|
||||
scene: '',
|
||||
language: ''
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -203,8 +209,15 @@ export default {
|
||||
}));
|
||||
this.journalList = mapped;
|
||||
if (mapped.length > 0) {
|
||||
this.tplFilters.journalId = String(mapped[0].journal_id);
|
||||
const rememberedJournalId = String(mailboxMouldSessionMemory.journalId || '').trim();
|
||||
const hasRemembered = rememberedJournalId && mapped.some(j => String(j.journal_id) === rememberedJournalId);
|
||||
this.tplFilters.journalId = hasRemembered
|
||||
? rememberedJournalId
|
||||
: String(mapped[0].journal_id);
|
||||
}
|
||||
this.tplFilters.scene = String(mailboxMouldSessionMemory.scene || '');
|
||||
this.tplFilters.language = String(mailboxMouldSessionMemory.language || '');
|
||||
this.syncTplFilterMemory();
|
||||
if (this.activeTab === 'styles') {
|
||||
this.fetchStyles();
|
||||
} else {
|
||||
@@ -226,6 +239,11 @@ export default {
|
||||
},
|
||||
|
||||
// ========== Templates ==========
|
||||
syncTplFilterMemory() {
|
||||
mailboxMouldSessionMemory.journalId = String((this.tplFilters && this.tplFilters.journalId) || '');
|
||||
mailboxMouldSessionMemory.scene = String((this.tplFilters && this.tplFilters.scene) || '');
|
||||
mailboxMouldSessionMemory.language = String((this.tplFilters && this.tplFilters.language) || '');
|
||||
},
|
||||
fetchTemplates() {
|
||||
this.tplLoading = true;
|
||||
const params = {
|
||||
@@ -233,6 +251,7 @@ export default {
|
||||
scene: this.tplFilters.scene || '',
|
||||
language: this.tplFilters.language || ''
|
||||
};
|
||||
this.syncTplFilterMemory();
|
||||
this.$api
|
||||
.post(API.listTemplates, params)
|
||||
.then(res => {
|
||||
@@ -257,11 +276,13 @@ export default {
|
||||
});
|
||||
},
|
||||
handleCreateTemplate() {
|
||||
this.syncTplFilterMemory();
|
||||
// 传入当前模板列表选中的期刊,详情页用于默认回填
|
||||
const journalId = this.tplFilters && this.tplFilters.journalId ? String(this.tplFilters.journalId) : '';
|
||||
this.$router.push({ path: '/mailboxMouldDetail', query: journalId ? { journal_id: journalId } : {} });
|
||||
},
|
||||
handleEditTemplate(row) {
|
||||
this.syncTplFilterMemory();
|
||||
const templateId = row && (row.template_id || row.id);
|
||||
const journalId = this.tplFilters && this.tplFilters.journalId ? String(this.tplFilters.journalId) : '';
|
||||
const query = templateId ? { template_id: String(templateId) } : {};
|
||||
@@ -274,6 +295,7 @@ export default {
|
||||
this.previewVisible = true;
|
||||
},
|
||||
handleDeleteTemplate(row) {
|
||||
this.syncTplFilterMemory();
|
||||
const templateId = row && (row.template_id || row.id);
|
||||
if (!templateId) return;
|
||||
this.$confirm(this.$t('mailboxMould.deleteConfirm'), this.$t('mailboxMould.colActions'), {
|
||||
|
||||
@@ -424,6 +424,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bus from '../common/bus'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -435,8 +436,7 @@ export default {
|
||||
baseUrl: this.Common.baseUrl,
|
||||
mediaUrl: this.Common.mediaUrl,
|
||||
query: {
|
||||
// journal_id: '',
|
||||
// year: 0,
|
||||
journal_id: 0,
|
||||
keywords: '',
|
||||
page: 1,
|
||||
limit: 15
|
||||
@@ -472,6 +472,23 @@ export default {
|
||||
this.initMajor();
|
||||
},
|
||||
methods: {
|
||||
readPromotionFactoryJumpJournalId() {
|
||||
try {
|
||||
const raw = sessionStorage.getItem('promotionFactoryJump');
|
||||
if (!raw) return '';
|
||||
const obj = JSON.parse(raw || '{}');
|
||||
if (!obj || obj.from !== 'promotionFactory') return '';
|
||||
if (obj.targetPath && String(obj.targetPath) !== String(this.$route.path)) return '';
|
||||
const id = obj.journal_id != null ? String(obj.journal_id) : '';
|
||||
sessionStorage.removeItem('promotionFactoryJump');
|
||||
return id;
|
||||
} catch (e) {
|
||||
try {
|
||||
sessionStorage.removeItem('promotionFactoryJump');
|
||||
} catch (e2) {}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
goDetail(row) {
|
||||
console.log('row at line 460:', row);
|
||||
this.$router.push({
|
||||
@@ -518,8 +535,31 @@ export default {
|
||||
.then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.jourList = res.data.journals;
|
||||
this.query.journal_id = this.jourList[0].journal_id;
|
||||
const fromQueryId = this.$route && this.$route.query ? this.$route.query.journal_id : '';
|
||||
const fromSessionId = this.readPromotionFactoryJumpJournalId();
|
||||
const pickedId = fromQueryId || fromSessionId;
|
||||
const matched = pickedId ? this.jourList.find((j) => String(j.journal_id) === String(pickedId)) : null;
|
||||
this.query.journal_id = matched ? matched.journal_id : this.jourList[0].journal_id;
|
||||
this.getData();
|
||||
|
||||
// 仅首次接收参数:应用后立刻清掉 URL,避免刷新仍然固定某一本期刊
|
||||
if (fromQueryId) {
|
||||
const oldFullPath = this.$route && this.$route.fullPath ? this.$route.fullPath : '';
|
||||
const nextQuery = Object.assign({}, (this.$route && this.$route.query) || {});
|
||||
delete nextQuery.journal_id;
|
||||
delete nextQuery.journalId;
|
||||
delete nextQuery._ap_from;
|
||||
if (oldFullPath) {
|
||||
bus.$emit('close_tag_by_path', { path: oldFullPath, silent: true });
|
||||
}
|
||||
this.$router.replace({ query: nextQuery }).catch(() => {});
|
||||
// 兜底:某些场景 router.replace 不更新地址栏,这里强制清掉 query
|
||||
try {
|
||||
const u = new URL(window.location.href);
|
||||
['_ap_from', 'journal_id', 'journalId', 'journalIdd'].forEach((k) => u.searchParams.delete(k));
|
||||
window.history.replaceState({}, '', u.toString());
|
||||
} catch (e) {}
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
|
||||
@@ -478,6 +478,7 @@ const currentYear = new Date().getFullYear();
|
||||
var ReviewTime = `(${currentYear - 2}–${currentYear})`;
|
||||
import commonReviewer from '../page/components/reviewerList/add.vue';
|
||||
import commonMajorTableList from '../page/components/major/tableList.vue';
|
||||
import bus from '../common/bus'
|
||||
export default {
|
||||
components: {
|
||||
commonReviewer,
|
||||
@@ -657,11 +658,27 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.getDate();
|
||||
this.getContent();
|
||||
this.loadFields();
|
||||
// this.initMajor()
|
||||
},
|
||||
methods: {
|
||||
readPromotionFactoryJumpJournalId() {
|
||||
try {
|
||||
const raw = sessionStorage.getItem('promotionFactoryJump');
|
||||
if (!raw) return '';
|
||||
const obj = JSON.parse(raw || '{}');
|
||||
if (!obj || obj.from !== 'promotionFactory') return '';
|
||||
if (obj.targetPath && String(obj.targetPath) !== String(this.$route.path)) return '';
|
||||
const id = obj.journal_id != null ? String(obj.journal_id) : '';
|
||||
sessionStorage.removeItem('promotionFactoryJump');
|
||||
return id;
|
||||
} catch (e) {
|
||||
try {
|
||||
sessionStorage.removeItem('promotionFactoryJump');
|
||||
} catch (e2) {}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
getProps() {
|
||||
return {
|
||||
value: 'value',
|
||||
@@ -716,6 +733,32 @@ export default {
|
||||
.then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.df_jour = res.data.journals;
|
||||
const fromQueryId = (this.$route && this.$route.query && (this.$route.query.journalId || this.$route.query.journal_id)) || '';
|
||||
const fromSessionId = this.readPromotionFactoryJumpJournalId();
|
||||
const pickedId = fromQueryId || fromSessionId;
|
||||
if (pickedId) {
|
||||
this.query.journalId = Number(pickedId) || 0;
|
||||
}
|
||||
this.getContent();
|
||||
|
||||
// 仅首次接收参数:应用后立刻清掉 URL,避免刷新仍然固定某一本期刊
|
||||
if (fromQueryId) {
|
||||
const oldFullPath = this.$route && this.$route.fullPath ? this.$route.fullPath : '';
|
||||
const nextQuery = Object.assign({}, (this.$route && this.$route.query) || {});
|
||||
delete nextQuery.journal_id;
|
||||
delete nextQuery.journalId;
|
||||
delete nextQuery._ap_from;
|
||||
if (oldFullPath) {
|
||||
bus.$emit('close_tag_by_path', { path: oldFullPath, silent: true });
|
||||
}
|
||||
this.$router.replace({ query: nextQuery }).catch(() => {});
|
||||
// 兜底:某些场景 router.replace 不更新地址栏,这里强制清掉 query
|
||||
try {
|
||||
const u = new URL(window.location.href);
|
||||
['_ap_from', 'journal_id', 'journalId', 'journalIdd'].forEach((k) => u.searchParams.delete(k));
|
||||
window.history.replaceState({}, '', u.toString());
|
||||
} catch (e) {}
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
|
||||
@@ -187,6 +187,7 @@
|
||||
|
||||
<script>
|
||||
import commonCv from '../common/cv.vue';
|
||||
import bus from '../common/bus';
|
||||
export default {
|
||||
components: {
|
||||
commonCv
|
||||
@@ -362,6 +363,7 @@ export default {
|
||||
for (var i = 0; i < this.tableData.length; i++) {
|
||||
this.getScoreData(i, this.tableData[i].score);
|
||||
}
|
||||
bus.$emit('apply-badge-refresh');
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import bus from '../common/bus'
|
||||
export default {
|
||||
name: 'youthList',
|
||||
data() {
|
||||
@@ -326,6 +327,21 @@
|
||||
this.yearData()
|
||||
},
|
||||
methods: {
|
||||
readPromotionFactoryJumpJournalId() {
|
||||
try {
|
||||
const raw = sessionStorage.getItem('promotionFactoryJump')
|
||||
if (!raw) return ''
|
||||
const obj = JSON.parse(raw || '{}')
|
||||
if (!obj || obj.from !== 'promotionFactory') return ''
|
||||
if (obj.targetPath && String(obj.targetPath) !== String(this.$route.path)) return ''
|
||||
const id = obj.journal_id != null ? String(obj.journal_id) : ''
|
||||
sessionStorage.removeItem('promotionFactoryJump')
|
||||
return id
|
||||
} catch (e) {
|
||||
try { sessionStorage.removeItem('promotionFactoryJump') } catch (e2) {}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
// 获取当年时间
|
||||
yearData() {
|
||||
this.YearThis = new Date().getFullYear()
|
||||
@@ -347,8 +363,33 @@
|
||||
.then(res => {
|
||||
if (res.code == 0) {
|
||||
this.df_jour = res.data.journals;
|
||||
this.query.journal_id = this.df_jour[0].journal_id
|
||||
const fromQueryId =
|
||||
(this.$route && this.$route.query && (this.$route.query.journal_id || this.$route.query.journalId)) || ''
|
||||
const fromSessionId = this.readPromotionFactoryJumpJournalId()
|
||||
const pickedId = fromQueryId || fromSessionId
|
||||
const matched = pickedId ? this.df_jour.find(j => String(j.journal_id) === String(pickedId)) : null
|
||||
this.query.journal_id = matched ? matched.journal_id : this.df_jour[0].journal_id
|
||||
this.getDate();
|
||||
|
||||
// 仅首次接收参数:应用后立刻清掉 URL,避免刷新仍然固定某一本期刊
|
||||
if (fromQueryId) {
|
||||
const oldFullPath = this.$route && this.$route.fullPath ? this.$route.fullPath : ''
|
||||
const nextQuery = Object.assign({}, (this.$route && this.$route.query) || {})
|
||||
delete nextQuery.journal_id
|
||||
delete nextQuery.journalId
|
||||
delete nextQuery.journalIdd
|
||||
delete nextQuery._ap_from
|
||||
if (oldFullPath) {
|
||||
bus.$emit('close_tag_by_path', { path: oldFullPath, silent: true })
|
||||
}
|
||||
this.$router.replace({ path: this.$route.path, query: nextQuery }).catch(() => {})
|
||||
// 兜底:某些场景 router.replace 不更新地址栏,这里强制清掉 query
|
||||
try {
|
||||
const u = new URL(window.location.href)
|
||||
;['_ap_from', 'journal_id', 'journalId', 'journalIdd'].forEach(k => u.searchParams.delete(k))
|
||||
window.history.replaceState({}, '', u.toString())
|
||||
} catch (e) {}
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ const i18n = new VueI18n({
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const currentRoute = to; // 获取当前路由路径,例如 "/home"
|
||||
|
||||
if (currentRoute.meta.hideJournal) {
|
||||
if (currentRoute.meta.hideJournal || currentRoute.meta.public) {
|
||||
|
||||
} else {
|
||||
try {
|
||||
@@ -239,11 +239,11 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
|
||||
// 无论接口成功/失败,都执行原有跳转逻辑
|
||||
document.title = `${to.meta.title} | Traditional Medicine Research`;
|
||||
document.title = `${to.meta.title || 'TMR'} | Traditional Medicine Research`;
|
||||
const role = localStorage.getItem('U_name');
|
||||
const userrole = localStorage.getItem('U_status');
|
||||
|
||||
if (!role && to.path != '/register' && to.path !== '/submission' && to.path !== '/verification' && to.path !== '/orcidLink' && to.path !== '/img' && to.path !== '/reviewer' && to.path !== '/thanks' && to.path !== '/login' && to.path !== '/refuse' && to.path !== '/managing' && to.path.search(/retrieve/i) < 0) {
|
||||
if (!role && to.meta.public !== true && to.path != '/register' && to.path !== '/submission' && to.path !== '/verification' && to.path !== '/orcidLink' && to.path !== '/img' && to.path !== '/reviewer' && to.path !== '/thanks' && to.path !== '/login' && to.path !== '/refuse' && to.path !== '/managing' && to.path.search(/retrieve/i) < 0) {
|
||||
next('/login');
|
||||
} else {
|
||||
if (navigator.userAgent.indexOf('MSIE') > -1 && to.path === '/editor') {
|
||||
|
||||
@@ -1477,6 +1477,24 @@ export default new Router({
|
||||
title: 'img'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/youthBoardRegister',
|
||||
component: () => import( /* webpackChunkName: "youthBoardRegister" */ '../components/page/YouthEditorialBoardRegistration.vue'),
|
||||
meta: {
|
||||
title: 'Youth Editorial Board Registration',
|
||||
public: true,
|
||||
hideJournal: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/youthBoardSubmitSuccess',
|
||||
component: () => import( /* webpackChunkName: "youthBoardSubmitSuccess" */ '../components/page/YouthBoardSubmitSuccess.vue'),
|
||||
meta: {
|
||||
title: 'Submission Success',
|
||||
public: true,
|
||||
hideJournal: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
redirect: '/404'
|
||||
|
||||
26
src/utils/emailHtmlView.js
Normal file
26
src/utils/emailHtmlView.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 将完整 HTML 邮件转为适合 div[v-html] 的片段:
|
||||
* 取 body.innerHTML,并前置 head 内 <style>,避免版式与改版前不一致。
|
||||
*/
|
||||
export function normalizeEmailHtmlForInlineDisplay(html) {
|
||||
if (!html || typeof html !== 'string') return '';
|
||||
const t = html.trim();
|
||||
if (!/^<!DOCTYPE|^<\s*html[\s>]/i.test(t)) return html;
|
||||
try {
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
const body = doc && doc.body;
|
||||
if (!body) return html;
|
||||
const inner = body.innerHTML;
|
||||
if (!inner || !inner.trim()) return html;
|
||||
let headInject = '';
|
||||
const head = doc.head;
|
||||
if (head) {
|
||||
head.querySelectorAll('style').forEach((node) => {
|
||||
headInject += node.outerHTML;
|
||||
});
|
||||
}
|
||||
return headInject + inner;
|
||||
} catch (e) {
|
||||
return html;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user