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 {