新增中英文切换

This commit is contained in:
eoao
2025-07-22 22:02:23 +08:00
parent 249edb9e19
commit bbcff4908d
64 changed files with 1951 additions and 1298 deletions

View File

@@ -21,8 +21,8 @@
<Icon icon="fluent:settings-24-filled" width="21" height="21" color="#909399" />
<template #dropdown >
<el-dropdown-menu >
<el-dropdown-item v-if="hasPerm('email:send')" @click="openSetName(item)">改名</el-dropdown-item>
<el-dropdown-item v-if="item.accountId !== userStore.user.accountId && hasPerm('account:delete')" @click="remove(item)">删除</el-dropdown-item>
<el-dropdown-item v-if="hasPerm('email:send')" @click="openSetName(item)">{{$t('rename')}}</el-dropdown-item>
<el-dropdown-item v-if="item.accountId !== userStore.user.accountId && hasPerm('account:delete')" @click="remove(item)">{{$t('delete')}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -61,23 +61,23 @@
</template>
<div class="noLoading" v-if="noLoading && accounts.length > 0">
<div>没有更多数据了</div>
<div>{{$t('noMoreData')}}</div>
</div>
<div class="empty" v-if="noLoading && accounts.length === 0">
<el-empty description="没有任何邮件" />
<el-empty :description="$t('noMessagesFound')" />
</div>
</div>
</el-scrollbar>
<el-dialog v-model="showAdd" title="添加邮箱" >
<el-dialog v-model="showAdd" :title="$t('addAccount')" >
<div class="container">
<el-input v-model="addForm.email" ref="addRef" type="text" placeholder="邮箱" autocomplete="off">
<el-input v-model="addForm.email" ref="addRef" type="text" :placeholder="$t('emailAccount')" autocomplete="off">
<template #append>
<div @click.stop="openSelect">
<el-select
ref="mySelect"
v-model="addForm.suffix"
placeholder="请选择"
:placeholder="$t('select')"
class="select"
>
<el-option
@@ -95,7 +95,7 @@
</template>
</el-input>
<el-button class="btn" type="primary" @click="submit" :loading="addLoading"
>添加
>{{$t('add')}}
</el-button>
</div>
<div
@@ -105,12 +105,12 @@
data-callback="onTurnstileSuccess"
></div>
</el-dialog>
<el-dialog v-model="setNameShow" title="修改用户名" >
<el-dialog v-model="setNameShow" :title="$t('changeUserName')" >
<div class="container">
<el-input v-model="accountName" type="text" placeholder="名字" autocomplete="off">
<el-input v-model="accountName" type="text" :placeholder="$t('username')" autocomplete="off">
</el-input>
<el-button class="btn" type="primary" @click="setName" :loading="setNameLoading"
>保存
>{{$t('save')}}
</el-button>
</div>
</el-dialog>
@@ -124,8 +124,10 @@ import {isEmail} from "@/utils/verify-utils.js";
import {useSettingStore} from "@/store/setting.js";
import {useAccountStore} from "@/store/account.js";
import {useUserStore} from "@/store/user.js";
import hasPerm from "@/utils/perm.js";
import { hasPerm } from "@/perm/perm.js"
import {useI18n} from "vue-i18n";
const { t } = useI18n();
const userStore = useUserStore();
const accountStore = useAccountStore();
const settingStore = useSettingStore();
@@ -186,7 +188,7 @@ function setName() {
if (!name) {
ElMessage({
message: '用户名不能为空',
message: t('emptyUserNameMsg'),
type: 'error',
plain: true,
})
@@ -203,7 +205,7 @@ function setName() {
}
ElMessage({
message: "保存成功",
message: t('changSuccessMsg'),
type: "success",
plain: true
})
@@ -227,9 +229,9 @@ function itemBg(accountId) {
}
function remove(account) {
ElMessageBox.confirm(`确认删除${account.email}吗?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
ElMessageBox.confirm(t('delConfirm',{msg: account.email}), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
accountDelete(account.accountId).then(() => {
@@ -239,7 +241,7 @@ function remove(account) {
getAccountList()
}
ElMessage({
message: '删除成功',
message: t('delSuccessMsg'),
type: 'success',
plain: true,
})
@@ -274,14 +276,14 @@ async function copyAccount(account) {
try {
await navigator.clipboard.writeText(account);
ElMessage({
message: '复制成功',
message: t('copySuccessMsg'),
type: 'success',
plain: true,
})
} catch (err) {
console.error('复制失败:', err);
console.error(`${t('copyFailMsg')}:`, err);
ElMessage({
message: '复制失败',
message: t('copyFailMsg'),
type: 'error',
plain: true,
})
@@ -321,7 +323,7 @@ function submit() {
if (!addForm.email){
ElMessage({
message: "邮箱不能为空",
message: t('emptyEmailMsg'),
type: "error",
plain: true
})
@@ -330,7 +332,7 @@ function submit() {
if (!isEmail(addForm.email+addForm.suffix)) {
ElMessage({
message: "非法邮箱",
message: t('notEmailMsg'),
type: "error",
plain: true
})
@@ -356,7 +358,7 @@ function submit() {
accounts.push(account)
verifyToken = ''
ElMessage({
message: "添加成功",
message: t('addSuccessMsg'),
type: "success",
plain: true
})

View File

@@ -9,60 +9,60 @@
<el-menu-item @click="router.push({name: 'email'})" index="email"
:class="route.meta.name === 'email' ? 'choose-item' : ''">
<Icon icon="hugeicons:mailbox-01" width="20" height="20" />
<span class="menu-name" style="margin-left: 21px">收件箱</span>
<span class="menu-name" style="margin-left: 21px">{{$t('inbox')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'send'})" index="send" v-perm="'email:send'"
:class="route.meta.name === 'send' ? 'choose-item' : ''">
<Icon icon="cil:send" width="20" height="20" />
<span class="menu-name" style="margin-left: 21px">已发送</span>
<span class="menu-name" style="margin-left: 21px">{{$t('sent')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'draft'})" index="draft" v-perm="'email:send'"
:class="route.meta.name === 'draft' ? 'choose-item' : ''">
<Icon icon="ep:document" width="19" height="19" />
<span class="menu-name" style="margin-left: 22px">草稿箱</span>
<span class="menu-name" style="margin-left: 22px">{{$t('drafts')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'star'})" index="star"
:class="route.meta.name === 'star' ? 'choose-item' : ''">
<Icon icon="solar:star-line-duotone" width="20" height="20" />
<span class="menu-name" style="margin-left: 21px">星标邮件</span>
<span class="menu-name" style="margin-left: 21px">{{$t('starred')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'setting'})" index="setting"
:class="route.meta.name === 'setting' ? 'choose-item' : ''">
<Icon icon="fluent:settings-48-regular" width="20" height="20" />
<span class="menu-name" style="margin-left: 21px">个人设置</span>
<span class="menu-name" style="margin-left: 21px">{{$t('settings')}}</span>
</el-menu-item>
<div class="manage-title" v-perm="['user:query','role:query','setting:query','analysis:query']">
<div>管理</div>
<div>{{$t('manage')}}</div>
</div>
<el-menu-item @click="router.push({name: 'analysis'})" index="analysis" v-perm="'analysis:query'"
:class="route.meta.name === 'analysis' ? 'choose-item' : ''">
<Icon icon="fluent:data-pie-20-regular" width="24" height="24" />
<span class="menu-name" style="margin-left: 18px">分析页</span>
<span class="menu-name" style="margin-left: 18px">{{$t('analytics')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'user'})" index="setting" v-perm="'user:query'"
:class="route.meta.name === 'user' ? 'choose-item' : ''">
<Icon icon="si:user-alt-2-line" width="20" height="20" />
<span class="menu-name" style="margin-left: 21px">用户列表</span>
<span class="menu-name" style="margin-left: 21px">{{$t('allUsers')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'sys-email'})" index="sys-email" v-perm="'sys-email:query'"
:class="route.meta.name === 'sys-email' ? 'choose-item' : ''">
<el-menu-item @click="router.push({name: 'all-email'})" index="all-email" v-perm="'all-email:query'"
:class="route.meta.name === 'all-email' ? 'choose-item' : ''">
<Icon icon="fluent:mail-list-28-regular" width="22" height="22" />
<span class="menu-name" style="margin-left: 20px">邮件列表</span>
<span class="menu-name" style="margin-left: 20px">{{$t('allMail')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'role'})" index="setting" v-perm="'role:query'"
:class="route.meta.name === 'role' ? 'choose-item' : ''">
<Icon icon="fluent:lock-closed-16-regular" width="22" height="22" />
<span class="menu-name" style="margin-left: 20px">权限控制</span>
<span class="menu-name" style="margin-left: 20px">{{$t('permissions')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'reg-key'})" index="reg-key" v-perm="'reg-key:query'"
:class="route.meta.name === 'reg-key' ? 'choose-item' : ''">
<Icon icon="fluent:fingerprint-20-filled" width="22" height="22" />
<span class="menu-name" style="margin-left: 20px">注册密钥</span>
<span class="menu-name" style="margin-left: 20px">{{$t('inviteCode')}}</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'sys-setting'})" index="sys-setting" v-perm="'setting:query'"
:class="route.meta.name === 'sys-setting' ? 'choose-item' : ''">
<Icon icon="eos-icons:system-ok-outlined" width="18" height="18" />
<span class="menu-name" style="margin-left: 24px">系统设置</span>
<span class="menu-name" style="margin-left: 24px">{{$t('SystemSettings')}}</span>
</el-menu-item>
</el-menu>
</div>

View File

@@ -2,7 +2,7 @@
<div class="header" :class="!hasPerm('email:send') ? 'not-send' : ''">
<div class="header-btn">
<hanburger @click="changeAside"></hanburger>
<span class="breadcrumb-item">{{ route.meta.title }}</span>
<span class="breadcrumb-item">{{ $t(route.meta.title) }}</span>
</div>
<div v-perm="'email:send'" class="writer-box" @click="openSend">
<div class="writer" >
@@ -32,12 +32,12 @@
{{ userStore.user.email }}
</div>
<div class="detail-user-type">
<el-tag >{{userStore.user.role.name}}</el-tag>
<el-tag >{{$t(userStore.user.role.name)}}</el-tag>
</div>
<div class="action-info">
<div>
<span style="margin-right: 10px">邮件发送 :</span>
<span style="margin-right: 10px">邮箱添加 :</span>
<span style="margin-right: 10px">{{$t('sendCount')}}</span>
<span style="margin-right: 10px">{{$t('accountCount')}}</span>
</div>
<div>
<div>
@@ -46,22 +46,19 @@
<el-tag v-else >{{sendType}}</el-tag>
</div>
<div>
<el-tag v-if="settingStore.settings.manyEmail || settingStore.settings.addEmail" >已关闭</el-tag>
<span v-else-if="accountCount && hasPerm('account:add')" style="margin-right: 5px">{{ accountCount }}</span>
<el-tag v-else-if="!accountCount && hasPerm('account:add')" >无限制</el-tag>
<el-tag v-else-if="!hasPerm('account:add')" >无权限</el-tag>
<el-tag v-if="settingStore.settings.manyEmail || settingStore.settings.addEmail" >{{$t('disabled')}}</el-tag>
<span v-else-if="accountCount && hasPerm('account:add')" style="margin-right: 5px">{{ $t('totalUserAccount',{msg: accountCount}) }}</span>
<el-tag v-else-if="!accountCount && hasPerm('account:add')" >{{$t('noLimit')}}</el-tag>
<el-tag v-else-if="!hasPerm('account:add')" >{{$t('blocked')}}</el-tag>
</div>
</div>
</div>
<div class="logout">
<el-button type="primary" :loading="logoutLoading" @click="clickLogout">退出</el-button>
<el-button type="primary" :loading="logoutLoading" @click="clickLogout">{{$t('signOut')}}</el-button>
</div>
</div>
</template>
</el-dropdown>
<div class="full" @click="full">
<Icon icon="iconamoon:screen-full-light" width="22" height="22" />
</div>
</div>
</div>
</template>
@@ -76,9 +73,10 @@ import {useUserStore} from "@/store/user.js";
import { useRoute } from "vue-router";
import {computed, ref} from "vue";
import {useSettingStore} from "@/store/setting.js";
import hasPerm from "@/utils/perm.js";
import screenfull from "screenfull";
import { hasPerm } from "@/perm/perm.js"
import {useI18n} from "vue-i18n";
const { t } = useI18n();
const route = useRoute();
const settingStore = useSettingStore();
const userStore = useUserStore();
@@ -91,23 +89,27 @@ const accountCount = computed(() => {
const sendType = computed(() => {
if (settingStore.settings.send === 1) {
return t('disabled')
}
if (!hasPerm('email:send')) {
return '无权限'
return t('noPerm')
}
if (!userStore.user.role.sendCount) {
return '无限制'
return t('noLimit')
}
if (userStore.user.role.sendCount < 0) {
return '无次数'
return t('blocked')
}
if (userStore.user.role.sendType === 'day') {
return '每天'
return t('daily')
}
if (userStore.user.role.sendType === 'count') {
return '次数'
return t('total')
}
})
@@ -150,10 +152,6 @@ function formatName(email) {
return email[0]?.toUpperCase() || ''
}
function full() {
screenfull.toggle();
}
</script>
<style lang="scss" scoped>
@@ -339,11 +337,8 @@ function full() {
.setting-icon {
position: relative;
top: 0;
margin-right: 5px;
margin-right: 10px;
bottom: 10px;
@media (max-width: 1024px) {
margin-right: 10px;
}
}
}

View File

@@ -3,7 +3,7 @@
<div :class="accountShow && hasPerm('account:query') ? 'block-show' : 'block-hide'" @click="uiStore.accountShow = false"></div>
<account :class="accountShow && hasPerm('account:query') ? 'show' : 'hide'" />
<router-view class="main-view" v-slot="{ Component,route }">
<keep-alive :include="['email','sys-email','send','sys-setting','star','user','role','analysis','reg-key','draft']">
<keep-alive :include="['email','all-email','send','sys-setting','star','user','role','analysis','reg-key','draft']">
<component :is="Component" :key="route.name"/>
</keep-alive>
</router-view>
@@ -15,7 +15,7 @@ import {useUiStore} from "@/store/ui.js";
import {useSettingStore} from "@/store/setting.js";
import {computed, onBeforeUnmount, onMounted} from "vue";
import { useRoute } from 'vue-router'
import hasPerm from "@/utils/perm.js";
import { hasPerm } from "@/perm/perm.js"
const props = defineProps({
openSend: Function

View File

@@ -6,7 +6,7 @@
<span class="title-text">
<Icon icon="hugeicons:quill-write-01" width="28" height="28" />
</span>
<span class="sender">发件人:</span>
<span class="sender">{{$t('sender')}}:</span>
<span class="sender-name">{{form.name}}</span>
<span class="send-email"><{{form.sendEmail}}></span>
</div>
@@ -15,17 +15,17 @@
</div>
</div>
<div class="container">
<el-input-tag @add-tag="addTagChange" tag-type="primary" size="default" v-model="form.receiveEmail" placeholder="多个邮箱用, 分开 example1.com,example2.com" >
<el-input-tag @add-tag="addTagChange" tag-type="primary" size="default" v-model="form.receiveEmail" :placeholder="$t('ruleEmailsInputDesc')" >
<template #prefix>
<div class="item-title">收件人 </div>
<div class="item-title">{{$t('recipient')}} </div>
</template>
<template #suffix>
<span class="distribute" :class="form.manyType ? 'checked' : ''" @click.stop="checkDistribute" >分别发送</span>
<span class="distribute" :class="form.manyType ? 'checked' : ''" @click.stop="checkDistribute" >{{$t('sendSeparately')}}</span>
</template>
</el-input-tag>
<el-input v-model="form.subject" placeholder="请输入邮件主题">
<el-input v-model="form.subject" :placeholder="$t('subjectInputDesc')">
<template #prefix>
<div class="item-title">主题 </div>
<div class="item-title">{{$t('subject')}} </div>
</template>
</el-input>
<tinyEditor :def-value="defValue" ref="editor" @change="change"/>
@@ -46,8 +46,8 @@
</div>
</div>
<div>
<el-button type="primary" @click="sendEmail" v-if="form.sendType === 'reply'">回复</el-button>
<el-button type="primary" @click="sendEmail" v-else >发送</el-button>
<el-button type="primary" @click="sendEmail" v-if="form.sendType === 'reply'">{{$t('reply')}}</el-button>
<el-button type="primary" @click="sendEmail" v-else >{{$t('send')}}</el-button>
</div>
</div>
</div>
@@ -66,11 +66,12 @@ import {useEmailStore} from "@/store/email.js";
import {fileToBase64, formatBytes} from "@/utils/file-utils.js";
import {getIconByName} from "@/utils/icon-utils.js";
import sendPercent from "@/components/send-percent/index.vue"
import {formatDetailDate, fromNow} from "@/utils/day.js";
import {formatDetailDate} from "@/utils/day.js";
import {useSettingStore} from "@/store/setting.js";
import {userDraftStore} from "@/store/draft.js";
import db from "@/db/db.js";
import dayjs from "dayjs";
import {useI18n} from "vue-i18n";
defineExpose({
open,
@@ -78,6 +79,7 @@ defineExpose({
openDraft
})
const { t } = useI18n()
const draftStore = userDraftStore()
const settingStore = useSettingStore()
const emailStore = useEmailStore();
@@ -131,9 +133,9 @@ function checkDistribute() {
}
function clearContent() {
ElMessageBox.confirm('确定要清空邮件吗?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
ElMessageBox.confirm(t('clearContentConfirm'), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}).then(() => {
resetForm()
@@ -159,7 +161,7 @@ function chooseFile() {
const TotalSize = form.attachments.reduce((acc, item) => acc + item.size, 0);
if ((TotalSize + size) > 29360128) {
ElMessage({
message: '附件文件大小限制28mb',
message: t('attLimitMsg'),
type: 'error',
plain: true,
})
@@ -174,7 +176,7 @@ async function sendEmail() {
if (form.receiveEmail.length === 0) {
ElMessage({
message: '收件人邮箱地址不能为空',
message: t('emptyRecipientMsg'),
type: 'error',
plain: true,
})
@@ -183,7 +185,7 @@ async function sendEmail() {
if (!form.subject) {
ElMessage({
message: '主题不能为空',
message: t('emptySubjectMsg'),
type: 'error',
plain: true,
})
@@ -192,7 +194,7 @@ async function sendEmail() {
if (!form.content) {
ElMessage({
message: '正文不能为空',
message: t('emptyContentMsg'),
type: 'error',
plain: true,
})
@@ -201,7 +203,7 @@ async function sendEmail() {
if (form.manyType === 'divide' && form.attachments.length > 0) {
ElMessage({
message: '分别发送暂时不支持附件',
message: t('noSeparateSendMsg'),
type: 'error',
plain: true,
})
@@ -210,7 +212,7 @@ async function sendEmail() {
if (sending) {
ElMessage({
message: '邮件正在发送中',
message: t('sendingErrorMsg'),
type: 'error',
plain: true,
})
@@ -218,7 +220,7 @@ async function sendEmail() {
}
percentMessage = ElMessage({
message: () => h(sendPercent, { value: percent.value }),
message: () => h(sendPercent, { value: percent.value,desc: t('sending') }),
dangerouslyUseHTMLString: true,
plain: true,
duration: 0,
@@ -238,7 +240,7 @@ async function sendEmail() {
})
ElNotification({
title: '邮件已发送',
title: t('sendSuccessMsg'),
type: "success",
message: h('span', { style: 'color: teal' }, email.subject),
position: 'bottom-right'
@@ -257,7 +259,7 @@ async function sendEmail() {
show.value = false
}).catch((e) => {
ElNotification({
title: '发送失败',
title: t('sendFailMsg'),
type: e.code === 403 ? 'warning' : 'error',
message: h('span', { style: 'color: teal' }, e.message),
position: 'bottom-right'
@@ -310,7 +312,7 @@ function openReply(email) {
<div></div>
<div>
<br>
${ formatDetailDate(email.createTime) }${email.name} &lt${email.sendEmail}&gt 来信:
${ formatDetailDate(email.createTime) } ${email.name} &lt${email.sendEmail}&gt ${t('wrote')}:
</div>
<blockquote class="mceNonEditable" style="margin: 0 0 0 0.8ex;border-left: 1px solid rgb(204,204,204);padding-left: 1ex;">
<articl>
@@ -397,9 +399,9 @@ function close() {
}
}
ElMessageBox.confirm('是否保存草稿?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
ElMessageBox.confirm(t('saveDraftConfirm'), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
distinguishCancelAndClose: true
}).then( async () => {