新增点击复制邮箱功能
This commit is contained in:
@@ -45,6 +45,8 @@
|
|||||||
|
|
||||||
- **📦附件收发**:支持收发附件,使用R2对象存储保存和下载文件
|
- **📦附件收发**:支持收发附件,使用R2对象存储保存和下载文件
|
||||||
|
|
||||||
|
- **🔔邮件推送**:接收邮件后可以转发到TG机器人或其他服务商邮箱
|
||||||
|
|
||||||
- **📈数据可视化**:使用echarts对系统数据详情,用户邮件增长可视化显示
|
- **📈数据可视化**:使用echarts对系统数据详情,用户邮件增长可视化显示
|
||||||
|
|
||||||
- **⭐星标邮件**:标记重要邮件,以便快速查阅
|
- **⭐星标邮件**:标记重要邮件,以便快速查阅
|
||||||
@@ -186,6 +188,7 @@ cloud-mail
|
|||||||
│ │ ├── entity #数据库实体层
|
│ │ ├── entity #数据库实体层
|
||||||
│ │ ├── error #自定义异常
|
│ │ ├── error #自定义异常
|
||||||
│ │ ├── hono #web框架配置 拦截器等
|
│ │ ├── hono #web框架配置 拦截器等
|
||||||
|
│ │ ├── init #数据库缓存初始化
|
||||||
│ │ ├── model #响应体数据封装
|
│ │ ├── model #响应体数据封装
|
||||||
│ │ ├── security #身份认证层
|
│ │ ├── security #身份认证层
|
||||||
│ │ ├── service #服务层
|
│ │ ├── service #服务层
|
||||||
|
|||||||
BIN
demo/demo8.png
BIN
demo/demo8.png
Binary file not shown.
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 207 KiB |
@@ -302,7 +302,7 @@ function htmlToText(email) {
|
|||||||
|
|
||||||
function cleanSpace(text) {
|
function cleanSpace(text) {
|
||||||
return text
|
return text
|
||||||
.replace(/[\u200B-\u200F\uFEFF\u034F\u200B-\u200F\u00A0\u3000]/g, '') // 移除零宽空格
|
.replace(/[\u200B-\u200F\uFEFF\u034F\u200B-\u200F\u00A0\u3000\u00AD]/g, '')// 移除零宽空格
|
||||||
.replace(/\s+/g, ' ') // 多空白合并成一个空格
|
.replace(/\s+/g, ' ') // 多空白合并成一个空格
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,13 @@
|
|||||||
<el-scrollbar class="scrollbar">
|
<el-scrollbar class="scrollbar">
|
||||||
<div v-infinite-scroll="getAccountList" :infinite-scroll-distance="600" :infinite-scroll-immediate="false">
|
<div v-infinite-scroll="getAccountList" :infinite-scroll-distance="600" :infinite-scroll-immediate="false">
|
||||||
<el-card class="item" :class="itemBg(item.accountId)" v-for="item in accounts" :key="item.accountId" @click="changeAccount(item)">
|
<el-card class="item" :class="itemBg(item.accountId)" v-for="item in accounts" :key="item.accountId" @click="changeAccount(item)">
|
||||||
<div class="account" @click.stop>
|
<div class="account" @click.stop="copyAccount(item.email)">
|
||||||
{{ item.email }}
|
<el-tooltip v-if="showCopyInfo" effect="dark" :hide-after="0" :show-after="800" placement="top" content="点击复制">
|
||||||
|
{{ item.email }}
|
||||||
|
</el-tooltip>
|
||||||
|
<template v-else >
|
||||||
|
{{ item.email }}
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="opt">
|
<div class="opt">
|
||||||
<div class="send-email" @click.stop>
|
<div class="send-email" @click.stop>
|
||||||
@@ -142,6 +147,7 @@ const addRef = ref({})
|
|||||||
let account = null
|
let account = null
|
||||||
let turnstileId = null
|
let turnstileId = null
|
||||||
let verifyToken = ''
|
let verifyToken = ''
|
||||||
|
let showCopyInfo = window.innerWidth > 1024
|
||||||
const addForm = reactive({
|
const addForm = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
suffix: settingStore.domainList[0]
|
suffix: settingStore.domainList[0]
|
||||||
@@ -264,6 +270,24 @@ function add() {
|
|||||||
},100)
|
},100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyAccount(account) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(account);
|
||||||
|
ElMessage({
|
||||||
|
message: '复制成功',
|
||||||
|
type: 'success',
|
||||||
|
plain: true,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error('复制失败:', err);
|
||||||
|
ElMessage({
|
||||||
|
message: '复制失败',
|
||||||
|
type: 'error',
|
||||||
|
plain: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getAccountList() {
|
function getAccountList() {
|
||||||
|
|
||||||
if (loading.value || followLoading.value || noLoading.value) return;
|
if (loading.value || followLoading.value || noLoading.value) return;
|
||||||
|
|||||||
@@ -19,12 +19,12 @@
|
|||||||
<el-menu-item @click="router.push({name: 'star'})" index="star"
|
<el-menu-item @click="router.push({name: 'star'})" index="star"
|
||||||
:class="route.meta.name === 'star' ? 'choose-item' : ''">
|
:class="route.meta.name === 'star' ? 'choose-item' : ''">
|
||||||
<Icon icon="solar:star-line-duotone" width="20" height="20" />
|
<Icon icon="solar:star-line-duotone" width="20" height="20" />
|
||||||
<span class="menu-name" style="margin-left: 20px">星标邮件</span>
|
<span class="menu-name" style="margin-left: 21px">星标邮件</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item @click="router.push({name: 'setting'})" index="setting"
|
<el-menu-item @click="router.push({name: 'setting'})" index="setting"
|
||||||
:class="route.meta.name === 'setting' ? 'choose-item' : ''">
|
:class="route.meta.name === 'setting' ? 'choose-item' : ''">
|
||||||
<Icon icon="fluent:settings-48-regular" width="20" height="20" />
|
<Icon icon="fluent:settings-48-regular" width="20" height="20" />
|
||||||
<span class="menu-name" style="margin-left: 20px">个人设置</span>
|
<span class="menu-name" style="margin-left: 21px">个人设置</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<div class="manage-title" v-perm="['user:query','role:query','setting:query','analysis:query']">
|
<div class="manage-title" v-perm="['user:query','role:query','setting:query','analysis:query']">
|
||||||
<div>管理</div>
|
<div>管理</div>
|
||||||
@@ -32,27 +32,27 @@
|
|||||||
<el-menu-item @click="router.push({name: 'analysis'})" index="analysis" v-perm="'analysis:query'"
|
<el-menu-item @click="router.push({name: 'analysis'})" index="analysis" v-perm="'analysis:query'"
|
||||||
:class="route.meta.name === 'analysis' ? 'choose-item' : ''">
|
:class="route.meta.name === 'analysis' ? 'choose-item' : ''">
|
||||||
<Icon icon="fluent:data-pie-20-regular" width="24" height="24" />
|
<Icon icon="fluent:data-pie-20-regular" width="24" height="24" />
|
||||||
<span class="menu-name" style="margin-left: 16px">分析页</span>
|
<span class="menu-name" style="margin-left: 18px">分析页</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item @click="router.push({name: 'user'})" index="setting" v-perm="'user:query'"
|
<el-menu-item @click="router.push({name: 'user'})" index="setting" v-perm="'user:query'"
|
||||||
:class="route.meta.name === 'user' ? 'choose-item' : ''">
|
:class="route.meta.name === 'user' ? 'choose-item' : ''">
|
||||||
<Icon icon="iconoir:user" width="24" height="24" />
|
<Icon icon="si:user-alt-2-line" width="20" height="20" />
|
||||||
<span class="menu-name" style="margin-left: 16px">用户列表</span>
|
<span class="menu-name" style="margin-left: 21px">用户列表</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item @click="router.push({name: 'sys-email'})" index="sys-email" v-perm="'sys-email:query'"
|
<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' : ''">
|
:class="route.meta.name === 'sys-email' ? 'choose-item' : ''">
|
||||||
<Icon icon="fluent:mail-list-28-regular" width="22" height="22" />
|
<Icon icon="fluent:mail-list-28-regular" width="22" height="22" />
|
||||||
<span class="menu-name" style="margin-left: 18px">邮件列表</span>
|
<span class="menu-name" style="margin-left: 20px">邮件列表</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item @click="router.push({name: 'role'})" index="setting" v-perm="'role:query'"
|
<el-menu-item @click="router.push({name: 'role'})" index="setting" v-perm="'role:query'"
|
||||||
:class="route.meta.name === 'role' ? 'choose-item' : ''">
|
:class="route.meta.name === 'role' ? 'choose-item' : ''">
|
||||||
<Icon icon="hugeicons:key-02" width="22" height="22" />
|
<Icon icon="hugeicons:key-02" width="22" height="22" />
|
||||||
<span class="menu-name" style="margin-left: 18px">权限控制</span>
|
<span class="menu-name" style="margin-left: 20px">权限控制</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item @click="router.push({name: 'sys-setting'})" index="sys-setting" v-perm="'setting:query'"
|
<el-menu-item @click="router.push({name: 'sys-setting'})" index="sys-setting" v-perm="'setting:query'"
|
||||||
:class="route.meta.name === 'sys-setting' ? 'choose-item' : ''">
|
:class="route.meta.name === 'sys-setting' ? 'choose-item' : ''">
|
||||||
<Icon icon="eos-icons:system-ok-outlined" width="18" height="18" />
|
<Icon icon="eos-icons:system-ok-outlined" width="18" height="18" />
|
||||||
<span class="menu-name" style="margin-left: 23px">系统设置</span>
|
<span class="menu-name" style="margin-left: 24px">系统设置</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -174,7 +174,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<div class="card-title">邮件转发通知</div>
|
<div class="card-title">邮件推送</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div><span>Telegram 机器人</span></div>
|
<div><span>Telegram 机器人</span></div>
|
||||||
@@ -362,7 +362,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="forward-head">
|
<div class="forward-head">
|
||||||
<span class="forward-set-title">第三方邮箱</span>
|
<span class="forward-set-title">第三方邮箱</span>
|
||||||
<el-tooltip effect="dark" trigger="click" content="可以将邮件转到其他服务商邮箱,需要在cloudflare验证邮箱">
|
<el-tooltip effect="dark" content="可以将邮件转到其他服务商邮箱,需要在cloudflare验证邮箱">
|
||||||
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
|
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
mail-worker/dist/index.html
vendored
4
mail-worker/dist/index.html
vendored
@@ -6,8 +6,8 @@
|
|||||||
<title></title>
|
<title></title>
|
||||||
<link rel="icon" href="/assets/favicon-C5dAZutX.svg" type="image/svg+xml">
|
<link rel="icon" href="/assets/favicon-C5dAZutX.svg" type="image/svg+xml">
|
||||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||||
<script type="module" crossorigin src="/assets/index-Cgh0xJS2.js"></script>
|
<script type="module" crossorigin src="/assets/index-DJSpSDrA.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Cobuvpco.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DuYAEzF2.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="loading-first">
|
<div id="loading-first">
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export async function email(message, env, ctx) {
|
|||||||
<b>收件人:\u200B</b>${message.to}
|
<b>收件人:\u200B</b>${message.to}
|
||||||
<b>时间:</b>${dayjs.utc(emailRow.createTime).tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm')}
|
<b>时间:</b>${dayjs.utc(emailRow.createTime).tz('Asia/Shanghai').format('YYYY-MM-DD HH:mm')}
|
||||||
|
|
||||||
${emailUtils.htmlToText(params.content) || ''}
|
${params.text || emailUtils.htmlToText(params.content) || ''}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const tgChatIds = tgChatId.split(',');
|
const tgChatIds = tgChatId.split(',');
|
||||||
|
|||||||
Reference in New Issue
Block a user