From 723ec0d1904e0fa08987a35c186e1174f61e4167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=8B=E4=BA=8E=E5=88=9D=E8=A7=81?= <752204717@qq.com> Date: Wed, 13 May 2026 13:25:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/common.vue | 10 +- src/components/common/langs/en.js | 9 + src/components/common/langs/zh.js | 9 + .../page/components/email/MailDetail.vue | 174 +++++++++++++++++- 4 files changed, 195 insertions(+), 7 deletions(-) diff --git a/src/components/common/common.vue b/src/components/common/common.vue index 9bac70a..a1781ec 100644 --- a/src/components/common/common.vue +++ b/src/components/common/common.vue @@ -2,14 +2,14 @@ //记得切换 //正式 -const mediaUrl = '/public/'; -const baseUrl = '/'; +// const mediaUrl = '/public/'; +// const baseUrl = '/'; //正式环境 -// const mediaUrl = 'https://submission.tmrjournals.com/public/'; -// // const mediaUrl = 'http://zmzm.tougao.dev.com/public/'; -// const baseUrl = '/api' +const mediaUrl = 'https://submission.tmrjournals.com/public/'; +// const mediaUrl = 'http://zmzm.tougao.dev.com/public/'; +const baseUrl = '/api' //测试环境 diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js index faf7dce..0df326a 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -657,6 +657,15 @@ colTitle: 'Template title', printBtn: 'Print', previewNotSupported: 'This file format cannot be previewed online', downloadToView: 'Download to view locally', + registerAuthorBtn: 'Create author account', + registerAuthorConfirm: + 'Create an account via the same admin API as User Management: login name "{account}", display name "{realname}", email "{email}", initial password 123456qwe (no captcha). Continue?', + registerAuthorSuccess: 'Author account created.', + registerAuthorFail: 'Creation failed. Try again later or add the user manually in User Management.', + registerAuthorExistsEmail: 'This email is already registered.', + registerAuthorExistsAccount: 'This login name is already taken. Edit the sender display name or add the user manually.', + registerAuthorNeedEmail: 'Sender email is missing; cannot create an account.', + registerAuthorNoQq: 'QQ Mail is not supported for author accounts. Please add the user manually.', }, crawlerKeywords: { pageTitle: 'Keyword Configuration', diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index 56f5898..ebeea0e 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -646,6 +646,15 @@ const zh = { printBtn: '打印', previewNotSupported: '该文件格式无法在线预览', downloadToView: '下载到本地查看', + registerAuthorBtn: '创建作者账号', + registerAuthorConfirm: + '将使用推广后台「添加用户」接口创建账号:登录名「{account}」,显示名「{realname}」,邮箱「{email}」,初始密码 123456qwe(无需验证码)。是否继续?', + registerAuthorSuccess: '作者账号已创建。', + registerAuthorFail: '创建失败,请稍后重试或到用户管理中手动添加。', + registerAuthorExistsEmail: '该邮箱已被注册。', + registerAuthorExistsAccount: '该登录名已被占用,请人工处理或修改发件人显示名后重试。', + registerAuthorNeedEmail: '缺少发件人邮箱,无法创建账号。', + registerAuthorNoQq: '本站不支持 QQ 邮箱作为作者账号,请在用户管理中手动添加。', }, crawlerKeywords: { pageTitle: '关键词配置', diff --git a/src/components/page/components/email/MailDetail.vue b/src/components/page/components/email/MailDetail.vue index 2313b7b..9b0179e 100644 --- a/src/components/page/components/email/MailDetail.vue +++ b/src/components/page/components/email/MailDetail.vue @@ -5,6 +5,18 @@

{{ $t('mailboxCollect.subject') }}:{{ mailData.subject }}

