新增注册码、草稿箱、权限拦截邮件、发件菜单隐藏

This commit is contained in:
eoao
2025-07-15 20:09:56 +08:00
parent 7e5a383bc6
commit 254325431b
55 changed files with 1819 additions and 361 deletions

View File

@@ -1,8 +1,8 @@
<template>
<div class="account-box">
<div class="head-opt" >
<Icon v-perm="'account:add'" class="icon" icon="ion:add-outline" width="23" height="23" @click="add" />
<Icon class="icon" icon="ion:reload" width="18" height="18" @click="refresh" />
<Icon v-perm="'account:add'" class="icon add" icon="ion:add-outline" width="23" height="23" @click="add" />
<Icon class="icon refresh" icon="ion:reload" width="18" height="18" @click="refresh" />
</div>
<el-scrollbar class="scrollbar">
<div v-infinite-scroll="getAccountList" :infinite-scroll-distance="600" :infinite-scroll-immediate="false">
@@ -16,11 +16,12 @@
</div>
<div class="settings" @click.stop>
<Icon icon="streamline-ultimate-color:copy-paste-1" width="19" height="19" @click.stop="copyAccount(item.email)"/>
<el-dropdown>
<Icon icon="fluent:settings-24-filled" width="21" height="21" color="#909399" />
<Icon icon="fluent:settings-24-filled" width="21" height="21" color="#909399" v-if="showNullSetting(item)" />
<el-dropdown v-else>
<Icon icon="fluent:settings-24-filled" width="21" height="21" color="#909399" />
<template #dropdown >
<el-dropdown-menu>
<el-dropdown-item @click="openSetName(item)">改名</el-dropdown-item>
<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-menu>
</template>
@@ -217,6 +218,10 @@ function openSetName (accountItem) {
setNameShow.value = true
}
function showNullSetting(item) {
return !hasPerm('email:send') && !(item.accountId !== userStore.user.accountId && hasPerm('account:delete'))
}
function itemBg(accountId) {
return accountStore.currentAccountId === accountId ? 'item-choose' : ''
}
@@ -388,8 +393,16 @@ path[fill="#ffdda1"] {
.icon{
cursor: pointer;
}
.icon:nth-child(2) {
margin-left: 15px;
.refresh {
margin-left: 10px;
}
.add {
margin-left: 2px;
}
.head-opt:not(.add) .refresh {
margin-left: 5px;
}
}
.scrollbar {

View File

@@ -11,11 +11,16 @@
<Icon icon="hugeicons:mailbox-01" width="20" height="20" />
<span class="menu-name" style="margin-left: 21px">收件箱</span>
</el-menu-item>
<el-menu-item @click="router.push({name: 'send'})" index="send"
<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>
</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>
</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" />
@@ -46,9 +51,14 @@
</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="hugeicons:key-02" width="22" height="22" />
<Icon icon="fluent:lock-closed-16-regular" width="22" height="22" />
<span class="menu-name" style="margin-left: 20px">权限控制</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>
</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" />

View File

@@ -1,10 +1,10 @@
<template>
<div class="header">
<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>
</div>
<div class="writer-box" @click="openSend">
<div v-perm="'email:send'" class="writer-box" @click="openSend">
<div class="writer" >
<Icon icon="material-symbols:edit-outline-sharp" width="22" height="22" />
</div>
@@ -95,10 +95,14 @@ const sendType = computed(() => {
return '无权限'
}
if (userStore.user.role.sendCount === 0) {
if (!userStore.user.role.sendCount) {
return '无限制'
}
if (userStore.user.role.sendCount < 0) {
return '无次数'
}
if (userStore.user.role.sendType === 'day') {
return '每天'
}
@@ -116,6 +120,11 @@ const sendCount = computed(() => {
if (!userStore.user.role.sendCount) {
return null
}
if (userStore.user.role.sendCount < 0) {
return null
}
return userStore.user.sendCount + '/' + userStore.user.role.sendCount
})
@@ -248,6 +257,10 @@ function full() {
grid-template-columns: auto auto 1fr;
}
.header.not-send {
grid-template-columns: auto 1fr;
}
.writer-box {
cursor: pointer;
display: flex;

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']">
<keep-alive :include="['email','sys-email','send','sys-setting','star','user','role','analysis','reg-key','draft']">
<component :is="Component" :key="route.name"/>
</keep-alive>
</router-view>

View File

@@ -1,5 +1,5 @@
<template>
<div class="send" v-show="show" @click="close">
<div class="send" v-show="show">
<div class="write-box" @click.stop>
<div class="title">
<div class="title-left">
@@ -15,7 +15,7 @@
</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="多个箱用, 分开 example1.com,example2.com" >
<template #prefix>
<div class="item-title">收件人 </div>
</template>
@@ -56,7 +56,7 @@
</template>
<script setup>
import tinyEditor from '@/components/tiny-editor/index.vue'
import {h, onMounted, onUnmounted, reactive, ref} from "vue";
import {h, nextTick, onMounted, onUnmounted, reactive, ref, toRaw} from "vue";
import {Icon} from "@iconify/vue";
import {useUserStore} from "@/store/user.js";
import {emailSend} from "@/request/email.js";
@@ -66,14 +66,19 @@ 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} from "@/utils/day.js";
import {formatDetailDate, fromNow} 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";
defineExpose({
open,
openReply
openReply,
openDraft
})
const draftStore = userDraftStore()
const settingStore = useSettingStore()
const emailStore = useEmailStore();
const accountStore = useAccountStore()
@@ -84,6 +89,12 @@ const percent = ref(0)
let percentMessage = null
let sending = false
const defValue = ref('')
const backReply = reactive({
receiveEmail: [],
subject: '',
content: '',
sendType: ''
})
const form = reactive({
sendEmail: '',
receiveEmail: [],
@@ -95,7 +106,8 @@ const form = reactive({
sendType: '',
text: '',
emailId: 0,
attachments: []
attachments: [],
draftId: null,
})
function addTagChange(val) {
@@ -110,10 +122,8 @@ function addTagChange(val) {
if (isEmail(email) && !form.receiveEmail.includes(email)) {
form.receiveEmail.push(email)
}
})
}
function checkDistribute() {
@@ -155,7 +165,6 @@ function chooseFile() {
})
return
}
const content = await fileToBase64(file)
form.attachments.push({content, filename, size, contentType})
}
@@ -217,7 +226,9 @@ async function sendEmail() {
})
sending = true
close()
show.value = false
emailSend(form, (e) => {
percent.value = Math.round((e.loaded * 98) / e.total)
}).then(emailList => {
@@ -225,15 +236,25 @@ async function sendEmail() {
emailList.forEach(item => {
emailStore.sendScroll?.addItem(item)
})
resetForm()
show.value = false
ElNotification({
title: '邮件已发送',
type: "success",
message: h('span', { style: 'color: teal' }, email.subject),
position: 'bottom-right'
})
userStore.refreshUserInfo();
if (form.draftId) {
form.subject = ''
form.content = ''
form.receiveEmail = []
draftStore.setDraft = {...toRaw(form)}
}
resetForm()
show.value = false
}).catch((e) => {
ElNotification({
title: '发送失败',
@@ -241,6 +262,7 @@ async function sendEmail() {
message: h('span', { style: 'color: teal' }, e.message),
position: 'bottom-right'
})
show.value = true
}).finally(() => {
percentMessage.close()
percent.value = 0
@@ -255,8 +277,13 @@ function resetForm() {
form.content = ''
form.manyType = null
form.attachments = []
form.sendType = null
form.sendType = ''
form.emailId = 0
form.draftId = null
backReply.content = ''
backReply.subject = ''
backReply.receiveEmail = []
backReply.sendType = ''
editor.value.clearEditor()
}
@@ -291,7 +318,13 @@ function openReply(email) {
</article>
</blockquote>`
open()
console.log(defValue.value)
nextTick(() => {
backReply.content = editor.value.getContent()
backReply.subject = form.subject
backReply.receiveEmail = form.receiveEmail
backReply.sendType = form.sendType
})
})
}
@@ -299,7 +332,6 @@ function openReply(email) {
function formatImage(content) {
content = content || '';
const domain = settingStore.settings.r2Domain;
console.log(content)
return content.replace(/{{domain}}/g, domain + '/');
}
@@ -317,6 +349,14 @@ function open() {
editor.value.focus()
}
function openDraft(draft) {
Object.assign(form,{...draft})
defValue.value = ''
setTimeout(() => defValue.value = form.content)
show.value = true;
editor.value.focus()
}
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
close()
@@ -332,7 +372,52 @@ onUnmounted(() => {
});
function close() {
show.value = false;
if (form.draftId) {
draftStore.setDraft = {...toRaw(form)}
show.value = false
resetForm()
return;
}
if (!(form.content || form.subject || form.receiveEmail.length > 0)) {
show.value = false
resetForm()
return;
}
if (backReply.sendType === 'reply') {
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]
if (subjectFlag && contentFlag && receiveFlag) {
resetForm();
close()
return;
}
}
ElMessageBox.confirm('是否保存草稿?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
distinguishCancelAndClose: true
}).then( async () => {
const formData = {...toRaw(form)}
delete formData.draftId
delete formData.attachments
formData.createTime = dayjs().utc().format('YYYY-MM-DD HH:mm:ss');
const draftId = await db.value.draft.add({...formData})
db.value.att.add({draftId,attachments: toRaw(form.attachments)})
draftStore.refreshList ++
show.value = false
}).catch((action) => {
if (action === 'cancel') {
show.value = false
resetForm()
}
})
}
</script>