新增邮件转发

This commit is contained in:
eoao
2026-02-07 12:24:45 +08:00
parent 1571981574
commit ccd6cb22bd
13 changed files with 79 additions and 25 deletions

View File

@@ -949,6 +949,11 @@ function loadData() {
@media (max-width: 1366px) {
height: 83px;
}
@media (pointer: coarse) {
/* 触屏 */
user-select: none;
}
&.all-email {
height: 65px;
@media (max-width: 1366px) {

View File

@@ -8,7 +8,6 @@
<script setup>
import {ref, onMounted, onBeforeUnmount, watch, nextTick, shallowRef, defineEmits, computed} from 'vue';
import loading from "@/components/loading/index.vue";
import {compressImage} from "@/utils/file-utils.js";
import {useI18n} from 'vue-i18n'
import {useUiStore} from '@/store/ui.js'
import {useSettingStore} from '@/store/setting.js'
@@ -140,7 +139,6 @@ function initEditor() {
input.addEventListener('change', async (e) => {
let file = e.target.files[0];
file = await compressImage(file);
const reader = new FileReader();
reader.onload = () => {
const id = 'blobid' + (new Date()).getTime();

View File

@@ -184,6 +184,7 @@ const en = {
changeUserName: 'Change Username',
send: 'Send',
reply: 'Reply',
forward: 'Forward',
confirm: 'Confirm',
cancel: 'Cancel',
delEmailConfirm: 'Confirm deleting this email?',
@@ -228,7 +229,6 @@ const en = {
banRestore: 'Confirm banning {msg}?',
logOut: 'Sign out',
clearContentConfirm: 'Are you sure to clear all content?',
attLimitMsg: 'Attachment size limit: 28MB',
emptyRecipientMsg: 'Recipient email cannot be empty',
emptySubjectMsg: 'Subject cannot be empty',
emptyContentMsg: 'Content cannot be empty',

View File

@@ -184,6 +184,7 @@ const zh = {
changeUserName: '修改用户名',
send: '发送',
reply: '回复',
forward: '转发',
confirm: '确定',
cancel: '取消',
delEmailConfirm: '确认删除该邮件吗?',
@@ -228,7 +229,6 @@ const zh = {
banRestore: '确认禁用 {msg} 吗?',
logOut: '退出',
clearContentConfirm: '确定要清空所有内容吗?',
attLimitMsg: '附件不能超过28MB',
emptyRecipientMsg: '收件人邮箱地址不能为空',
emptySubjectMsg: '主题不能为空',
emptyContentMsg: '邮件正文不能为空',

View File

@@ -61,6 +61,7 @@
</div>
<div>
<el-button type="primary" @click="sendEmail" v-if="form.sendType === 'reply'">{{ $t('reply') }}</el-button>
<el-button type="primary" @click="sendEmail" v-else-if="form.sendType === 'forward'">{{ $t('forward') }}</el-button>
<el-button type="primary" @click="sendEmail" v-else>{{ $t('send') }}</el-button>
</div>
</div>
@@ -114,10 +115,11 @@ import router from "@/router/index.js";
defineExpose({
open,
openReply,
openForward,
openDraft
})
const {t} = useI18n()
const {t, locale} = useI18n()
const writerStore = useWriterStore();
const draftStore = userDraftStore()
const settingStore = useSettingStore()
@@ -207,7 +209,6 @@ function selectChange(value) {
function selectStatusChange(status) {
selectStatus = status
ruleEmailsInputDesc.value = status ? '' : ruleEmailsInputDesc.value = t('ruleEmailsInputDesc')
}
const openSelect = () => {
@@ -264,25 +265,23 @@ function delAtt(index) {
function chooseFile() {
const doc = document.createElement("input")
doc.setAttribute("type", "file")
doc.multiple = true;
doc.click()
doc.onchange = async (e) => {
const file = e.target.files[0]
const size = file.size
const filename = file.name
const contentType = file.type
const fileList = e.target.files;
for (const file of fileList) {
const size = file.size
const filename = file.name
const contentType = file.type
const content = await fileToBase64(file)
form.attachments.push({content, filename, size, contentType})
const TotalSize = form.attachments.reduce((acc, item) => acc + item.size, 0);
if ((TotalSize + size) > 29360128) {
ElMessage({
message: t('attLimitMsg'),
type: 'error',
plain: true,
})
return
}
const content = await fileToBase64(file)
form.attachments.push({content, filename, size, contentType})
}
}
@@ -427,6 +426,35 @@ function focusChange() {
if (selectStatus) openSelect()
}
function openForward(email) {
resetForm();
email.subject = email.subject || ''
form.subject = email.subject
form.sendType = 'forward'
form.emailId = email.emailId
defValue.value = ''
setTimeout(() => {
defValue.value = `
<articl class="mceNonEditable" >
${formatImage(email.content) || `<pre style="font-family: inherit;word-break: break-word;white-space: pre-wrap;margin: 0">${email.text}</pre>`}
</article>
`
open()
nextTick(() => {
backReply.content = editor.value.getContent()
backReply.subject = form.subject
backReply.receiveEmail = form.receiveEmail
backReply.sendType = form.sendType
})
});
}
function openReply(email) {
resetForm();
@@ -527,7 +555,7 @@ function close() {
return;
}
if (backReply.sendType === 'reply') {
if (backReply.sendType === 'reply' || backReply.sendType === 'forward') {
let subjectFlag = form.subject === backReply.subject
let contentFlag = editor.value.getContent() === backReply.content
let receiveFlag = form.receiveEmail.length === 1 && form.receiveEmail[0] === backReply.receiveEmail[0]

View File

@@ -7,7 +7,8 @@
<Icon class="icon" @click="changeStar" v-if="email.isStar" icon="fluent-color:star-16" width="20" height="20"/>
<Icon class="icon" @click="changeStar" v-else icon="solar:star-line-duotone" width="18" height="18"/>
</span>
<Icon class="icon" v-if="emailStore.contentData.showReply" v-perm="'email:send'" @click="openReply" icon="la:reply" width="20" height="20" />
<Icon class="icon" v-if="emailStore.contentData.showReply" v-perm="'email:send'" @click="openReply" icon="la:reply" width="21" height="21" />
<Icon class="icon" v-if="emailStore.contentData.showReply" v-perm="'email:send'" @click="openForward" icon="tabler:location-share" width="19" height="19" />
</div>
<div></div>
<el-scrollbar class="scrollbar">
@@ -121,6 +122,10 @@ function openReply() {
uiStore.writerRef.openReply(email)
}
function openForward() {
uiStore.writerRef.openForward(email)
}
function toMessage(message) {
return message ? JSON.parse(message).message : '';
}

View File

@@ -57,7 +57,7 @@
</div>
</div>
<div class="empty" v-if="regKeyData.length === 0">
<el-empty v-if="!regKeyFirst" :image-size="isMobile ? 120 : 0" :description="$t('noCodeFound')"/>
<el-empty v-if="!regKeyFirst" :image-size="isMobile ? 120 : null" :description="$t('noCodeFound')"/>
</div>
</el-scrollbar>
<el-dialog v-model="showAdd" :title="$t('addRegKey')">

View File

@@ -1273,6 +1273,13 @@ function adjustWidth() {
white-space: nowrap;
}
:deep(.el-table) {
@media (pointer: coarse) {
/* 触屏 */
user-select: none;
}
}
:deep(.el-table th.el-table__cell>.cell.highlight) {
color: #909399;
}

View File

@@ -23,6 +23,8 @@ const en = {
noResendToken: 'Resend API token not configured',
sendEmailNotCurUser: 'Sender email does not belong to current user',
notExistEmailReply: 'Mail does not exist and cannot be replied to',
imageAttLimit: 'The maximum number of image attachments is 10',
attLimit: 'The maximum number of attachments is 10.',
pwdLengthLimit: 'Password length exceeds the limit',
emailLengthLimit: 'Email length exceeds the limit',
minEmailPrefix: 'Email must be at least {{msg}} characters',

View File

@@ -23,6 +23,8 @@ const zh = {
noResendToken: 'resend密钥未配置',
sendEmailNotCurUser: '发件人邮箱非当前用户所有',
notExistEmailReply: '邮件不存在无法回复',
imageAttLimit: '图片不能超过10个',
attLimit: '附件不能超过10个',
pwdLengthLimit: '密码长度超出限制',
emailLengthLimit: '邮箱长度超出限制',
minEmailPrefix: '邮箱名至少{{msg}}位',

View File

@@ -179,6 +179,7 @@ const attService = {
cacheControl: `max-age=259200`,
contentDisposition: `inline;filename=${attData.filename}`
});
delete attData.buff;
}
await orm(c).insert(att).values(attDataList).run();

View File

@@ -331,11 +331,17 @@ const emailService = {
//保存内嵌附件
if (imageDataList.length > 0) {
if (imageDataList.length > 10) {
throw new BizError(t('imageAttLimit'));
}
await attService.saveArticleAtt(c, imageDataList, userId, accountId, emailResult.emailId);
}
//保存普通附件
if (attachments?.length > 0) {
if (attachments.length > 10) {
throw new BizError(t('attLimit'));
}
await attService.saveSendAtt(c, attachments, userId, accountId, emailResult.emailId);
}

View File

@@ -77,10 +77,10 @@ const settingService = {
if (!showSiteKey) {
settingRow.siteKey = settingRow.siteKey ? `${settingRow.siteKey.slice(0, 12)}******` : null;
settingRow.siteKey = settingRow.siteKey ? `${settingRow.siteKey.slice(0, 6)}******` : null;
}
settingRow.secretKey = settingRow.secretKey ? `${settingRow.secretKey.slice(0, 12)}******` : null;
settingRow.secretKey = settingRow.secretKey ? `${settingRow.secretKey.slice(0, 6)}******` : null;
Object.keys(settingRow.resendTokens).forEach(key => {
settingRow.resendTokens[key] = `${settingRow.resendTokens[key].slice(0, 12)}******`;