+ + {{ $t('mailboxCollect.registerAuthorBtn') }} +
@@ -70,6 +82,18 @@ {{ $t('mailboxCollect.viewAttachments') }} + + {{ $t('mailboxCollect.registerAuthorBtn') }} + @@ -152,10 +176,20 @@ export default { mediaUrl: Common.mediaUrl, isDetailExpanded: false, downloadingIndex: -1, - packingAll: false + packingAll: false, + registerAuthorLoading: false }; }, computed: { + /** 存在 Word 附件(.doc / .docx)时显示「创建作者账号」 */ + hasWordAttachment() { + const att = (this.mailData && this.mailData.attachments) || []; + if (!att.length) return false; + return att.some((f) => { + const n = (f && f.name) || ''; + return /\.(doc|docx)$/i.test(n); + }); + }, 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); @@ -175,6 +209,127 @@ export default { } }, methods: { + stripHtml(html) { + if (html == null || html === '') return ''; + const s = String(html); + const d = typeof document !== 'undefined' ? document.createElement('div') : null; + if (d) { + d.innerHTML = s; + return (d.textContent || d.innerText || '').trim(); + } + return s.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim(); + }, + /** 与 partyList 添加用户一致:从正文尝试提取手机号(国内 11 位或含 + 的国际号) */ + extractPhoneFromMailBody() { + const m = this.mailData || {}; + let blob = ''; + ['content_text', 'content', 'body', 'content_html', 'html', 'body_html'].forEach((k) => { + const v = m[k]; + if (v != null && String(v).trim() !== '') blob += `\n${String(v)}`; + }); + const text = this.stripHtml(blob); + const cn = text.match(/(?:^|\D)(1[3-9]\d{9})(?:\D|$)/); + if (cn) return cn[1]; + const intl = text.match(/\+\d{1,3}[\s\-]?\d[\d\s\-]{8,18}\d/); + if (intl) return intl[0].replace(/\s+/g, ' ').trim().slice(0, 32); + const labeled = text.match(/(?:Tel|Phone|Mobile|MW)[\s::]*([+()\d][\d\s\-().]{10,40})/i); + if (labeled) return labeled[1].trim().slice(0, 32); + return ''; + }, + buildAuthorRegisterPayload() { + const m = this.mailData || {}; + const email = String(m.from_email || '') + .trim() + .toLowerCase(); + const localPart = email.split('@')[0] || 'user'; + let rawName = String(m.from_name || '').trim(); + let realname = rawName || localPart; + if (this.$validateString && !this.$validateString(realname)) { + realname = localPart; + if (this.$validateString && !this.$validateString(realname)) { + realname = 'Author'; + } + } + let account = rawName.replace(/[^a-zA-Z0-9_-]/g, ''); + if (!account || account.length < 2) { + account = localPart.replace(/[^a-zA-Z0-9_-]/g, ''); + } + if (!account || account.length < 2) { + account = `u${email.replace(/[^a-zA-Z0-9]/g, '').slice(0, 12) || 'ser'}`; + } + const phone = this.extractPhoneFromMailBody() || ''; + return { email, account, realname, phone }; + }, + async registerAuthorFromMail() { + if (!this.hasWordAttachment) return; + const { email, account, realname, phone } = this.buildAuthorRegisterPayload(); + if (!email) { + this.$message.warning(this.$t('mailboxCollect.registerAuthorNeedEmail')); + return; + } + if (email.endsWith('@qq.com')) { + this.$message.warning(this.$t('mailboxCollect.registerAuthorNoQq')); + return; + } + const pwd = '123456qwe'; + try { + await this.$confirm( + this.$t('mailboxCollect.registerAuthorConfirm', { email, account, realname }), + this.$t('mailboxCollect.registerAuthorBtn'), + { type: 'warning', distinguishCancelAndClose: true } + ); + } catch (e) { + return; + } + this.registerAuthorLoading = true; + try { + const p = this.buildAuthorRegisterPayload(); + const email2 = p.email; + const account2 = p.account; + const realname2 = p.realname; + const phone2 = p.phone; + const ef = { email: email2, account: account2 }; + const r1 = await this.$api.post('api/User/checkUserByEmail', ef); + if (!r1 || Number(r1.code) !== 0) { + this.$message.error((r1 && r1.msg) || this.$t('mailboxCollect.registerAuthorFail')); + return; + } + const r2 = await this.$api.post('api/User/checkUserByAccount', ef); + if (!r2 || Number(r2.code) !== 0) { + this.$message.error((r2 && r2.msg) || this.$t('mailboxCollect.registerAuthorFail')); + return; + } + const hasEmail = r1.data && Number(r1.data.has) === 1; + const hasAccount = r2.data && Number(r2.data.has) === 1; + if (hasEmail) { + this.$message.warning(this.$t('mailboxCollect.registerAuthorExistsEmail')); + return; + } + if (hasAccount) { + this.$message.warning(this.$t('mailboxCollect.registerAuthorExistsAccount')); + return; + } + const addForm = { + email: email2, + account: account2, + password: pwd, + repassword: pwd, + realname: realname2, + phone: phone2 || '' + }; + const res = await this.$api.post('api/User/addUser', addForm); + if (res && Number(res.code) === 0) { + this.$message.success(this.$t('mailboxCollect.registerAuthorSuccess')); + } else { + this.$message.error((res && res.msg) || this.$t('mailboxCollect.registerAuthorFail')); + } + } catch (err) { + console.error(err); + this.$message.error(this.$t('mailboxCollect.registerAuthorFail')); + } finally { + this.registerAuthorLoading = false; + } + }, escapeHtml(text) { if (text == null) return ''; return String(text) @@ -383,6 +538,19 @@ const res = await this.$api.post('api/email_client/getAttachment', { color: #606266; cursor: pointer; } +.toolbar-right { + display: flex; + align-items: center; + gap: 12px; + flex-shrink: 0; +} +.register-author-btn { + flex-shrink: 0; +} +.register-author-btn-inline { + margin-left: 10px; + vertical-align: middle; +} .action-icon:hover { color: #409eff; } @@ -665,7 +833,9 @@ const res = await this.$api.post('api/email_client/getAttachment', { } .attachment-brief-bar { display: flex; + flex-wrap: wrap; align-items: center; + gap: 8px 12px; padding: 8px 0; font-size: 13px; color: #606266; @@ -681,7 +851,7 @@ const res = await this.$api.post('api/email_client/getAttachment', { margin-left: 4px; } .jump-link { - margin-left: 15px; + margin-left: 0; font-size: 13px; } .attachment-section {