tijiao
This commit is contained in:
@@ -874,6 +874,30 @@ const en = {
|
||||
taskStoppedMsg: 'Crawl has been paused.',
|
||||
runOnceQueued: 'A one-off crawl has been queued.',
|
||||
},
|
||||
articleDetailEmail: {
|
||||
pageTitle: 'Manuscript email detail',
|
||||
sender: 'Sender :',
|
||||
content: 'Content :',
|
||||
attachment: 'Attachment :',
|
||||
uploadTip: 'Only word file can be uploaded(.docx)',
|
||||
clickUpload: 'Click Upload',
|
||||
sendMail: 'Send mail',
|
||||
templateSelection: 'Template selection',
|
||||
letterLang: 'Email language',
|
||||
langZh: '中文',
|
||||
langEn: 'English',
|
||||
greeting: 'Dear Dr. {name},',
|
||||
signOff: 'Yours Sincerely',
|
||||
officeLine: '{journal} | Editorial Office | New Zealand',
|
||||
telephone: 'Telephone: +64 02108293806',
|
||||
emailLine: 'Email: {email}',
|
||||
subscribeLine: 'Subscribe to receive Latest Research and News from {journal}',
|
||||
contentRequired: 'Please enter the message content!',
|
||||
sendSuccess: 'Sent successfully!',
|
||||
uploadError: 'upload error',
|
||||
serviceError: 'service error: {msg}',
|
||||
uploadLimit: 'The maximum number of uploaded files has been exceeded!'
|
||||
},
|
||||
mailboxSend: {
|
||||
title: 'Write mail',
|
||||
to: 'To:',
|
||||
@@ -1502,7 +1526,55 @@ const en = {
|
||||
previewWithVariablesHint: 'The expert data is an example, used for variable spelling check only.',
|
||||
close: 'Close',
|
||||
placeholder: 'Please enter email content'
|
||||
}
|
||||
},
|
||||
refRelevance: {
|
||||
filterAll: 'All ({count})',
|
||||
filterModify: 'Needs revision ({count})',
|
||||
columnTitle: 'AI citation relevance review',
|
||||
uncitedTag: 'Not cited in text',
|
||||
uncitedDesc: 'This reference appears in the list but no matching in-text citation was found.',
|
||||
uncitedTip: 'Remove it from the reference list or add an in-text citation in the manuscript.',
|
||||
citationN: 'Cite {n}',
|
||||
relevancePct: 'Relevance {score}%',
|
||||
relevancePctShort: '{score}%',
|
||||
viewAiAnalysis: 'View AI analysis →',
|
||||
viewAiAnalysisShort: 'AI →',
|
||||
dialogTitle: 'Citation relevance details',
|
||||
prevRef: 'Previous reference',
|
||||
nextRef: 'Next reference',
|
||||
manuscript: 'Manuscript',
|
||||
excerpt: '(excerpt)',
|
||||
citeListTitle: 'This reference · all in-text citations',
|
||||
citeListTotal: '{n} citation(s)',
|
||||
current: 'Current',
|
||||
close: 'Close',
|
||||
emptyModify: 'No references need revision',
|
||||
locatorMark: 'Highlighted in-text marker {mark}',
|
||||
locatorMarkRef: 'Highlighted marker {mark} (reference [{ref}])',
|
||||
locatorMarkOnly: 'Highlighted citation marker {mark}',
|
||||
summaryModify: '{total} cite(s) · {modify} revise',
|
||||
summaryOk: '{total} cite(s) · OK',
|
||||
insightTitle: 'Context review',
|
||||
insightTitleRef: 'Context review [{ref}]',
|
||||
recommendAction: 'Editorial note',
|
||||
recommendRevise: 'Suggested revision',
|
||||
recommendKeep: 'Keep as is',
|
||||
recommendTextRevise: '{brief} Review the passage at left before revising or removing this citation.',
|
||||
recommendTextReviseDefault: 'Review the passage at left before revising or removing this citation.',
|
||||
recommendTextKeep: 'This citation strongly supports the argument; no change recommended.',
|
||||
revisionLabel: 'Revision',
|
||||
citeTag: 'Citation {idx}',
|
||||
citeTagTotal: 'Citation {idx}/{total}',
|
||||
briefSuggestRevise: 'Revise',
|
||||
briefOk: 'OK',
|
||||
briefAppropriate: 'OK',
|
||||
briefKeep: 'Keep',
|
||||
briefTrim: 'Revise',
|
||||
briefWeakEvidence: 'Peripheral',
|
||||
briefNeedDiff: 'Clarify',
|
||||
briefDelete: 'Drop',
|
||||
briefYearMismatch: 'Year?'
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -619,6 +619,30 @@ const zh = {
|
||||
saveFail: '保存失败',
|
||||
saveSuccessMock: '模板已保存(模拟)',
|
||||
},
|
||||
articleDetailEmail: {
|
||||
pageTitle: '稿件邮件详情',
|
||||
sender: '发件人:',
|
||||
content: '正文:',
|
||||
attachment: '附件:',
|
||||
uploadTip: '仅支持上传 Word 文件(.docx)',
|
||||
clickUpload: '点击上传',
|
||||
sendMail: '发送邮件',
|
||||
templateSelection: '模板选择',
|
||||
letterLang: '邮件语言',
|
||||
langZh: '中文',
|
||||
langEn: 'English',
|
||||
greeting: '尊敬的 Dr. {name}:',
|
||||
signOff: '此致敬礼',
|
||||
officeLine: '{journal} | 编辑部 | 新西兰',
|
||||
telephone: '电话:+64 02108293806',
|
||||
emailLine: '邮箱:{email}',
|
||||
subscribeLine: '订阅 {journal} 最新研究与资讯',
|
||||
contentRequired: '请输入邮件正文内容!',
|
||||
sendSuccess: '发送成功!',
|
||||
uploadError: '上传失败',
|
||||
serviceError: '服务错误:{msg}',
|
||||
uploadLimit: '已超过可上传文件数量上限!'
|
||||
},
|
||||
mailboxCollect: {
|
||||
inboxTab: '收件箱',
|
||||
outboxTab: '发件箱',
|
||||
@@ -1483,7 +1507,55 @@ const zh = {
|
||||
previewWithVariablesHint: '专家数据仅为示例,仅用于变量拼写检查。',
|
||||
close: '关闭',
|
||||
placeholder: '请输入邮件内容'
|
||||
}
|
||||
},
|
||||
refRelevance: {
|
||||
filterAll: '全部({count})',
|
||||
filterModify: '需修改({count})',
|
||||
columnTitle: 'AI 智能评定相关性建议',
|
||||
uncitedTag: '正文未引用',
|
||||
uncitedDesc: '该文献已列入参考文献列表,但正文中未找到对应的引用上标。',
|
||||
uncitedTip: '建议:从参考文献列表中删除,或在正文中补充引用。',
|
||||
citationN: '第{n}处',
|
||||
relevancePct: '相关性 {score}%',
|
||||
relevancePctShort: '{score}%',
|
||||
viewAiAnalysis: '查看 AI 分析 →',
|
||||
viewAiAnalysisShort: 'AI分析→',
|
||||
dialogTitle: '相关性检测详情',
|
||||
prevRef: '上一篇参考文献',
|
||||
nextRef: '下一篇参考文献',
|
||||
manuscript: '稿件',
|
||||
excerpt: '(摘录)',
|
||||
citeListTitle: '本参考文献 · 全部引用处',
|
||||
citeListTotal: '共 {n} 处',
|
||||
current: '当前',
|
||||
close: '关闭',
|
||||
emptyModify: '暂无需要修改的参考文献',
|
||||
locatorMark: '高亮段落中的引用标记 {mark}',
|
||||
locatorMarkRef: '高亮标记 {mark}(对应参考文献 [{ref}])',
|
||||
locatorMarkOnly: '高亮引用标记 {mark}',
|
||||
summaryModify: '{total}处引用·{modify}处待改',
|
||||
summaryOk: '{total}处引用·均可保留',
|
||||
insightTitle: '语境深度批核',
|
||||
insightTitleRef: '语境深度批核 「[{ref}]」',
|
||||
recommendAction: '操作建议',
|
||||
recommendRevise: '建议修订引用',
|
||||
recommendKeep: '建议完美保留',
|
||||
recommendTextRevise: '{brief}。请根据左侧正文核对后决定是否删改或改写该处引用。',
|
||||
recommendTextReviseDefault: '请根据左侧正文核对后决定是否删改或改写该处引用。',
|
||||
recommendTextKeep: '与段落论述高度一致,有力支撑核心论点,建议不作修改保留该引用。',
|
||||
revisionLabel: '修订建议',
|
||||
citeTag: '引用 {idx}',
|
||||
citeTagTotal: '引用 {idx}/{total}',
|
||||
briefSuggestRevise: '待改',
|
||||
briefOk: '恰当',
|
||||
briefAppropriate: '恰当',
|
||||
briefKeep: '保留',
|
||||
briefTrim: '宜改写',
|
||||
briefWeakEvidence: '非主依据',
|
||||
briefNeedDiff: '需说明差异',
|
||||
briefDelete: '删除',
|
||||
briefYearMismatch: '年份不符'
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -999,6 +999,10 @@
|
||||
</span>
|
||||
<span class="plagiarism-sim-date">{{ formatPlagiarismDate(row) }}</span>
|
||||
<span class="plagiarism-sim-state" :class="getPlagiarismStateClass(row)">
|
||||
<i
|
||||
v-if="isPlagiarismStateLoading(row)"
|
||||
class="el-icon-loading plagiarism-state-loading-icon"
|
||||
></i>
|
||||
{{ formatPlagiarismStateLabel(row) }}
|
||||
</span>
|
||||
<span class="plagiarism-sim-report">
|
||||
@@ -1012,7 +1016,11 @@
|
||||
{{ $t('articleListEditor.plagiarismPreviewPdf') }}
|
||||
<i class="el-icon-link"></i>
|
||||
</a>
|
||||
<span v-else class="plagiarism-check-no-pdf">{{ $t('articleListEditor.plagiarismNoPdfLink') }}</span>
|
||||
<span
|
||||
v-else-if="!isPlagiarismStateLoading(row)"
|
||||
class="plagiarism-check-no-pdf"
|
||||
>{{ $t('articleListEditor.plagiarismNoPdfLink') }}</span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2973,6 +2981,15 @@ export default {
|
||||
closeResubmit() {
|
||||
(this.resubmitVisible = false), this.$refs['resubmitJournal'].resetFields();
|
||||
},
|
||||
/** 查重列表静默轮询间隔(毫秒) */
|
||||
getPlagiarismPollIntervalMs() {
|
||||
return 60 * 1000;
|
||||
},
|
||||
/** 无「进行中」记录时视为已结束,停止轮询(state 1=进行中,2/3=完成,4/5=失败) */
|
||||
shouldStopPlagiarismPolling(list) {
|
||||
if (!Array.isArray(list) || !list.length) return false;
|
||||
return !list.some((row) => Number(row && row.state) === 1);
|
||||
},
|
||||
startPlagiarismPolling(resetList) {
|
||||
this.stopPlagiarismPolling();
|
||||
if (resetList !== false) {
|
||||
@@ -2982,7 +2999,7 @@ export default {
|
||||
this.fetchPlagiarismList(false);
|
||||
this.plagiarismPollTimer = setInterval(() => {
|
||||
this.fetchPlagiarismList(false);
|
||||
}, 3 * 60 * 1000);
|
||||
}, this.getPlagiarismPollIntervalMs());
|
||||
},
|
||||
stopPlagiarismPolling() {
|
||||
if (this.plagiarismPollTimer) {
|
||||
@@ -2998,10 +3015,11 @@ export default {
|
||||
}
|
||||
this.plagiarismSubmitLoading = true;
|
||||
try {
|
||||
const res = await this.$api.post('api/Plagiarism/submit', { article_id: articleId });
|
||||
const res = await this.$api.post('api/Plagiarism/submit', { article_id: articleId ,type:'body_only'});
|
||||
if (res && Number(res.code) === 0) {
|
||||
this.$message.success((res && res.msg) || this.$t('articleListEditor.plagiarismChecking'));
|
||||
await this.fetchPlagiarismList(true);
|
||||
this.startPlagiarismPolling(false);
|
||||
} else {
|
||||
this.$message.error((res && res.msg) || this.$t('articleListEditor.plagiarismCheckFailed'));
|
||||
}
|
||||
@@ -3026,6 +3044,9 @@ export default {
|
||||
const payload = res.data || {};
|
||||
const list = Array.isArray(payload.list) ? payload.list : Array.isArray(payload) ? payload : [];
|
||||
this.plagiarismList = list;
|
||||
if (!manual && this.shouldStopPlagiarismPolling(list)) {
|
||||
this.stopPlagiarismPolling();
|
||||
}
|
||||
} else if (manual) {
|
||||
this.$message.error((res && res.msg) || this.$t('articleListEditor.plagiarismStatusFailed'));
|
||||
}
|
||||
@@ -3058,6 +3079,13 @@ export default {
|
||||
if (s === 4 || s === 5) return 'state-fail';
|
||||
return '';
|
||||
},
|
||||
/** 上传中 / 比对中等进行中状态显示加载图标 */
|
||||
isPlagiarismStateLoading(row) {
|
||||
if (!row || typeof row !== 'object') return false;
|
||||
if (Number(row.state) === 1) return true;
|
||||
const label = String(row.state_label || row.stateLabel || '').trim();
|
||||
return /上传中|比对中/i.test(label);
|
||||
},
|
||||
getPlagiarismSimilarityScore(row) {
|
||||
if (!row || typeof row !== 'object') return null;
|
||||
const raw = row.similarity_score != null ? row.similarity_score : row.similarity;
|
||||
@@ -3338,13 +3366,24 @@ export default {
|
||||
min-width: 48px;
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.plagiarism-state-loading-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.plagiarism-sim-state.state-uploading {
|
||||
color: #e6a23c;
|
||||
}
|
||||
.plagiarism-sim-state.state-uploading .plagiarism-state-loading-icon {
|
||||
color: #e6a23c;
|
||||
}
|
||||
.plagiarism-sim-state.state-done {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item>
|
||||
<i class="el-icon-lx-calendar"></i> Manuscript email detail
|
||||
<i class="el-icon-lx-calendar"></i> {{ $t('articleDetailEmail.pageTitle') }}
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
@@ -11,39 +11,46 @@
|
||||
<el-row :gutter="30">
|
||||
<el-col :span="16">
|
||||
<el-form :model="EmailData" label-width="110px" class="Email_Data">
|
||||
<el-form-item label="Sender :">
|
||||
<el-form-item :label="$t('articleDetailEmail.sender')">
|
||||
<el-input v-model="AuthorMes.email"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Content :">
|
||||
<el-form-item :label="$t('articleDetailEmail.content')">
|
||||
<div class="email-letter-lang">
|
||||
<span class="email-letter-lang-label">{{ $t('articleDetailEmail.letterLang') }}</span>
|
||||
<el-radio-group v-model="emailLetterLang" size="small" @change="applyEmailLetterBlocks">
|
||||
<el-radio-button label="zh">{{ $t('articleDetailEmail.langZh') }}</el-radio-button>
|
||||
<el-radio-button label="en">{{ $t('articleDetailEmail.langEn') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<p v-html="EmailData.topmail"></p>
|
||||
<p v-html="EmailData.articleInfor"></p>
|
||||
<el-input type="textarea" rows="9" v-model="EmailData.substance" @input="btn_ft=false">
|
||||
</el-input>
|
||||
<p v-html="EmailData.bottomail"></p>
|
||||
</el-form-item>
|
||||
<el-form-item label="Attachment :">
|
||||
<el-form-item :label="$t('articleDetailEmail.attachment')">
|
||||
<el-upload style="display: inline-block;" class="upload-demo" :action="upload_enclosure"
|
||||
accept=".docx," name="enclosure" :before-upload="beforeupload_enclosure"
|
||||
:on-error="uperr_enclosure" :on-success="upSuccess_enclosure" :limit="1"
|
||||
:on-exceed="alertlimit" :on-remove="removefilenclosure">
|
||||
<div class="el-upload__text" style="padding:0 5px;">
|
||||
<em>Click Upload</em>
|
||||
<em>{{ $t('articleDetailEmail.clickUpload') }}</em>
|
||||
</div>
|
||||
<div class="el-upload__tip" slot="tip">Only word file can be uploaded(.docx)</div>
|
||||
<div class="el-upload__tip" slot="tip">{{ $t('articleDetailEmail.uploadTip') }}</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="sendData" :disabled="btn_ft"
|
||||
style="float: right;margin-top: -20px;">Send mail
|
||||
style="float: right;margin-top: -20px;">{{ $t('articleDetailEmail.sendMail') }}
|
||||
</el-button>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="muban_list">
|
||||
<p>Template selection</p>
|
||||
<p>{{ $t('articleDetailEmail.templateSelection') }}</p>
|
||||
<el-collapse v-model="activeNames" default-expand-all>
|
||||
<el-collapse-item v-for="(itemC, indexC) in mubanList" :title="itemC.etitle"
|
||||
:name="itemC.eid">
|
||||
<div class="sel_muban" v-for="(item, index) in itemC.children">
|
||||
:name="itemC.eid" :key="itemC.eid || indexC">
|
||||
<div class="sel_muban" v-for="(item, index) in itemC.children" :key="index">
|
||||
<i class="header-icon el-icon-circle-plus" @click="add_muban(item)"></i>
|
||||
{{item.econtent}}
|
||||
<span style="color: #999;margin-left: 10px;">({{item.num}})</span>
|
||||
@@ -62,11 +69,20 @@
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const savedLang = localStorage.getItem('langs');
|
||||
return {
|
||||
loading: false,
|
||||
baseUrl: this.Common.baseUrl,
|
||||
articleId: this.$route.query.id,
|
||||
AuthorMes: {},
|
||||
emailLetterLang: savedLang === 'zh' ? 'zh' : 'en',
|
||||
authorDisplayName: '',
|
||||
journalMeta: {
|
||||
title: '',
|
||||
email: '',
|
||||
website: '',
|
||||
issn: ''
|
||||
},
|
||||
EmailData: {
|
||||
email: '',
|
||||
attachment: '',
|
||||
@@ -83,12 +99,52 @@
|
||||
created: function() {
|
||||
this.getData();
|
||||
},
|
||||
watch: {
|
||||
'$i18n.locale'(val) {
|
||||
if (val === 'zh' || val === 'en') {
|
||||
this.emailLetterLang = val;
|
||||
this.applyEmailLetterBlocks();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
upload_enclosure: function() {
|
||||
return this.baseUrl + 'api/Email/up_enclosure_file';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
letterT(key, params) {
|
||||
const loc = this.emailLetterLang === 'zh' ? 'zh' : 'en';
|
||||
return params ? this.$t('articleDetailEmail.' + key, loc, params) : this.$t('articleDetailEmail.' + key, loc);
|
||||
},
|
||||
applyEmailLetterBlocks() {
|
||||
const name = this.authorDisplayName || '';
|
||||
this.EmailData.topmail = this.letterT('greeting', { name }) + '<br/>';
|
||||
const j = this.journalMeta;
|
||||
if (!j.title) return;
|
||||
const subscribeUrl = 'https://www.tmrjournals.com/draw_up.html?issn=' + (j.issn || '');
|
||||
this.EmailData.bottomail =
|
||||
'<br/>' +
|
||||
this.letterT('signOff') +
|
||||
'<br/>' +
|
||||
this.letterT('officeLine', { journal: j.title }) +
|
||||
'<br/>' +
|
||||
this.letterT('telephone') +
|
||||
'<br/>' +
|
||||
this.letterT('emailLine', { email: j.email || '' }) +
|
||||
'<br/>' +
|
||||
(j.website || '') +
|
||||
'<br/>' +
|
||||
this.letterT('subscribeLine', { journal: j.title }) +
|
||||
'<br/>' +
|
||||
subscribeUrl;
|
||||
},
|
||||
setAuthorFromResponse(user) {
|
||||
if (!user) return;
|
||||
this.AuthorMes = user;
|
||||
this.authorDisplayName = user.realname || user.name || '';
|
||||
this.applyEmailLetterBlocks();
|
||||
},
|
||||
//获取数据
|
||||
getData() {
|
||||
this.$api
|
||||
@@ -96,9 +152,6 @@
|
||||
.then(res => {
|
||||
if (res.code == 0) {
|
||||
this.mubanList = res.data.templates;
|
||||
// for (let i in this.mubanList) {
|
||||
// this.activeNames.push(this.mubanList[i].eid)
|
||||
// }
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
@@ -117,15 +170,13 @@ if(this.$route.query.user_id){
|
||||
.then(res => {
|
||||
if(this.$route.query.user_id){
|
||||
if (res.status == 1) {
|
||||
this.AuthorMes = res.data.user;
|
||||
this.EmailData.topmail = 'Dear Dr. ' + this.AuthorMes.realname + ',<br/>';
|
||||
this.setAuthorFromResponse(res.data.user);
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
}else{
|
||||
if (res.code == 0) {
|
||||
this.AuthorMes = res.data.userDetail;
|
||||
this.EmailData.topmail = 'Dear Dr. ' + this.AuthorMes.realname + ',<br/>';
|
||||
this.setAuthorFromResponse(res.data.userDetail);
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
@@ -156,13 +207,13 @@ if(this.$route.query.user_id){
|
||||
.then(res => {
|
||||
if (res.code == 0) {
|
||||
let arry = res.data.journal;
|
||||
this.EmailData.bottomail = '<br/>Yours Sincerely' +
|
||||
'<br/>' + arry.title + ' | Editorial Office | New Zealand' +
|
||||
'<br/>Telephone: +64 02108293806' +
|
||||
'<br/>Email: ' + arry.email +
|
||||
'<br/>' + arry.website +
|
||||
'<br/>Subscribe to receive Latest Research and News from ' + arry.title +
|
||||
'<br/>https://www.tmrjournals.com/draw_up.html?issn=' + arry.issn
|
||||
this.journalMeta = {
|
||||
title: arry.title || '',
|
||||
email: arry.email || '',
|
||||
website: arry.website || '',
|
||||
issn: arry.issn || ''
|
||||
};
|
||||
this.applyEmailLetterBlocks();
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
@@ -176,7 +227,7 @@ if(this.$route.query.user_id){
|
||||
// 发送邮件
|
||||
sendData() {
|
||||
if (this.EmailData.substance == '') {
|
||||
this.$message.error('Please enter the message content!');
|
||||
this.$message.error(this.$t('articleDetailEmail.contentRequired'));
|
||||
return
|
||||
}
|
||||
this.loading = true;
|
||||
@@ -191,7 +242,7 @@ if(this.$route.query.user_id){
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.btn_ft = true
|
||||
this.$message.success('Sent successfully!');
|
||||
this.$message.success(this.$t('articleDetailEmail.sendSuccess'));
|
||||
this.$router.push({
|
||||
path: 'articleDetailEmailist',
|
||||
query: {
|
||||
@@ -229,20 +280,20 @@ if(this.$route.query.user_id){
|
||||
// 附件上传
|
||||
beforeupload_enclosure() {},
|
||||
uperr_enclosure(err) {
|
||||
this.$message.error('upload error');
|
||||
this.$message.error(this.$t('articleDetailEmail.uploadError'));
|
||||
},
|
||||
upSuccess_enclosure(res, file) {
|
||||
if (res.code == 0) {
|
||||
this.EmailData.attachment = 'enclosure/' + res.upurl;
|
||||
} else {
|
||||
this.$message.error('service error:' + res.msg);
|
||||
this.$message.error(this.$t('articleDetailEmail.serviceError', { msg: res.msg }));
|
||||
}
|
||||
},
|
||||
removefilenclosure(file, fileList) {
|
||||
this.EmailData.attachment = '';
|
||||
},
|
||||
alertlimit() {
|
||||
this.$message.error('The maximum number of uploaded files has been exceeded!');
|
||||
this.$message.error(this.$t('articleDetailEmail.uploadLimit'));
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -254,6 +305,20 @@ if(this.$route.query.user_id){
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
|
||||
.email-letter-lang {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.email-letter-lang-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.Email_Data .el-textarea__inner {
|
||||
line-height: 26px;
|
||||
margin: 10px 0 -20px 0;
|
||||
|
||||
@@ -59,22 +59,38 @@
|
||||
mailData.to_list.length
|
||||
}}人
|
||||
</div>
|
||||
<div v-if="mailData.attachments && mailData.attachments.length" class="attachment-brief-bar">
|
||||
<span class="brief-info">
|
||||
{{ $t('mailboxCollect.totalAttachments', { count: mailData.attachments.length }) }}(
|
||||
<i class="el-icon-document" style="color: #409EFF;"></i>
|
||||
<span class="first-file-name">{{ mailData.attachments[0].name }}{{ mailData.attachments.length > 1 ? $t('mailboxCollect.etcSuffix') : '' }}</span>
|
||||
)
|
||||
|
||||
</span>
|
||||
<el-link type="primary" :underline="false" @click="scrollToAttachments" class="jump-link">
|
||||
{{ $t('mailboxCollect.viewAttachments') }}
|
||||
</el-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mail-body-content" v-html="mailBodyHtml"></div>
|
||||
<div
|
||||
v-if="mailData.attachments && mailData.attachments.length"
|
||||
class="attachment-brief-bar"
|
||||
>
|
||||
<span class="brief-info">
|
||||
{{ $t('mailboxCollect.totalAttachments', { count: mailData.attachments.length }) }}(
|
||||
<i class="el-icon-document" style="color: #409EFF;"></i>
|
||||
<span class="first-file-name">{{ mailData.attachments[0].name }}{{ mailData.attachments.length > 1 ? $t('mailboxCollect.etcSuffix') : '' }}</span>
|
||||
)
|
||||
</span>
|
||||
<el-link type="primary" :underline="false" @click="scrollToAttachments" class="jump-link">
|
||||
{{ $t('mailboxCollect.viewAttachments') }}
|
||||
</el-link>
|
||||
</div>
|
||||
|
||||
<div class="mail-body-wrap">
|
||||
<iframe
|
||||
v-if="mailIframeSrcdoc"
|
||||
ref="mailHtmlFrame"
|
||||
class="mail-html-iframe"
|
||||
:srcdoc="mailIframeSrcdoc"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
frameborder="0"
|
||||
scrolling="no"
|
||||
:style="mailIframeStyle"
|
||||
@load="onMailIframeLoad"
|
||||
></iframe>
|
||||
<div v-else class="mail-body-content" v-html="mailBodyHtml"></div>
|
||||
</div>
|
||||
|
||||
<div v-if="mailData.attachments && mailData.attachments.length" class="attachment-section">
|
||||
<div class="attachment-header">
|
||||
@@ -123,7 +139,16 @@
|
||||
|
||||
<script>
|
||||
import Common from '@/components/common/common';
|
||||
import { normalizeEmailHtmlForInlineDisplay } from '@/utils/emailHtmlView';
|
||||
import {
|
||||
normalizeEmailHtmlForInlineDisplay,
|
||||
buildEmailPreviewDocument,
|
||||
isRichHtmlEmail,
|
||||
unescapeHtmlEntities,
|
||||
resizeIframeToContent,
|
||||
measureHtmlContentHeight,
|
||||
sanitizeMailPreviewHeight,
|
||||
MAIL_PREVIEW_HEIGHT_MSG
|
||||
} from '@/utils/emailHtmlView';
|
||||
import JSZip from 'jszip';
|
||||
import { saveAs } from 'file-saver';
|
||||
import FilePreviewDialog from './FilePreviewDialog.vue';
|
||||
@@ -152,19 +177,61 @@ export default {
|
||||
mediaUrl: Common.mediaUrl,
|
||||
isDetailExpanded: false,
|
||||
downloadingIndex: -1,
|
||||
packingAll: false
|
||||
packingAll: false,
|
||||
mailIframeHeight: 120,
|
||||
_iframeResizeObserver: null,
|
||||
_iframeResizeTimers: [],
|
||||
_mailPreviewMessageHandler: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this._mailPreviewMessageHandler = (e) => this.handleMailPreviewMessage(e);
|
||||
window.addEventListener('message', this._mailPreviewMessageHandler);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this._mailPreviewMessageHandler) {
|
||||
window.removeEventListener('message', this._mailPreviewMessageHandler);
|
||||
this._mailPreviewMessageHandler = null;
|
||||
}
|
||||
this.teardownIframeResize();
|
||||
},
|
||||
computed: {
|
||||
totalAttachmentSize() {
|
||||
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);
|
||||
},
|
||||
mailRawHtml() {
|
||||
const m = this.mailData || {};
|
||||
const html =
|
||||
m.content_html ||
|
||||
m.body_html ||
|
||||
m.html ||
|
||||
m.body ||
|
||||
m.content ||
|
||||
'';
|
||||
if (html && String(html).trim()) return html;
|
||||
const text = m.content_text || '';
|
||||
if (isRichHtmlEmail(text)) return text;
|
||||
return '';
|
||||
},
|
||||
mailIframeSrcdoc() {
|
||||
if (!isRichHtmlEmail(this.mailRawHtml)) return '';
|
||||
return buildEmailPreviewDocument(this.mailRawHtml) || '';
|
||||
},
|
||||
mailIframeStyle() {
|
||||
const h = Math.max(Number(this.mailIframeHeight) || 0, 80);
|
||||
return {
|
||||
height: h + 'px',
|
||||
minHeight: '80px',
|
||||
maxHeight: 'none'
|
||||
};
|
||||
},
|
||||
/** 正文:兼容 content_html / body / html,纯文本时包一层 pre */
|
||||
mailBodyHtml() {
|
||||
if (this.mailIframeSrcdoc) return '';
|
||||
const m = this.mailData || {};
|
||||
let raw = m.content_html || m.body_html || m.html || m.body || m.content || '';
|
||||
let raw = unescapeHtmlEntities(this.mailRawHtml);
|
||||
raw = normalizeEmailHtmlForInlineDisplay(raw);
|
||||
if (raw) return raw;
|
||||
const text = m.content_text;
|
||||
@@ -174,7 +241,109 @@ export default {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
mailIframeSrcdoc() {
|
||||
this.teardownIframeResize();
|
||||
this.mailIframeHeight = 120;
|
||||
this.$nextTick(() => this.onMailIframeLoad());
|
||||
},
|
||||
mailRawHtml() {
|
||||
this.mailIframeHeight = 120;
|
||||
this.teardownIframeResize();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleMailPreviewMessage(e) {
|
||||
if (!e || !e.data || e.data.type !== MAIL_PREVIEW_HEIGHT_MSG) return;
|
||||
const frame = this.$refs.mailHtmlFrame;
|
||||
if (
|
||||
frame &&
|
||||
frame.contentWindow &&
|
||||
e.source &&
|
||||
e.source !== frame.contentWindow
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const h = sanitizeMailPreviewHeight(e.data.height);
|
||||
if (h > 0) {
|
||||
this.mailIframeHeight = h;
|
||||
}
|
||||
},
|
||||
measureMailIframeOffscreen() {
|
||||
const panel = this.$el && this.$el.querySelector('.detail-scroll-content');
|
||||
const w = panel ? Math.max(panel.clientWidth - 48, 280) : 640;
|
||||
const h = sanitizeMailPreviewHeight(measureHtmlContentHeight(this.mailRawHtml, w));
|
||||
if (h > 0) {
|
||||
this.mailIframeHeight = Math.max(this.mailIframeHeight, h);
|
||||
}
|
||||
},
|
||||
teardownIframeResize() {
|
||||
if (this._iframeResizeObserver) {
|
||||
this._iframeResizeObserver.disconnect();
|
||||
this._iframeResizeObserver = null;
|
||||
}
|
||||
if (this._iframeResizeTimers && this._iframeResizeTimers.length) {
|
||||
this._iframeResizeTimers.forEach((id) => clearTimeout(id));
|
||||
this._iframeResizeTimers = [];
|
||||
}
|
||||
},
|
||||
resizeMailIframe() {
|
||||
const frame = this.$refs.mailHtmlFrame;
|
||||
if (!frame) return;
|
||||
try {
|
||||
const h = sanitizeMailPreviewHeight(resizeIframeToContent(frame));
|
||||
if (h > 0) {
|
||||
this.mailIframeHeight = h;
|
||||
}
|
||||
} catch (e) {
|
||||
/* 无法访问 iframe 文档时保持当前高度 */
|
||||
}
|
||||
},
|
||||
setupIframeResizeObserver() {
|
||||
if (this._iframeResizeObserver) {
|
||||
this._iframeResizeObserver.disconnect();
|
||||
this._iframeResizeObserver = null;
|
||||
}
|
||||
const frame = this.$refs.mailHtmlFrame;
|
||||
if (!frame || typeof ResizeObserver === 'undefined') return;
|
||||
try {
|
||||
const doc = frame.contentDocument;
|
||||
if (!doc || !doc.body) return;
|
||||
const ro = new ResizeObserver(() => this.resizeMailIframe());
|
||||
ro.observe(doc.body);
|
||||
if (doc.documentElement) ro.observe(doc.documentElement);
|
||||
this._iframeResizeObserver = ro;
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
bindIframeImageLoad() {
|
||||
const frame = this.$refs.mailHtmlFrame;
|
||||
if (!frame) return;
|
||||
try {
|
||||
const doc = frame.contentDocument;
|
||||
if (!doc) return;
|
||||
doc.querySelectorAll('img').forEach((img) => {
|
||||
if (img.complete) return;
|
||||
img.addEventListener('load', () => this.resizeMailIframe());
|
||||
img.addEventListener('error', () => this.resizeMailIframe());
|
||||
});
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
onMailIframeLoad() {
|
||||
this.$nextTick(() => {
|
||||
this.resizeMailIframe();
|
||||
this.measureMailIframeOffscreen();
|
||||
this.setupIframeResizeObserver();
|
||||
this.bindIframeImageLoad();
|
||||
const delays = [100, 400, 1000, 2000];
|
||||
this._iframeResizeTimers = delays.map((ms) =>
|
||||
setTimeout(() => this.resizeMailIframe(), ms)
|
||||
);
|
||||
});
|
||||
},
|
||||
escapeHtml(text) {
|
||||
if (text == null) return '';
|
||||
return String(text)
|
||||
@@ -453,6 +622,17 @@ const res = await this.$api.post('api/email_client/getAttachment', {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 完整 HTML 邮件 iframe:高度由 JS 设为固定 px,禁止 height:auto */
|
||||
.mail-html-iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
margin-bottom: 50px;
|
||||
border: 0;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 正文 */
|
||||
.mail-body-content {
|
||||
line-height: 1.6;
|
||||
@@ -460,6 +640,30 @@ const res = await this.$api.post('api/email_client/getAttachment', {
|
||||
color: #303133;
|
||||
min-height: 200px;
|
||||
margin-bottom: 50px;
|
||||
overflow-x: auto;
|
||||
word-break: break-word;
|
||||
}
|
||||
/* 营销邮件在窄容器内常被 @media 规则隐藏 desktop-only,预览强制展示 */
|
||||
.mail-body-content >>> .desktop-only,
|
||||
.mail-body-content >>> .mobile-only {
|
||||
display: block !important;
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.mail-body-content >>> .imgDesk {
|
||||
display: block !important;
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.mail-body-content >>> .imgMobile {
|
||||
display: none !important;
|
||||
}
|
||||
.mail-body-content >>> table {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.mail-body-content >>> img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* v-html 注入的正文无 scoped 属性,用 deep 命中内部的 pre */
|
||||
@@ -664,26 +868,37 @@ const res = await this.$api.post('api/email_client/getAttachment', {
|
||||
color: #0052d9;
|
||||
}
|
||||
.attachment-brief-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
border-top: 1px solid #ebeef5;
|
||||
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
.attachment-brief-bar .brief-info {
|
||||
|
||||
}
|
||||
.attachment-brief-bar .first-file-name {
|
||||
color: #909399;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.jump-link {
|
||||
margin-left: 15px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.attachment-brief-bar .first-file-name {
|
||||
color: #909399;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.attachment-brief-bar .jump-link {
|
||||
flex-shrink: 0;
|
||||
margin-left: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.mail-body-wrap {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.attachment-section {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -172,7 +172,11 @@ const API = {
|
||||
getAllJournal: 'api/Article/getJournal'
|
||||
};
|
||||
import MailDetail from '../../components/page/components/email/MailDetail.vue';
|
||||
import { normalizeEmailHtmlForInlineDisplay } from '@/utils/emailHtmlView';
|
||||
import {
|
||||
normalizeEmailHtmlForInlineDisplay,
|
||||
isRichHtmlEmail,
|
||||
unescapeHtmlEntities
|
||||
} from '@/utils/emailHtmlView';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -551,20 +555,31 @@ fetchLatestSingleMail(jEmailId, journalId) {
|
||||
has_attachment: (item && item.has_attachment) || 0
|
||||
};
|
||||
}
|
||||
const html = d.content_html || d.body_html || d.html || d.body || '';
|
||||
let html = d.content_html || d.body_html || d.html || d.body || '';
|
||||
const text = d.content_text || '';
|
||||
if (!html && isRichHtmlEmail(text)) {
|
||||
html = 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;
|
||||
content_html = unescapeHtmlEntities(content_html);
|
||||
if (isRichHtmlEmail(content_html) || isRichHtmlEmail(html)) {
|
||||
const normalized = normalizeEmailHtmlForInlineDisplay(content_html || html);
|
||||
if (normalized && normalized.trim()) {
|
||||
content_html = normalized;
|
||||
}
|
||||
}
|
||||
if (!content_html || !String(content_html).trim()) {
|
||||
content_html =
|
||||
(text ? `<pre class="mail-plain-pre">${this.escapeHtml(text)}</pre>` : '') || listSnippet;
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
...d,
|
||||
content_html,
|
||||
content_text: text || d.content_text || '',
|
||||
inbox_id: inboxKey,
|
||||
attachments: []
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- publish 引用编辑页面 -->
|
||||
<editPublicRefRdit ref="editPublicRefRdit" :chanFerFormRepeatList="chanFerFormRepeatList" :chanFerForm = "chanFerForm" :gridData = "gridData" :p_article_id='p_article_id' @ChanFerMashUp="ChanFerMashUp" @refrashComp="refrashComp" @changeRefer="changeRefer"></editPublicRefRdit>
|
||||
<editPublicRefRdit ref="editPublicRefRdit" :chanFerFormRepeatList="chanFerFormRepeatList" :chanFerForm = "chanFerForm" :gridData = "gridData" :p_article_id='p_article_id' :article_id="article_id" @ChanFerMashUp="ChanFerMashUp" @refrashComp="refrashComp" @changeRefer="changeRefer"></editPublicRefRdit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
export default {
|
||||
data(){
|
||||
return{
|
||||
article_id: this.$route.query.id,
|
||||
article_id: null,
|
||||
chanFerForm: [],
|
||||
chanFerFormRepeatList: [],
|
||||
gridData: '',
|
||||
@@ -21,9 +21,27 @@ export default {
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.getData()
|
||||
this.initPage()
|
||||
},
|
||||
methods:{
|
||||
/** 正文接口 getArticleMainsNew 需用 production.article_id,非路由 id */
|
||||
fetchProductionArticleId() {
|
||||
if (!this.p_article_id) return Promise.resolve();
|
||||
return this.$api
|
||||
.post('api/Production/getProductionDetail', { p_article_id: this.p_article_id })
|
||||
.then((res) => {
|
||||
if (res.code == 0 && res.data && res.data.production) {
|
||||
this.article_id = res.data.production.article_id;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('getProductionDetail failed', err);
|
||||
});
|
||||
},
|
||||
async initPage() {
|
||||
await this.fetchProductionArticleId();
|
||||
this.getData();
|
||||
},
|
||||
// 获取p_article_id的值
|
||||
getArtcleDetails(){
|
||||
// 获得文章详情
|
||||
|
||||
Reference in New Issue
Block a user